//-----!!!!!!!!!!!!!!!!!!!!!-IMPORTANT-!!!!!!!!!!!!!!!!!!!!!!!!!!!!
// The physical cyberdeck has a hardware error with pin assignments vs this WOKWI sketch.
// The LED instances section contains a commented out section that must be swapped in for correct pin assignment
// See lines 93-114
//-----------------------------------------------------------------


//------------------------------Versioning---------------------------
// V0.1 initial software load. Worked but drained battery
// V0.2 added auto dimming feature and auto-off feature
//        Auto dimming - dims everything to min after 1 min of inactivity
//        Auto off     - turns off all LEDs after 2 min of inactivity 
//                     - places arduino into low power "off" mode and waits for inturrups to wake
// Known bug: The auto dimming feature doesn't really work but the power seems to shut off.
//-------------------------------------------------------------------

// -----------------------------Include libraries -------------------
#include <Arduino.h>
#include <LowPower.h>

// -----------------------------Define variables----------------------
int debounceDelay = 50;
int minLEDdim = 10; // minimum LED dim level (never let the pots turn the LEDs off completely)
unsigned long dimTimeout = 60000; //time in ms to wait after the last switch changes state before dimming all LEDs
int dimLowPower = 10; //if the dimTimeout is exceeded, use this dim level (dim is out of 254)
unsigned long LEDtimeout = 120000; //time in ms to wait after the last switch changes state before turning LEDs off
unsigned long lastActivityTime = 0; // Keeps track of the last activity time
bool lowPowerMode = false; // Flag to indicate if the system is in low-power mode

// -----------------------------Define structures---------------------
struct Switch {
  String name;
  int pin1;
  //  int pin2;  // for SP3T, 0 for SPST (pin 2 is always gnd - no definition req'd)
  int pin3;  // for SP3T, 0 for SPST
  int state1; // the states match the LED structure for easy matching
  int state2; // this will be set to high if state 1 and 3 are low 
  int state3; // this should initialize at low and stay low for SPST switches (which don't have a third state)
};

struct LEDSet {
  String name;
  int dimPin; // for sets of leds that only have one, only the dim pin and dim level is used
  int pin1; // Used for 2 or 3 LEDs, 0 if 1 LED
  int pin2; // Used for 2 or 3 LEDs, 0 if 1 LED
  int pin3;  // Used for 3 LEDs, 0 otherwise
  int state1;
  int state2; 
  int state3;
  int dimLevel;
};

struct Button {
  String name;
  int pin;
  int state;
  int lastState;
  unsigned long lastDebounceTime;  // debounce time
};

struct Potentiometer {
  String name;
  int pin;
  int state; //this will utilize the analog in values from 0-1024
};

struct SimonLED {
  String name;
  int dimPin;
  int dimLevel;
  unsigned long turnOffTime;
};

// ----------------------instances of structures----------------------------
// Switch instances (set here is the set of data for one switch)
// struct: name, pin 1, pin 3, State 1, State 2, State 3)
Switch SWset1 = {"PowerSwitch1", A13, 0, LOW, LOW, LOW};
Switch SWset2 = {"PowerSwitch2", A14, 0, LOW, LOW, LOW};
Switch SWset3 = {"SelectionSwitch1", A12, 0, LOW, LOW, LOW};
Switch SWset4 = {"SelectionSwitch2", A15, 0, LOW, LOW, LOW};
Switch SWset5 = {"SP3T1", 22, 23, LOW, LOW, LOW};
Switch SWset6 = {"SP3T2", 24, 25, LOW, LOW, LOW};
Switch SWset7 = {"SP3T3", 26, 27, LOW, LOW, LOW};
Switch SWset8 = {"SP3T4", 28, 29, LOW, LOW, LOW};
Switch SWset9 = {"SPST1", A10, 0, LOW, LOW, LOW};
Switch SWset10 = {"SPST2", A11, 0, LOW, LOW, LOW};

// LED instances (set here is up to 3 LEDs and associated with the same switch number)
// struct: name, dim pin, pin 1, pin 2, pin 3, State 1, State 2, State 3, dimlevel)
LEDSet LEDset1 = {"PowerLED1", 6, 0, 0, 0, HIGH, HIGH, HIGH, 128};
LEDSet LEDset2 = {"PowerLED2", 7, 0, 0, 0, HIGH, HIGH, HIGH, 128};
LEDSet LEDset3 = {"null", 0, 0, 0, 0, HIGH, HIGH, HIGH, 0};
LEDSet LEDset4 = {"null", 0, 0, 0, 0, HIGH, HIGH, HIGH, 0};

//------------This section valid for WOKWI simulator---------------
//-------------Comment out for physical instance-------------------
LEDSet LEDset5 = {"SP3TSetLED1", 2, 30, 31, 32, HIGH, HIGH, HIGH, 128};
LEDSet LEDset6 = {"SP3TSetLED2", 3, 33, 34, 35, HIGH, HIGH, HIGH, 128};
LEDSet LEDset7 = {"SP3TSetLED3", 4, 36, 37, 38, HIGH, HIGH, HIGH, 128};
LEDSet LEDset8 = {"SP3TSetLED4", 5, 39, 40, 41, HIGH, HIGH, HIGH, 128};
LEDSet LEDset9 = {"SPSTSetLED1", 44, 43, 42, 0, HIGH, HIGH, HIGH, 128};
LEDSet LEDset10 = {"SPSTSetLED2", 45, 49, 48, 0, HIGH, HIGH, HIGH, 128};
//-----------------------------------------------------------------

//-------------This section valid for physical instance-------------
/*
LEDSet LEDset5 = {"SP3TSetLED1", 2, 30, 32, 34, HIGH, HIGH, HIGH, 128};
LEDSet LEDset6 = {"SP3TSetLED2", 3, 36, 38, 40, HIGH, HIGH, HIGH, 128};
LEDSet LEDset7 = {"SP3TSetLED3", 4, 31, 35, 33, HIGH, HIGH, HIGH, 128};
LEDSet LEDset8 = {"SP3TSetLED4", 5, 37, 39, 41, HIGH, HIGH, HIGH, 128};
LEDSet LEDset9 = {"SPSTSetLED1", 44, 43, 42, 0, HIGH, HIGH, HIGH, 128};
LEDSet LEDset10 = {"SPSTSetLED2", 45, 48, 49, 0, HIGH, HIGH, HIGH, 128};
*/
//------------------------------------------------------------------

// Potentiometer instances
Potentiometer Pot1 = {"Potentiometer1", A0, 0};
Potentiometer Pot2 = {"Potentiometer2", A1, 0};
Potentiometer Pot3 = {"Potentiometer3", A2, 0};

// Create instances of the Buttons
Button ButtonDown = {"Down", 50, HIGH, HIGH, 0};
Button ButtonUp = {"Up", 51, HIGH, HIGH, 0};
Button ButtonLeft = {"Left", 52, HIGH, HIGH, 0};
Button ButtonRight = {"Right", 53, HIGH, HIGH, 0};

// Create instances of the simon says LEDs
SimonLED SimonLEDLeft = {"Left", 8, 0, 0};
SimonLED SimonLEDRight = {"Right", 9, 0, 0};
SimonLED SimonLEDUp = {"Up", 10, 0, 0};
SimonLED SimonLEDDown = {"Down", 11, 0, 0};

//------------------------------Arrays of Structures-----------------------
//This could be combined with the above, if the code doesn't need to individually
//call the above structure instances, but I'm keeping them separate for the moment.

// Array of all potentiometers
Potentiometer allPots[] = {Pot1, Pot2, Pot3};

// Array of all switch sets
Switch allSwitches[] = {SWset1, SWset2, SWset3, SWset4, SWset5, SWset6, SWset7, SWset8, SWset9, SWset10};

// Array of all LED sets
LEDSet allLEDs[] = {LEDset1, LEDset2, LEDset3, LEDset4, LEDset5, LEDset6, LEDset7, LEDset8, LEDset9, LEDset10}; // Add LEDset9 and LEDset10

// Array of all buttons
Button allButtons[] = {ButtonDown, ButtonUp, ButtonLeft, ButtonRight};

// Array of simon LEDs
SimonLED simonLEDs[] = {SimonLEDDown, SimonLEDUp, SimonLEDLeft, SimonLEDRight};


//--------------------------------Functions --------------------------------
void checkSwitchStates() {
  bool activityDetected = false;
  
  for (int i = 0; i < sizeof(allSwitches) / sizeof(Switch); i++) {
    // Check the state of pin 1
    int currentState1 = digitalRead(allSwitches[i].pin1);
    if (currentState1 != allSwitches[i].state1) {
      allSwitches[i].state1 = currentState1;
      activityDetected = true;
    }
    
    // If pin 3 exists, check its state. This is only for SP3T switches.
    if (allSwitches[i].pin3 != 0) {
      int currentState3 = digitalRead(allSwitches[i].pin3);
      if (currentState3 != allSwitches[i].state3) {
        allSwitches[i].state3 = currentState3;
        activityDetected = true;
      }
    } else {
      allSwitches[i].state3 = HIGH; // If pin 3 is 0 then it's an SPST switch and we keep state 3 High (off)
    }

    // Set state2 LOW if states 1 and 3 are HIGH (off)
    if (allSwitches[i].state1 == HIGH && allSwitches[i].state3 == HIGH) {
      if (allSwitches[i].state2 != LOW) {
        allSwitches[i].state2 = LOW;
        activityDetected = true;
      }
    } else {
      if (allSwitches[i].state2 != HIGH) {
        allSwitches[i].state2 = HIGH;
        activityDetected = true;
      }
    }
  }
  
  if (activityDetected) {
    lastActivityTime = millis(); // Update last activity time
    lowPowerMode = false;        // Exit low-power mode on activity
  }
}



void matchLEDStates() {
  if (lowPowerMode) return; // Exit if in low-power mode
  for(int i = 0; i < sizeof(allSwitches) / sizeof(Switch); i++) {
    if (allLEDs[i].name != "null") {
      allLEDs[i].state1 = allSwitches[i].state1;
      allLEDs[i].state2 = allSwitches[i].state2;
      allLEDs[i].state3 = allSwitches[i].state3;
    }
  }
}

void updateLEDs() {
  if (lowPowerMode) return; // Exit if in low-power mode
  for(int i = 0; i < sizeof(allLEDs) / sizeof(LEDSet); i++) {
    if (allLEDs[i].name != "null") {
      // If the LED set only contains one LED and no control pins,
      // turn it off when state1 is HIGH by setting analogWrite to 0.
      if(allLEDs[i].pin1 == 0 && allLEDs[i].pin2 == 0 && allLEDs[i].pin3 == 0 && allLEDs[i].state1 == HIGH) {
        analogWrite(allLEDs[i].dimPin, 0);

      } else {   

        // Turn on or off each LED based on its state by matching the switch state (LOW is ON)
        if(allLEDs[i].pin1 != 0) {
          digitalWrite(allLEDs[i].pin1, allLEDs[i].state1); 
        }
        if(allLEDs[i].pin2 != 0) {
          digitalWrite(allLEDs[i].pin2, allLEDs[i].state2); 
        }
        if(allLEDs[i].pin3 != 0) {
          digitalWrite(allLEDs[i].pin3, allLEDs[i].state3); 
        }
        
        // Update the brightness level
        analogWrite(allLEDs[i].dimPin, allLEDs[i].dimLevel);
      }
    }
  }
}

void checkPotentiometers() {
  for(int i = 0; i < sizeof(allPots) / sizeof(Potentiometer); i++) {
    // Read the value of the potentiometer (from 0 to 1023)
    allPots[i].state = analogRead(allPots[i].pin);

    // Scale the value down to between 0 and 255
    allPots[i].state = map(allPots[i].state, 0, 1023, 0, 255);

    // Print the potentiometer state to the serial monitor
    //Serial.print(allPots[i].name);
    //Serial.print(": ");
    //Serial.println(allPots[i].state);
  }
}

void setLEDDimming() {
  if (lowPowerMode) return; // Exit if in low-power mode
  for(int i = 0; i < sizeof(allLEDs) / sizeof(LEDSet); i++) {
    if (allLEDs[i].name != "null") {
      // Use the state of the corresponding potentiometer to set the dimming level
      allLEDs[i].dimLevel = max(allPots[i % (sizeof(allPots) / sizeof(Potentiometer))].state, minLEDdim);
    }
    // Print the LED dimLevel to the serial monitor
    //Serial.print(allLEDs[i].name);
    //Serial.print(": ");
    //Serial.println(allLEDs[i].dimLevel);
  }
  //set the dim level for the simon leds, but use the pot on the left
  for (int i = 0; i < sizeof(simonLEDs) / sizeof(SimonLED); i++){
    if (simonLEDs[i].name != "null") {
      // Use the state of the corresponding potentiometer to set the dimming level
      simonLEDs[i].dimLevel = max(allPots[0].state, minLEDdim);
    }
  }
}

void checkButtons() {
  for (int i = 0; i < sizeof(allButtons) / sizeof(Button); i++) {
    int reading = digitalRead(allButtons[i].pin);

    if (reading != allButtons[i].lastState) {
      allButtons[i].lastDebounceTime = millis();
    }

    if ((millis() - allButtons[i].lastDebounceTime) > debounceDelay) {
      if (reading != allButtons[i].state) {
        allButtons[i].state = reading;

        // Print the button state to the serial monitor for debugging
        //Serial.print(allButtons[i].name);
        //Serial.print(": ");
        //Serial.println(allButtons[i].state == HIGH ? "HIGH" : "LOW");
      }
    }

    allButtons[i].lastState = reading;
  }
}

void controlSimonLEDs() {
  if (lowPowerMode) return; // Exit if in low-power mode
  for (int i = 0; i < sizeof(simonLEDs) / sizeof(SimonLED); i++) {
    if (allButtons[i].state == LOW && millis() >= simonLEDs[i].turnOffTime) {
      simonLEDs[i].turnOffTime = millis() + 1000;
      analogWrite(simonLEDs[i].dimPin, simonLEDs[i].dimLevel);
    } else if (millis() >= simonLEDs[i].turnOffTime) {
      simonLEDs[i].dimLevel = 0; // Ensure the LED dim level is reset to 0 when turned off
      analogWrite(simonLEDs[i].dimPin, 0);
    }
  }
}


void checkForInactivity() {
  unsigned long currentTime = millis();
  
  // Check if dimming should be applied
  if ((currentTime - lastActivityTime) >= dimTimeout && (currentTime - lastActivityTime) < LEDtimeout) {
    Serial.print("Dimming Applied");
    applyDimming();
    lowPowerMode = false;  // Not yet in full low-power mode, only dimmed
  }
  
  // Check if LEDs should be turned off
  if ((currentTime - lastActivityTime) >= LEDtimeout) {
    Serial.print("Enter Low Power");

    // Setup interrupts just before going to low power mode
    setupLowPowerInterrupts();

    turnOffAllLEDs();
    lowPowerMode = true;  // Enter low-power mode
    
    // Enter low-power mode (Power-down)
    LowPower.powerDown(SLEEP_FOREVER, ADC_OFF, BOD_OFF);
    // The microcontroller will remain here until it is woken up by the interrupt
  }
}



void applyDimming() {
  // Apply dimming to the main LEDs
  for(int i = 0; i < sizeof(allLEDs) / sizeof(LEDSet); i++) {
    if (allLEDs[i].name != "null") {
      allLEDs[i].dimLevel = dimLowPower; // Set to low power dim level
      analogWrite(allLEDs[i].dimPin, dimLowPower);
    }
  }
  
  // Apply dimming to the Simon LEDs only if they are currently on
  for (int i = 0; i < sizeof(simonLEDs) / sizeof(SimonLED); i++) {
    if (simonLEDs[i].dimLevel > 0) { // Only adjust if the LED is currently on
      simonLEDs[i].dimLevel = dimLowPower;
      analogWrite(simonLEDs[i].dimPin, dimLowPower);
    } else {
      analogWrite(simonLEDs[i].dimPin, 0); // Ensure the LED stays off
    }
  }
}


void turnOffAllLEDs() {
  for(int i = 0; i < sizeof(allLEDs) / sizeof(LEDSet); i++) {
    if (allLEDs[i].name != "null") {
      allLEDs[i].dimLevel = 0; // Turn off
      analogWrite(allLEDs[i].dimPin, 0);
    }
  }
  for (int i = 0; i < sizeof(simonLEDs) / sizeof(SimonLED); i++){
    simonLEDs[i].dimLevel = 0;
    analogWrite(simonLEDs[i].dimPin, 0);
  }
}

//----------------------------ISR functions (Inturrupt Service Routines)----------------
// ISR for PCINT1_vect (handles A10 to A15)
ISR(PCINT1_vect) {
  // This will be called when any of the pins A10 to A15 change state
  wakeUpISR();
}

// ISR for PCINT2_vect (handles digital pins 22 to 29)
ISR(PCINT2_vect) {
  // This will be called when any of the pins 22 to 29 change state
  wakeUpISR();
}

// Common wake-up ISR function
void wakeUpISR() {
  // Disable interrupts
  PCICR &= ~((1 << PCIE1) | (1 << PCIE0));  // Disable Pin Change Interrupts

  // Reconfigure pins back to normal operation
  reconfigurePinsForNormalOperation();

  // Reset the lowPowerMode flag
  lowPowerMode = false;

  // Reset the lastActivityTime
  lastActivityTime = millis();
}

void reconfigurePinsForNormalOperation() {
  // Reconfigure all switch pins as inputs with pullup resistors
  for(int i = 0; i < sizeof(allSwitches)/sizeof(Switch); i++) {
    pinMode(allSwitches[i].pin1, INPUT_PULLUP);
    if (allSwitches[i].pin3 != 0)
      pinMode(allSwitches[i].pin3, INPUT_PULLUP);
  }

  // Reconfigure other peripherals as needed
}


void setupLowPowerInterrupts() {
  //-----------set up inturrupts for low power mode ------------------------------
  // Enable Pin Change Interrupt on PCIE1 (A10 to A15) and PCIE0 (Pins 50-53)
  PCICR |= (1 << PCIE1) | (1 << PCIE0);

  // Enable interrupts on specific pins (PCINT1 group: A10, A11, A12, A13, A14, A15)
  PCMSK1 |= (1 << PCINT10) | (1 << PCINT11) | (1 << PCINT12) | (1 << PCINT13) | (1 << PCINT14) | (1 << PCINT15);

  // Enable interrupts on specific pins (PCINT0 group: Pins 50-53)
  PCMSK0 |= (1 << PCINT0) | (1 << PCINT1) | (1 << PCINT2) | (1 << PCINT3);
  //-----------end inturrupt setup section --------------------------------------
}
//------------------------------End Functions Section-----------------------------

void setup() {



  // Initialize the serial communication at baud rate of 9600
  Serial.begin(9600);

  // Initialize switch pins as inputs with pullup resistors
  for(int i = 0; i < sizeof(allSwitches)/sizeof(Switch); i++) {
    pinMode(allSwitches[i].pin1, INPUT_PULLUP);
    if (allSwitches[i].pin3 != 0)
      pinMode(allSwitches[i].pin3, INPUT_PULLUP);
  }

  // Initialize LED pins as outputs
  for(int i = 0; i < sizeof(allLEDs)/sizeof(LEDSet); i++) {
    pinMode(allLEDs[i].dimPin, OUTPUT);
    pinMode(allLEDs[i].pin1, OUTPUT);
    if (allLEDs[i].pin2 != 0)
      pinMode(allLEDs[i].pin2, OUTPUT);
    if (allLEDs[i].pin3 != 0)
      pinMode(allLEDs[i].pin3, OUTPUT);
  }

  // Initialize potentiometer pins as inputs
  for(int i = 0; i < sizeof(allPots) / sizeof(Potentiometer); i++) {
    pinMode(allPots[i].pin, INPUT);
  }

  // Initialize button pins as inputs with pullup resistors
  for (int i = 0; i < sizeof(allButtons) / sizeof(Button); i++) {
    pinMode(allButtons[i].pin, INPUT_PULLUP);
  }

  // Initialize SimonLED pins as outputs
  for (int i = 0; i < sizeof(simonLEDs) / sizeof(SimonLED); i++) {
    pinMode(simonLEDs[i].dimPin, OUTPUT);
  }
}

void loop() {
  checkSwitchStates();
  matchLEDStates();
  checkPotentiometers();
  setLEDDimming();
  updateLEDs();
  checkButtons();
  controlSimonLEDs();
  checkForInactivity(); //checks for inactivity to apply dimming or turning off LEDs
  //delay(500);
  // Other code...
}