// #include <EEPROM.h>

#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <SoftwareSerial.h>

#include <EEPROM.h>

// EEPROM settings
const int EEPROM_SIZE = 1024;                         // Total EEPROM size in bytes
const int DATA_SIZE = sizeof(int);                    // Size of the data type (int) to be stored
const int SIGNATURE_SIZE = sizeof(long);              // Size of the signature
const int SLOT_SIZE = DATA_SIZE + SIGNATURE_SIZE;     // Total slot size (value + signature)
const int NUM_SLOTS = (EEPROM_SIZE / SLOT_SIZE) - 1;  // Number of slots for wear leveling

int totalCycleCounter = 0;          // The cycle counter variable
int startAddress = 0;               // Tracks the current EEPROM slot index
const long SIGNATURE = 0xDEADBEEF;  // A unique signature to identify valid slots

LiquidCrystal_I2C lcd(0x27, 20, 4);  // Create an Instance for LCD
// Bluetooth Serial Communication
SoftwareSerial BTSerial(10, 11);  // RX, TX

const byte dumpCycles = 10;  // Define number of allowed dump cycles

// Pin definitions
const byte sensorpin = 6;
const byte up_relay1pin = 8;     // Up Solenoid Relay 1
const byte down_relay2pin = 12;  // Down Solenoid Relay 2
const byte dump_relay3pin = 17;  // Dump Solenoid Relay 3

const byte buzzerpin = 9;

const byte button1 = 2;  // Mode selection Button
const byte button2 = 5;  // Raises the cylinder (activates the Up relay on Pin 8).
const byte button3 = 4;  // Lowers the cylinder (activates the Down relay on Pin 12).
const byte button4 = 3;  // Activates the Dump relay (Pin 17) and Down relay (Pin 12) for 1 second, then both go low.

const byte eepromAddress = 0;  // EEPROM address to start writing from, you can change this depending on your needs

bool prev4State = LOW;  // Variable to hold button 4 previous state

int cycleCounter = 0;  // Variable to hold cycle counter
const byte maxCycles = 20;
int selectedMenu = 1;

unsigned long debounceDelay = 250;
unsigned long debounce1Delay = 450;  // Debounce delay for button 1

unsigned long lastBtUpdateTime = millis();  // Hold last time update was send to bluetooth
unsigned long btUpdateTime = 5000;          // Update time to send current status to bluetooth if not updated previously

// Variables to hold last button press time for dobounce delay
volatile unsigned long lastDebounceTime1 = 0;
unsigned long lastDebounceTime4 = 0;

enum MenuState {
  MENU_FAST_AUTO,
  MENU_HALF_DROP,
  MENU_MANUAL,
  MENU_UNDEFINED
};
enum SubState {
  SUB_STATE_1,
  SUB_STATE_2,
  NO_STATE
};

SubState currentSubState = SUB_STATE_1;

SubState lastSentSubState = NO_STATE;  // To track the last sent substate

MenuState currentMenuState = MENU_MANUAL;


// Variables for button states and initially set them LOW
volatile bool button1State = LOW;  // Variable to track button 1 state
bool button2State, button3State, button4State = LOW;
byte menuNumber = 1;  // This menu number will be send to Application


bool btCommand2, btCommand3, btCommand4 = LOW;  // Variable to hold bluetooth commands

void setup() {
  Serial.begin(9600);
  BTSerial.begin(9600);  // Initialize Bluetooth Serial

  // Load the last saved value from EEPROM using wear leveling
  if (!loadValueFromEEPROM()) {
    // If no valid value was found, initialize the counter to 0
    totalCycleCounter = 0;
    startAddress = 1;  // Start at slot 1 since slot 0 is reserved
  }


  lcd.init();
  lcd.setCursor(0, 0);
  lcd.backlight();
  lcd.display();
  // Set pin Modes of Button as iNPUT with PULL-UP
  pinMode(button1, INPUT_PULLUP);  // Set button pin as input with Pull-UP

  pinMode(button2, INPUT_PULLUP);  // Set button pin as input with Pull-UP
  pinMode(button3, INPUT_PULLUP);  // Set button pin as input with Pull-UP
  pinMode(button4, INPUT_PULLUP);  // Set button pin as input with Pull-UP

  pinMode(sensorpin, INPUT_PULLUP);

  // Set Pin modes of Solenoid as output
  pinMode(down_relay2pin, OUTPUT);
  pinMode(dump_relay3pin, OUTPUT);
  pinMode(up_relay1pin, OUTPUT);
  pinMode(buzzerpin, OUTPUT);
  resetCounters();

  displayMessage(0, 3, "Total:", HIGH);       // Print this message on dispaly and clear it as well
  displayMessage(8, 0, "Manual      ", LOW);  // Display current menu on screen adn dont' clear
  sendDataToPhone();                          // send Data to phone if connected
}
void resetCounters() {
  cycleCounter = 0;
  digitalWrite(down_relay2pin, LOW);
  digitalWrite(dump_relay3pin, LOW);
  digitalWrite(up_relay1pin, LOW);
}

void saveTotalCycleCounter() {
  // Store the value with wear leveling if it has changed
  storeValueIfChanged(totalCycleCounter);
}


void loop() {
  unsigned long currentTime = millis();

  // Read physical button states and bluetooth Commands
  checkBluetoothCommands();  // Check for bluetooth
  readButtonStates();

  if (button1State == HIGH) {  // Check for button 1 press change menu state
    button1State = LOW;        // Reset button 1 state flag
    if (currentMenuState == MENU_FAST_AUTO) {
      Serial.println("Change Menu to Manual");
      currentMenuState = MENU_MANUAL;
      displayMessage(8, 0, "Manual      ", LOW);  // Display current menu on screen adn dont' clear (Add white space after name if we get raw data previously)
      menuNumber = 1;                             // Set menu number to 2 to be send to application
      sendDataToPhone();
    } else {
      Serial.println("Change Menu to Auto");
      currentMenuState = MENU_FAST_AUTO;
      menuNumber = 2;
    }
    lastSentSubState = SUB_STATE_2;  // change last sub state
  }

  // Menu selection logic


  if (currentMenuState == MENU_FAST_AUTO && currentSubState == SUB_STATE_2 && button4State == HIGH && cycleCounter < dumpCycles) {
    // if (cycleCounter >= dumpCycles) {  // Check if cycle counter is greater then 10 reset it and change back to substate 1

    //   cycleCounter = 0;  // Reset the counter
    //   // currentSubState = SUB_STATE_1;  // Switch to Sub Menu 1
    //   // Optionally, reset other relevant states or counters as needed
    //   // This function resets cycleCounter and totalCycleCounter, adjust as necessary
    // }
    // ... [Add Fast Auto state functionality]            // Raise the cylinder if sensorpin is LOW and dump_relay3pin (dump) is LOW
    if (digitalRead(sensorpin) == HIGH) {  // if the sensor pin is not triggered, the Up relay (Pin 8) is activated
      digitalWrite(up_relay1pin, HIGH);    // Activate solenoid1 to raise
    } else {
      digitalWrite(up_relay1pin, LOW);  // Deactivate solenoid1
    }

    // If the sensor pin is triggered, the Dump relay (Pin 17) and Down relay (Pin 12) are activated for 1 second, and the system returns to Substate 1.
    if (digitalRead(sensorpin) == LOW) {
      digitalWrite(dump_relay3pin, HIGH);  // Activate solenoid2 dumping cylinder
      digitalWrite(down_relay2pin, HIGH);
      cycleCounter++;
      totalCycleCounter++;
      saveTotalCycleCounter();
      sendDataToPhone();  // Send updated data to phone
      customDelay(1000);
      sendDataToPhone();  // Send updated data to phone
      digitalWrite(dump_relay3pin, LOW);
      digitalWrite(down_relay2pin, LOW);  // Deactivate solenoid2
      while (digitalRead(sensorpin) == LOW) { // wait until sensor is triggered and get back to normal state
        ;
      }
    }
  }
  // Menu functionality
  switch (currentMenuState) {
    case MENU_MANUAL:
      currentSubState = SUB_STATE_1;  // Change substate back to substate 1

      if (button2State == HIGH && digitalRead(sensorpin) == HIGH && digitalRead(dump_relay3pin) == LOW) {  // Button press raises cylinder
        digitalWrite(up_relay1pin, HIGH);
      } else if (button2State == LOW) {  // Button released Stop raising cylinder
        digitalWrite(up_relay1pin, LOW);
      }
      if (button3State == HIGH) {  // Lower cylinder with button press
        digitalWrite(down_relay2pin, HIGH);
      } else {
        digitalWrite(down_relay2pin, LOW);
      }
      if (button4State == HIGH) {            // button press dumps cylinder Activates the Dump relay (Pin 17) and Down relay (Pin 12) for 1 second
        digitalWrite(dump_relay3pin, HIGH);  // Activate Dump Relay
        digitalWrite(down_relay2pin, HIGH);  // Activate Down Relay
        // Mark and count cylinder dump
        cycleCounter++;  // Increase Cycle Count
        totalCycleCounter++;
        saveTotalCycleCounter();  // Save the counter to eeprom
        sendDataToPhone();        // Send updated data to phone
        customDelay(1000);
        sendDataToPhone();                  // Send updated data to phone
        button4State = LOW;                 // reset button 4 state
        digitalWrite(dump_relay3pin, LOW);  // Turn off  Dump relay
        digitalWrite(down_relay2pin, LOW);  // Turn off Down Relay
      }


      break;
    case MENU_FAST_AUTO:           // Switch-case logic for MENU_FAST_AUTO
      if (button4State == HIGH) {  // Toggle the substate on button 4 press
        if (currentSubState == SUB_STATE_1) {
          cycleCounter = 0;  // Reset the counter
          currentSubState = SUB_STATE_2;
          prev4State = HIGH;  // set button 4 previous state as high on substate 2
          menuNumber = 3;     // Set menu number to 4 to be send to application
          sendDataToPhone();
        } else if (prev4State == LOW) {  // Change state only when button 4 is released
          currentSubState = SUB_STATE_1;
          button4State = LOW;  // Reset button 4 state
        }
      }

      switch (currentSubState) {  // Check current sub state and take action
        case SUB_STATE_1:
          if (lastSentSubState != SUB_STATE_1) {        // If last substate is not substate 1 update it and turn All relay LOW
            displayMessage(8, 0, "AUTO STANDBY", LOW);  // Display current menu on screen adn dont' clear
            lastSentSubState = SUB_STATE_1;             // Set last substate as substate 1
            // Execute AUTO STANDBY specif
            digitalWrite(dump_relay3pin, LOW);  // Activate solenoid2 to dump
            digitalWrite(down_relay2pin, LOW);
            digitalWrite(up_relay1pin, LOW);
            menuNumber = 2;  // Set menu number to 3 to be send to application
            sendDataToPhone();
            // ... [Your specific logic for this sub-state]
          }
          break;

        case SUB_STATE_2:                             // Cases for substate 2
          lastSentSubState = SUB_STATE_2;             // change last sub state
          displayMessage(8, 0, "AUTO CYCLING", LOW);  // Display current menu on screen adn dont' clear

          break;  // Clear the LCD display at set intervals
      }
  }
  if ((millis() - lastBtUpdateTime) > btUpdateTime) {  // If enough time has passed send an auto update message to device
    Serial.println("Send Regular Update to Phone");
    sendDataToPhone();  // Send an update to phone
  }
}

void sendDataToPhone() {
  lastBtUpdateTime = millis();  // note current time
  BTSerial.print(menuNumber);   // Get current menu number and update it on display
  BTSerial.print(";");
  BTSerial.print(totalCycleCounter);
  BTSerial.print(";");
  BTSerial.print(cycleCounter);
  BTSerial.println(";");  // Update the last sent menu state to MENU_IDLE
  // Update cycle and total cycle counters on LCD

  displayMessage(0, 2, cycleCounter);
  displayMessage(6, 3, totalCycleCounter);
}
void customDelay(unsigned long waitTime) {
  unsigned long cTime = millis();
  while ((millis() - cTime) < waitTime) {
    checkBluetoothCommands();  // Check for bluetooth commands
    readButtonStates();        // Read button states
  }
}
void readButtonStates() {
  if (digitalRead(button1) == LOW && ((millis() - lastDebounceTime1) > debounce1Delay)) {  // Check if button 1 pressed
    button1State = HIGH;                                                                   // Set button 1 state as HIGH
    lastDebounceTime1 = millis();                                                          // Note current time when button is pressed
  }
  if (btCommand2 == LOW)                    // Check if bluetooth command was not received previously
    if (digitalRead(button2) == LOW)        // Check if button 2 pressed
      button2State = HIGH;                  // Set button 2 state as HIGH
    else if (digitalRead(button2) == HIGH)  // Check if button 2 Released
      button2State = LOW;                   // Reset button 2 state back to low

  if (btCommand3 == LOW)                    // Check if bluetooth command was not received previously
    if (digitalRead(button3) == LOW)        // Check if button 3 pressed
      button3State = HIGH;                  // Set button 3 state as HIGH
    else if (digitalRead(button3) == HIGH)  // Check if button 3 Released
      button3State = LOW;                   // Reset Button 3 state back to low

  if (btCommand4 == LOW)                                                                      // Check if bluetooth command was not received previously
    if ((digitalRead(button4) == LOW) && ((millis() - lastDebounceTime4) > debounceDelay)) {  // Check if button 1 pressed
      button4State = HIGH;                                                                    // Set button 4 state as HIGH
      lastDebounceTime4 = millis();                                                           // Note current time when button is pressed
    } else if (digitalRead(button4) == HIGH) {                                                // Check if button 4 is released and we are in substate 2
      if (currentSubState == SUB_STATE_2) {
        button4State = LOW;  // Reset button 4 state
        cycleCounter = 0;
        prev4State = LOW;
        currentSubState = SUB_STATE_1;  // Get back to substate 1
      }
    }
}
void checkBluetoothCommands() {
  while (BTSerial.available()) {
    char btChar = BTSerial.read();
    Serial.print("Received Bluetooth command:");
    Serial.println(btChar);
    // Process single-character commands for Bluetooth control
    // Process button state commands
    switch (btChar) {
      case 'A': button1State = HIGH; break;
      case 'C':
        button2State = HIGH;
        btCommand2 = HIGH;
        break;
      case 'D':
        button2State = LOW;
        btCommand2 = LOW;
        break;
      case 'E':
        button3State = HIGH;
        btCommand3 = HIGH;
        break;
      case 'F':
        button3State = LOW;
        btCommand3 = LOW;
        break;
      case 'G':
        button4State = HIGH;
        btCommand4 = HIGH;
        break;
      case 'H':
        button4State = LOW;
        btCommand4 = LOW;
        break;
    }
  }
}
// Function to load the last valid value from EEPROM using wear leveling
bool loadValueFromEEPROM() {
  for (int i = NUM_SLOTS; i >= 1; i--) {
    long signature;
    EEPROM.get(i * SLOT_SIZE, signature);
    if (signature == SIGNATURE) {
      // If the signature matches, read the counter value
      EEPROM.get(i * SLOT_SIZE + SIGNATURE_SIZE, totalCycleCounter);
      startAddress = i;
      return true;
    }
  }
  // No valid value was found
  return false;
}

// Function to store the value in EEPROM using wear leveling
void storeValueIfChanged(int value) {
  // Read the value at the current slot
  int storedValue;
  long signature;
  EEPROM.get(startAddress * SLOT_SIZE, signature);
  EEPROM.get(startAddress * SLOT_SIZE + SIGNATURE_SIZE, storedValue);

  // If the value has changed or the signature is invalid, write the new value
  if (signature != SIGNATURE || storedValue != value) {
    // Store the signature and the new value at the current slot
    EEPROM.put(startAddress * SLOT_SIZE, SIGNATURE);
    EEPROM.put(startAddress * SLOT_SIZE + SIGNATURE_SIZE, value);

    // Update startAddress to point to the next slot
    startAddress++;
    if (startAddress > NUM_SLOTS) {
      startAddress = 1;  // Wrap around to the first slot (avoiding address 0)
    }

    Serial.println("Value updated in EEPROM with wear leveling.");
  }
}
void displayMessage(int x, int y, const String& msg, bool clearScreen) {
  // This function will receive cursor x, y position and message to display and a flag to clear previous display or not
  if (clearScreen) {  // If clear screen flag is high clear lcd
    lcd.clear();
    delay(500);  // Wait for 500 seconds
  }
  lcd.setCursor(x, y);  // Set cursor at x, y position
  lcd.print(msg);       // Print msg on lcd
}
void displayMessage(int x, int y, int value) {  // This function will receive cursor x, y position and integer value to display
  lcd.setCursor(x, y);                          // Set cursor at x, y position
  lcd.print(value);                             // Print msg on lcd
  lcd.print("  ");                              // Remove any extra value
}