#include <RTClib.h>
#include <Wire.h>
#include <LiquidCrystal_I2C.h>
#include <EEPROM.h>

RTC_DS1307 rtc;  // Creates an RTC object
LiquidCrystal_I2C lcd(0x27, 16, 2);
const int buttonTouchPin = 3;
const int buttonOkPin = 4;  // Button to enter "OK"
const int buttonPlusPin = 5;
const int buttonMinusPin = 6;
const int buttonBackPin = 7;
const int ledPin = 9;
bool menu_state = false;
bool clock_state = true;
bool change_alarm_state = false;
bool alarm_menu_state = false;
bool alarm_state = true;
bool change_time_state = false;
bool clock_menu_state = false;
String main_options[2] = {"time", "alarm"};
int main_options_max = 1;
String alarm_options[2] = {"change", "toggle"};
int alarm_options_max = 1;
String clock_options[2] = {"display", "change"};
int clock_options_max = 1;
int hover_index = 0;
DateTime alarmTime = DateTime(2023, 1, 1, 0, 0, 0);
unsigned long previousMillis = 0;
bool displayTime = true;
int touchPresses = 0;
int maxPresses = 10;
bool alarm_light = false;
bool alarm_on = false;
const unsigned long backlightTimeout = 30000; // 30 seconds timeout
unsigned long lastInputTime = 0;
bool backlightOn = true;

void setup() {
  rtc.begin();
  lcd.init();
  lcd.backlight();
  lcd.setCursor(0, 0);
  pinMode(buttonOkPin, INPUT_PULLUP);
  pinMode(buttonPlusPin, INPUT_PULLUP);
  pinMode(buttonMinusPin, INPUT_PULLUP);
  pinMode(buttonBackPin, INPUT_PULLUP);
  pinMode(buttonTouchPin, INPUT_PULLUP);
  pinMode(ledPin, OUTPUT);
  // Attempt to read the alarm time from EEPROM
  if (EEPROM.read(0) != 255) {
    // If the EEPROM value is not 255 (uninitialized), read it
    EEPROM.get(0, alarmTime);
  }
}


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

  // Check if it's time to turn off the backlight
  if (currentMillis - lastInputTime >= backlightTimeout && backlightOn) {
    lcd.noBacklight(); // Turn off the backlight
    backlightOn = false;
  }

  if (currentMillis - lastInputTime < backlightTimeout && !backlightOn) {
    lcd.backlight(); // Turn on the backlight
    backlightOn = true;
  }

  if (clock_state) {
    clock();
    if (digitalRead(buttonOkPin) == LOW) {
      selectMainMenu();
    }
  }

  if (clock_menu_state) {
    clock_menu();
  }

  if (menu_state) {
    main_menu();
  }
  
  if (alarm_menu_state) {
    alarm_menu();
  }

  if (change_alarm_state) {
    change_alarm();
  }

  if (alarm_state) {
    alarm();
  }

  if (digitalRead(buttonOkPin) == LOW || digitalRead(buttonPlusPin) == LOW || digitalRead(buttonMinusPin) == LOW || digitalRead(buttonBackPin) == LOW) {
    lastInputTime = currentMillis; // Update the last input time
  }
  
  if (digitalRead(buttonBackPin) == LOW) {
    if (change_alarm_state) {
      selectAlarm();
    }
    if (change_time_state) {
      displayTime = true;
      change_time_state = false;
    }
  }
  if (digitalRead(buttonTouchPin) == LOW) {
    int brightness = 0;
    if (alarm_on) {
      alarm_light = false;
    }
    else {
      if (touchPresses >= maxPresses) {
        touchPresses = 0;
      }
      else {
        touchPresses += 1;
      }
      int range = 255;
      brightness = (range * touchPresses) / maxPresses;
    }
    analogWrite(ledPin, brightness);
    delay(100);
  }
  // Adds a delay to avoid multiple rapid button presses
  delay(100);
}


void displayMenu(String menu_options[], int options_max, String menu_identifier) {
  // Displays menu
  lcd.setCursor(0, 0);
  String option_1 = menu_options[hover_index];
  option_1.toUpperCase();
  lcd.print(option_1);
  lcd.setCursor(0, 1);
  if (hover_index + 1 > options_max) {
    lcd.print(menu_options[0]);
  } 
  else {
    lcd.print(menu_options[hover_index + 1]);
  }

  if (digitalRead(buttonMinusPin) == LOW) {
    hover_index += 1;
    if (hover_index > options_max) {
      hover_index = 0;
    }
    lcd.clear();
  } 
  else if (digitalRead(buttonPlusPin) == LOW) {
    hover_index -= 1;
    if (hover_index < 0) {
      hover_index = options_max;
    }
    lcd.clear();
  }

  if (digitalRead(buttonOkPin) == LOW) {
    // Perform the action associated with the selected option
    selectOption(hover_index, options_max, menu_identifier);
    lcd.clear();
    hover_index = 0;
  }

  if (digitalRead(buttonBackPin) == LOW) {
    if (menu_state) {
      selectTime();
    }
    
    if (alarm_menu_state || clock_menu_state) {
      selectMainMenu();
    }
    lcd.clear();
  }
}


// Define function pointers for menu options
void (*menu_optionActions[])(void) = {selectTime, selectAlarm};


void selectTime() {
  // Perform actions for "time" option
  menu_state = false;
  clock_menu_state = true;
  delay(100);
}


void selectAlarm() {
  // Perform actions for "alarm" option
  menu_state = false;
  change_alarm_state = false;
  alarm_menu_state = true;
  delay(100);
}


void selectMainMenu() {
  hover_index = 0;
  lcd.clear();
  menu_state = true;
  clock_state = false;
  clock_menu_state = false;
  alarm_menu_state = false;
  delay(100);
}

void (*alarm_optionActions[])(void) = {change, toggle};

void toggle() {
  // Toggle the alarm
  alarm_toggle();
}

void change() {
  // Change alarm time
  alarm_menu_state = false;
  change_alarm_state = true;
}

void (*clock_optionActions[])(void) = {display, change_time};

void display() {
  clock_menu_state = false;
  clock_state = true;
}

void change_time() {
  clock_menu_state = false;
  change_time_state = true;
  clock_state = true;
}


void selectOption(int selectedOption, int option_max, String menu_type) {
  if (selectedOption >= 0 && selectedOption <= option_max) {
    // Call the corresponding function based on the selected option
    if (menu_type == "main") {
      menu_optionActions[selectedOption]();
    }

    if (menu_type == "alarm")
      alarm_optionActions[selectedOption]();

    if (menu_type == "clock") {
      clock_optionActions[selectedOption]();
    }
  }
}


void clock_menu() {
  change_time_state = false;
  displayMenu(clock_options, clock_options_max, "clock");
}


void clock() {
  unsigned long currentMillis = millis();  // Get the current time
  int interval = 500;

  if (change_time_state){
    if (currentMillis - previousMillis >= interval) {
      previousMillis = currentMillis;  // Save the last time the display was updated
      displayTime = !displayTime;  // Toggle the display flag
    }
  }
  // Reads the current time from the RTC module
  DateTime now = rtc.now();

  if (displayTime) {
    // Checks if the hour button is pressed
    if (change_time_state) {
      if (digitalRead(buttonPlusPin) == LOW) {
        // Increment hours
        now = now + TimeSpan(0, 1, 0, 0);  // Increments by one hour
      }

      // Checks if the minute button is pressed
      if (digitalRead(buttonMinusPin) == LOW) {
        // Increment minutes
        now = now + TimeSpan(0, 0, 1, 0);  // Increments by one minute
      }
    }

    // Sets the updated time back to the RTC module
    rtc.adjust(now);

    // Display the time
    lcd.setCursor(0, 0);
    if (now.hour() < 10) {
      lcd.print('0'); // Adds leading zero for single-digit hours
    }
    lcd.print(now.hour(), DEC);
    lcd.print(':');
    if (now.minute() < 10) {
      lcd.print('0'); // Adds leading zero for single-digit minutes
    }
    lcd.print(now.minute(), DEC);
  }
  else {
    lcd.clear();
  }
}


void alarm_menu() {
  // Displays menu
  displayMenu(alarm_options, alarm_options_max, "alarm");
}


void change_alarm() {
  // Displays alarm settings

  if (digitalRead(buttonPlusPin) == LOW) {
    // Increment hours
    alarmTime = alarmTime + TimeSpan(0, 1, 0, 0);  // Increments by one hour
  }

  // Checks if the minute button is pressed
  if (digitalRead(buttonMinusPin) == LOW) {
    // Increment minutes
    alarmTime = alarmTime + TimeSpan(0, 0, 1, 0);  // Increments by one minute
  }
  
  // Update and store the alarm time in EEPROM
  EEPROM.put(0, alarmTime);

  // Displays the time
  lcd.setCursor(0, 0);
  if (alarmTime.hour() < 10) {
    lcd.print('0'); // Adds leading zero for single-digit minutes
  }
  lcd.print(alarmTime.hour(), DEC);
  lcd.print(':');
  if (alarmTime.minute() < 10) {
    lcd.print('0'); // Adds leading zero for single-digit minutes
  }
  lcd.print(alarmTime.minute(), DEC);
}


void alarm_toggle() {
  lcd.clear();
  lcd.setCursor(0, 0);
  if (alarm_state) {
    lcd.print("Alarm off");
    alarm_state = false;
  }
  else {
    lcd.print("Alarm on");
    alarm_state = true;
  }
  delay(400);
  lcd.clear();
}


void alarm() {
  // Activates alarm
  DateTime time = rtc.now();
  int alarm_hour = alarm_value_check(alarmTime.hour());
  int time_hour = alarm_value_check(time.hour());
  int time_delta = (alarm_hour * 60 + alarmTime.minute()) - (time_hour * 60 + time.minute());
  int range = 255; // 0 - 255 for LED birghtness
  int alarm_length = 30; // 30 minute diff until light should turn on
  int brightness = (range * (alarm_length - time_delta)) / alarm_length;
  if (time_delta > 0 && time_delta <= alarm_length) {
    if (alarm_light) {
      analogWrite(ledPin, brightness);
      alarm_on = true;
    }
  }
  else {
    alarm_light = true;
    alarm_on = false;
  }
}


int alarm_value_check(int to_check) {
  if (to_check == 0) {
    to_check = 24;
  }
  return to_check;
}

void main_menu() {
  // Displays menu
  displayMenu(main_options, main_options_max, "main");
}
uno:A5.2
uno:A4.2
uno:AREF
uno:GND.1
uno:13
uno:12
uno:11
uno:10
uno:9
uno:8
uno:7
uno:6
uno:5
uno:4
uno:3
uno:2
uno:1
uno:0
uno:IOREF
uno:RESET
uno:3.3V
uno:5V
uno:GND.2
uno:GND.3
uno:VIN
uno:A0
uno:A1
uno:A2
uno:A3
uno:A4
uno:A5
lcd1:GND
lcd1:VCC
lcd1:SDA
lcd1:SCL
btn1:1.l
btn1:2.l
btn1:1.r
btn1:2.r
btn2:1.l
btn2:2.l
btn2:1.r
btn2:2.r
GND5VSDASCLSQWRTCDS1307+
rtc1:GND
rtc1:5V
rtc1:SDA
rtc1:SCL
rtc1:SQW
btn3:1.l
btn3:2.l
btn3:1.r
btn3:2.r
led1:A
led1:C
btn6:1.l
btn6:2.l
btn6:1.r
btn6:2.r
btn4:1.l
btn4:2.l
btn4:1.r
btn4:2.r