// on road work mode flash orange light only at all junction
void FlashForRoadWork() {
    static Counter roadWorkCounter;
    // flash the orange light by checking the counter time
    if(roadWorkCounter.GetCounter() <= 500) {
        trafficLight0.RoadWorkLightOnly();
        trafficLight90.RoadWorkLightOnly();
        trafficLight270.RoadWorkLightOnly();
    }
    else if(roadWorkCounter.GetCounter() <= 1000) {
        trafficLight0.ledOrange.SwitchOff();
        trafficLight90.ledOrange.SwitchOff();
        trafficLight270.ledOrange.SwitchOff();
    }
    else
        roadWorkCounter.Reset();
}
// on ped crossing mode when time is finished flash ped light at all junction
void FlashForPedCrossFinish() {
    static Counter pedCrossCounter;
    // flash the ped light by checking the counter time
    if(pedCrossCounter.GetCounter() <= 500) {
        trafficLight270.ledPed.SwitchOn();
        trafficLight0.ledPed.SwitchOn();
        trafficLight90.ledPed.SwitchOn();
    }
    else if(pedCrossCounter.GetCounter() <= 1000) {
        trafficLight0.ledPed.SwitchOff();
        trafficLight90.ledPed.SwitchOff();
        trafficLight270.ledPed.SwitchOff();
    }
    else
        pedCrossCounter.Reset();
}
// Method is called when the obeservable object has a change in its state
void CounterObserver::Update(Observable* obj)
{
	Counter* pCounter = dynamic_cast<Counter*>(obj);
	if(pCounter != NULL)
		std::cout << "Observer address : " << this << " Counter has changed : " << pCounter->GetCounter() << std::endl;
	else
		std::cout << "Unknown observable" << std ::endl;
}
 // this function simulates normal light condition when called at every loop
 // it automatically transitions from green to red signal by using the OrangeTransitionCounter
 void UpdateWithLightTransition(bool goSignal, bool turnLight, bool pedLight, int orangeLightTime) {
 
     // if green or go signal turn off transitioning process 
     // and only light up the green light
     if (goSignal) {
         transitionState = 0;
         GreenLight();
     }
     
     // if red signal and green light was previously on
     // start transitioning to red by firt turning on the 
     // orange light
     else if(!goSignal && ledGreen.IsOn() && transitionState == 0) {
         OrangeLight();
         transitionState = 1;
         OrangeTransitionCounter.Reset();
     } 
     // if red signal and greenlight was not on, directly
     // switch on the red light
     else if(!goSignal && !ledGreen.IsOn() && transitionState == 0)
         RedLight();
     
     // check if we are still transitioning to red light
     // if orangeTransitionCounter has passed orangeLightTime interval 
     // turn on the Red light only
     if (transitionState == 1) {
         if (OrangeTransitionCounter.GetCounter() < orangeLightTime)
             OrangeLight();
         else
             transitionState = 2;
     }
     
     if (transitionState == 2)
         RedLight();
         
     if (turnLight)
         ledFilterTurn.SwitchOn();
     else
         ledFilterTurn.SwitchOff();
     
     // ped lights turn on only when the normal lights are in red signal
     if (pedLight && ledRed.IsOn())
         ledPed.SwitchOn();
     else
         ledPed.SwitchOff();
 }
// Update normal light sequence by checking the main counter
int UpdateNormalSequence(int goLightTime) {     
    int i = 1;
    while (mainCounter.GetCounter() > i * goLightTime)
        i++;
    
    // On Select Sequence Button, cycle through the sequences immediately
    if (btnSelectSequence.IsPressed()) {
        // reset the main counter to start of new sequence
        mainCounter.Reset(i * goLightTime);
        i++;
    }
        
    // if maximum sequences has reached reset the main counter
    if (i == MAX_SEQUENCES + 1) {
        mainCounter.Reset();
        i = 1;
    }
    
    return i;
}
void loop() {
    delay(50);
    
    boolean hotDay = ther.GetKelvinTempUsingHart() >= THER_CRITICAL_DEG;
    boolean nightMode = ldr.ReadADC() <= LDR_DARK_ADC;
    
    static int goLightTimeCalibed = DEFAULT_GO_LIGHT_TIME;
    static int orangeLightTimeCalibed = DEFAULT_ORANGE_LIGHT_TIME;
    
    // if both emergency button and road work are pressed activate the calibration
    if (btnEmergency.IsPressed() && btnRoadWork.IsPressed()) {
        goLightTimeCalibed = map(pot.ReadADC(), 0, 1023, DEFAULT_GO_LIGHT_TIME/2, DEFAULT_GO_LIGHT_TIME * 2);
        orangeLightTimeCalibed = map(pot.ReadADC(), 0, 1023, DEFAULT_ORANGE_LIGHT_TIME/2, DEFAULT_ORANGE_LIGHT_TIME * 2);
    } 
    else {
        boolean emergencyMode = btnEmergency.ReadSwitch();
        boolean roadWorkMode = btnRoadWork.ReadSwitch();
        
        if (emergencyMode) {
            OnEmergencyMode();
        }
        
        if (roadWorkMode) {
            FlashForRoadWork();
        }
        
        // pause the main counter when one of the modes are activated
        if(roadWorkMode || emergencyMode) {
            mainCounter.Pause();
            return;
        }
    }
    
    int goLightTime = goLightTimeCalibed;
    int orangeLightTime = orangeLightTimeCalibed;
    double multiplier =  1.0;
    
    if (nightMode) { 
        multiplier += 0.4;
    }
    
    if(hotDay) {
        multiplier += 0.3;
    }
    
    // extended the go or green signal time and the orange light time
    int goLightTime = goLightTimeCalibed * multiplier;
    int orangeLightTime = orangeLightTimeCalibed * multiplier;
    
    // get the current state of the sequence
    int state = UpdateNormalSequence(goLightTime);
    
    // print out the state of the system for debugging
    Serial.print("Temperature: ");
    Serial.print(ther.GetKelvinTempUsingHart());
    Serial.print(" LDR: ");
    Serial.print(ldr.ReadADC());
    Serial.print(" PotADC: ");
    Serial.print(pot.ReadADC());
    Serial.print(" Cross: ");
    Serial.print(btnPedCross0.IsPressed() || btnPedCross90.IsPressed() || btnPedCross270.IsPressed());
        
    Serial.print(" main Counter: ");
    Serial.print(mainCounter.GetCounter());
    Serial.print(" goTime: ");
    Serial.print(goLightTime);
    Serial.print(" Orange: ");
    Serial.print(orangeLightTime);
    Serial.print(" state: ");   
    Serial.println(state);
        
    // if alarm is in progress, exit from normal sequence
    if (AddPedCrossStageOnRequest(state, goLightTime, orangeLightTime))
        return;
    
    // unpause the main counter if it was paused
    mainCounter.UnPause();
    
    // update the leds on the traffic light depending on the values in the sequence array
    trafficLight270.UpdateWithLightTransition(sequence[state - 1][0], sequence[state - 1][1], sequence[state - 1][2], orangeLightTime);
    trafficLight0.UpdateWithLightTransition(sequence[state - 1][3], sequence[state - 1][4], sequence[state - 1][5], orangeLightTime);
    trafficLight90.UpdateWithLightTransition(sequence[state - 1][6], sequence[state - 1][7], sequence[state - 1][8], orangeLightTime);
}
// function to allow for ped crossing on the press of a button
// ped crossing works by pausing the main counter at the end of a sequence 
// and resuming after the ped has crossed.
// returns whether alarm is currently on progression or not
bool AddPedCrossStageOnRequest(int state, int pedCrossTime, int orangeLightTime){
#define NO_PED_CROSS 0
#define PED_CROSS_BTN_PRESSED 1
#define PED_CROSS_IN_PROGRESS 2
    static int alarmState = 0;
    
    // check for button presses at all the junctions
    if(btnPedCross0.IsPressed() || btnPedCross90.IsPressed() || btnPedCross270.IsPressed())
        if (alarmState == NO_PED_CROSS) alarmState = PED_CROSS_BTN_PRESSED;
    
    // update the buzzer asyncronously
    trafficLight0.crossingBuzzer.UpdateAsync();
    trafficLight90.crossingBuzzer.UpdateAsync();
    trafficLight270.crossingBuzzer.UpdateAsync();
    
    static int prevState = 0;
    static Counter pedCrossCounter;
    
    // check if the current sequence has ended
    if (prevState != state && alarmState == PED_CROSS_BTN_PRESSED) {
        // pause the timer
        mainCounter.Pause();
        // reset the crossing counter to 0
        pedCrossCounter.Reset();
        // activate the flag to transition all the green signal to red first
        alarmState = PED_CROSS_IN_PROGRESS; 
        return true;
    }

    if (alarmState == PED_CROSS_IN_PROGRESS) {
        if (pedCrossCounter.GetCounter() <= pedCrossTime) {
                // if there were no green lights from the previous sequence, directly turn on the red light
                if (trafficLight0.ledRed.IsOn() && trafficLight90.ledRed.IsOn() && trafficLight270.ledRed.IsOn())
                {
                    trafficLight270.UpdateWithLightTransition(0, 0, 1, orangeLightTime);
                    trafficLight0.UpdateWithLightTransition(0, 0, 1, orangeLightTime);
                    trafficLight90.UpdateWithLightTransition(0, 0, 1, orangeLightTime);
                    trafficLight0.crossingBuzzer.TriggerAlarm();
                    trafficLight90.crossingBuzzer.TriggerAlarm();
                    trafficLight270.crossingBuzzer.TriggerAlarm();
                }
                // if there were green lights on the previous sequence, transition to red first
                else {
                    trafficLight270.UpdateWithLightTransition(0, 0, 0, orangeLightTime);
                    trafficLight0.UpdateWithLightTransition(0, 0, 0, orangeLightTime);
                    trafficLight90.UpdateWithLightTransition(0, 0, 0, orangeLightTime);
                    trafficLight0.crossingBuzzer.TurnOffAlarm();
                    trafficLight90.crossingBuzzer.TurnOffAlarm();
                    trafficLight270.crossingBuzzer.TurnOffAlarm();
                }
                return true;
        } 
        // if ped cross time has finish blink the ped light as in australian ped crossing
        else if (pedCrossCounter.GetCounter() <= pedCrossTime + orangeLightTime) {
            FlashForPedCrossFinish();
            trafficLight0.crossingBuzzer.TurnOffAlarm();
            trafficLight90.crossingBuzzer.TurnOffAlarm();
            trafficLight270.crossingBuzzer.TurnOffAlarm();
            return true;
        } 
        else {
            alarmState = NO_PED_CROSS;
        }
    }
    prevState = state;
    return false;
}