#include <Arduino.h>
#include <LiquidCrystal.h>
#include <EEPROM.h>

// Pin Definitions
#define MENU_BUTTON A2
#define SET_BUTTON A3
#define GAUCHE_BUTTON A4
#define DROITE_BUTTON A5
#define BUZZER_PIN 8
#define ALARM_LED_PIN 13
#define TEMPERATURE_SENSOR_PIN A0
#define ALARM_SETTINGS_ADDRESS 0
#define ALARM_SET_FLAG_ADDRESS 2
#define LCD_D2 2
#define LCD_D3 3
#define LCD_D4 4
#define LCD_D5 5
#define LCD_D11 11
#define LCD_D12 12

// LCD Configuration
LiquidCrystal lcd(LCD_D12, LCD_D11, LCD_D5, LCD_D4, LCD_D3, LCD_D2);

// Global Variables
bool alarmEnabled = true;
bool editMode = false;
bool isAlarmSet = false;
bool menuButtonPressed = false;
bool setButtonPressed = false;
bool gaucheButtonPressed = false;
bool droiteButtonPressed = false;
unsigned int hour = 0;
unsigned int minute = 0;
unsigned int second = 0;
byte setHour = 12;
byte setMinute = 0;
byte setSecond = 0;
byte alarmHour = 0;
byte alarmMinute = 0;
float temperature = 0.0;
unsigned int ledTimer;

enum EditModeState{
  EDIT_HOUR,
  EDIT_MINUTE,
  EDIT_SECOND
};

EditModeState editModeState = EDIT_HOUR;

enum MenuState {
  MENU_OFF,
  MENU_TIME,
  MENU_ALARM_TIME,
  MENU_ALARM_STATUS
};

MenuState menuState = MENU_OFF;

// Alarm State
enum AlarmState {
  ALARM_OFF,
  ALARM_ON,
  ALARM_SNOOZE,
};

AlarmState alarmState = ALARM_OFF;
unsigned long alarmStartTime = 0;
const unsigned long snoozeDuration = 120000;     // 2 minutes snooze duration (in milliseconds)


void updateTemperature() {
  // Read temperature from the sensor and return the value
  int valeur = analogRead(TEMPERATURE_SENSOR_PIN);
  
  //Formule pour transformer en degré -> 10000 pour resistance 10k, 3977 pour beta
  double voltage = valeur * 5.0 / 1023.0;
  double resistance = 10000.0 * voltage / (5.0 - voltage);
  double tempKelvin = 1.0 / (1.0 / 298.15 + log(resistance / 10000.0) / 3977); 

  double tempCelcius = tempKelvin - 273.15;

  temperature =  tempCelcius;
}

void updateTime() {
  // Update the time variables based on the elapsed time
  second = (millis() / 1000) + setSecond;
  minute = (second / 60) + setMinute;
  hour = (second / 3600) + setHour;
  minute %= 60;
  second %= 60;
  hour %= 24;
}


void displayTime() {
  // Display current time on the LCD
  lcd.setCursor(0, 0); // Set cursor to top-left corner
  char str[16];
  snprintf(str, sizeof(str), "%02d:%02d:%02d", hour, minute, second);
  lcd.print(str);
}

void displayTemperature() {
  // Display temperature on the LCD
  lcd.setCursor(9, 0); // Set cursor to top-right corner
  lcd.print(String(temperature, 2));
}

void displayAlarmStatus() {
  // Display alarm status (ON/OFF) and time on the bottom row of the LCD
  lcd.setCursor(0, 1); // Set cursor to the start of the second row

  char str[16];
  snprintf(str, sizeof(str), "%02d:%02d", alarmHour, alarmMinute);

  // Display alarm status and time
  if (alarmEnabled)
    { lcd.print(str); lcd.print(" (ON)"); }
  else 
    { lcd.print(str); lcd.print(" (OFF)"); }
}

void displayMenuTop(){
  lcd.setCursor(0, 0);
  lcd.print("Menu: ");
  lcd.setCursor(6, 0);
  switch (menuState) {
    case MENU_TIME:
      lcd.print("Heure");
      break;
    case MENU_ALARM_TIME:
      lcd.print("Alarme");
      break;
    case MENU_ALARM_STATUS:
      lcd.print("Alarme");
      break;
  }
}

void stopAlarm() {
  alarmState = ALARM_OFF;
  noTone(BUZZER_PIN);
  digitalWrite(ALARM_LED_PIN, LOW);
}

void snoozeAlarm() {
  alarmState = ALARM_SNOOZE;
  noTone(BUZZER_PIN);
  alarmStartTime = millis();
}

void saveAlarmSettings() {
  // Save alarm settings and the alarm set flag to EEPROM
  EEPROM.write(ALARM_SETTINGS_ADDRESS, alarmHour);
  EEPROM.write(ALARM_SETTINGS_ADDRESS + 1, alarmMinute);
  EEPROM.write(ALARM_SET_FLAG_ADDRESS, isAlarmSet);
}

void displayMenuInfo(){
  lcd.setCursor(0, 1);
  char str[16];
  switch (menuState) {
    case MENU_TIME:
      snprintf(str, sizeof(str), "%02d:%02d:%02d", hour, minute, second);
      lcd.print(str);
      break;
    case MENU_ALARM_TIME:
      snprintf(str, sizeof(str), "%02d:%02d", alarmHour, alarmMinute);
      lcd.print(str);
      break;
    case MENU_ALARM_STATUS:
      if(alarmEnabled)
        lcd.print("ON");
      else
        lcd.print("OFF");
      break;
  }
}

void handleMenuButton() {
  if(digitalRead(MENU_BUTTON))
    menuButtonPressed = false;
    
  if(!menuButtonPressed){
    if(!digitalRead(MENU_BUTTON)){
      if(alarmState == ALARM_ON){
        snoozeAlarm();
        return;
      }

      switch (menuState) {
        case MENU_OFF:
          lcd.clear();
          menuState = MENU_TIME;
          editModeState = EDIT_HOUR;
          editMode = false;
          break;
        case MENU_TIME:
          lcd.clear();
          menuState = MENU_ALARM_TIME;
          editModeState = EDIT_HOUR;
          saveAlarmSettings();
          editMode = false;
          break;
        case MENU_ALARM_TIME:
          lcd.clear();
          menuState = MENU_ALARM_STATUS;
          editModeState = EDIT_HOUR;
          editMode = false;
          break;
        case MENU_ALARM_STATUS:
          lcd.clear();
          menuState = MENU_OFF;
          editModeState = EDIT_HOUR;
          editMode = false;
          break;
      }
      
      menuButtonPressed = true;
    }
  }
}
void handleSetButton() {
  if(digitalRead(SET_BUTTON))
    setButtonPressed = false;
    
  if(!setButtonPressed){
    if(!digitalRead(SET_BUTTON)){
      switch (menuState) {
        case MENU_OFF:
          break;
        case MENU_TIME:
          if(!editMode){
            editMode = true;
          }
          else{
              switch (editModeState){
              case EDIT_HOUR:
                editModeState = EDIT_MINUTE;
                break;
              case EDIT_MINUTE:
                editModeState = EDIT_SECOND;
                break;
              case EDIT_SECOND:
                editMode = false;
                editModeState = EDIT_HOUR;
                break;
              }
          }
          break;
        case MENU_ALARM_TIME:
          if(!editMode){
            editMode = true;
          }
          else{
            switch (editModeState){
              case EDIT_HOUR:
                editModeState = EDIT_MINUTE;
                break;
              case EDIT_MINUTE:
                editModeState = EDIT_HOUR;
                saveAlarmSettings();
                editMode = false;
                break;
            }
          }
          break;
        case MENU_ALARM_STATUS:
          if(!editMode){
            editMode = true;
          }
          else{
            editMode = false;
          }
          break;
      }

      setButtonPressed = true;
    }
  }
}
void handleGaucheButton() {
  if(digitalRead(GAUCHE_BUTTON))
    gaucheButtonPressed = false;
    
  if(!gaucheButtonPressed){
    if(!digitalRead(GAUCHE_BUTTON)){
      if(alarmState == ALARM_ON){
        if(!digitalRead(DROITE_BUTTON)){
          stopAlarm();
        }
      }

      switch (menuState) {
        case MENU_OFF:
          break;
        case MENU_TIME:
          if(editMode){
            switch (editModeState){
              case EDIT_HOUR:
                setHour++;
                break;
              case EDIT_MINUTE:
                setMinute++;
                break;
              case EDIT_SECOND:
                setSecond++;
                break;
            }
          }
          break;
        case MENU_ALARM_TIME:
          if(editMode){
            switch (editModeState){
              case EDIT_HOUR:
                alarmHour++;
                break;
              case EDIT_MINUTE:
                alarmMinute++;
                break;
            }
          }
          break;
        case MENU_ALARM_STATUS:
          if(editMode){
            lcd.clear();
            if(alarmEnabled)
              alarmEnabled = false;
            else
              alarmEnabled = true;
          }
          break;
      }

      gaucheButtonPressed = true;
    }
  }
}
void handleDroiteButton() {
  if(digitalRead(DROITE_BUTTON))
    droiteButtonPressed = false;
    
  if(!droiteButtonPressed){
    if(!digitalRead(DROITE_BUTTON)){

      switch (menuState) {
        case MENU_OFF:
          break;
        case MENU_TIME:
          if(editMode){
            switch (editModeState){
              case EDIT_HOUR:
                setHour--;
                break;
              case EDIT_MINUTE:
                setMinute--;
                break;
              case EDIT_SECOND:
                setSecond--;
                break;
            }
          }
          break;
        case MENU_ALARM_TIME:
          if(editMode){
            switch (editModeState){
              case EDIT_HOUR:
                alarmHour--;
                break;
              case EDIT_MINUTE:
                alarmMinute--;
                break;
            }
          }
          break;
        case MENU_ALARM_STATUS:
          break;
      }

      droiteButtonPressed = true;
    }
  }
}

void checkAlarm() {
  if (alarmEnabled && (hour == alarmHour) && (minute == alarmMinute) && alarmState == ALARM_OFF) {
    // Set alarm state to sound
    alarmState = ALARM_ON;
    alarmStartTime = millis();
    ledTimer = 0;
  }
}

void restoreAlarmSettings() {
  // Read alarm settings from EEPROM and set variables accordingly
  // If no settings are found, use default values
  byte storedHour = EEPROM.read(ALARM_SETTINGS_ADDRESS);
  byte storedMinute = EEPROM.read(ALARM_SETTINGS_ADDRESS + 1);
  isAlarmSet = EEPROM.read(ALARM_SET_FLAG_ADDRESS);

  Serial.println(storedHour + ", " + storedMinute);

  // Check if EEPROM contains valid alarm settings
  if (isAlarmSet && storedHour != 255 && storedMinute != 255) {
    alarmHour = storedHour;
    alarmMinute = storedMinute;
  } else {
    // Default alarm time (12:01) if EEPROM is empty or contains invalid data
    alarmHour = 12;
    alarmMinute = 1;
    isAlarmSet = false;
  }
}

void setup() {
  //For debugging
  Serial.begin(115200);
  
  // Initialize LCD
  lcd.begin(16, 2);

  // Restore alarm settings from EEPROM
  restoreAlarmSettings();

  // Initialize buttons as inputs
  pinMode(MENU_BUTTON, INPUT_PULLUP);
  pinMode(SET_BUTTON, INPUT_PULLUP);
  pinMode(GAUCHE_BUTTON, INPUT_PULLUP);
  pinMode(DROITE_BUTTON, INPUT_PULLUP);
}

void loop() {
  // Update time
    updateTime();

  if(menuState == MENU_OFF){
    // Update temperature
    updateTemperature();

    // Display information on LCD
    displayTime();
    displayTemperature();
    displayAlarmStatus();

    // Check and activate the alarm
    checkAlarm();

    // Handle the alarm state
    switch (alarmState) {
      case ALARM_OFF:
        stopAlarm();
        break;
      case ALARM_ON:
        if (ledTimer <= 250){
          digitalWrite(ALARM_LED_PIN, HIGH);
          ledTimer += millis();
        }
        else if (ledTimer <= 750){
          digitalWrite(ALARM_LED_PIN, LOW);
          ledTimer += millis();
        }
        else{
          ledTimer = 0;
        }
        
        tone(BUZZER_PIN, 300);
        break;
      case ALARM_SNOOZE:
        // Check if snooze duration has passed
        if (millis() - alarmStartTime >= snoozeDuration) {
          alarmState = ALARM_ON;
          alarmStartTime = millis();
          ledTimer = 0;
        }
        break;
    }
  }
  else{
    displayMenuTop();
    displayMenuInfo();
  }

  // Handle button presses
  handleMenuButton();
  handleSetButton();
  handleGaucheButton();
  handleDroiteButton();
}