class Timer {
  private:
    unsigned long nextChangeTime;
    unsigned long timeOn_;
    boolean overFlow;
  public:
    void TimerSet (unsigned long timeOn) {
      timeOn_ = timeOn;
      unsigned long currentTime = millis();
      nextChangeTime = currentTime + timeOn_;
      if (nextChangeTime > currentTime) {
        overFlow = false;
      } else {
        overFlow = true;
      }
   }
    boolean TimerActive() {
      unsigned long currentTime = millis();
      boolean val = false;
      if (! overFlow) {
        if (currentTime < nextChangeTime) {
          val = true;
        }
      } else if ((currentTime + timeOn_)<(nextChangeTime + timeOn_)){
        val = true;
      }
      return val;
    }
};

Timer TMR;
int minute = 10000;

#include <LiquidCrystal_I2C.h>  /*include LCD I2C Library*/
#define I2C_ADDR    0x27
#define LCD_COLUMNS 16
#define LCD_LINES   2
LiquidCrystal_I2C lcd(I2C_ADDR, LCD_COLUMNS, LCD_LINES);

int BTN_SET = 12;
byte buttonState_SET = HIGH;
int BTN_TH = 11;
byte buttonState_TH = HIGH;
int BTN_TM = 10;
byte buttonState_TM = HIGH;
int BTN_DD = 9;
byte buttonState_DD = HIGH;
int BTN_DM = 8;
byte buttonState_DM = HIGH;
int BTN_AH = 7;
byte buttonState_AH = HIGH;
int BTN_AM = 6;
byte buttonState_AM = HIGH;
int BTN_RD = 5;
byte buttonState_RD = HIGH;
int BTN_RU = 4;
byte buttonState_RU = HIGH;
int BTN_RADIO = A1;
byte buttonState_RADIO = HIGH;
int BTN_SNOOZE = A0;
byte buttonState_SNOOZE = HIGH;

unsigned long debounceDuration = 50; //millis

int LED = 13;

int SWITCH_ONOFF = 3;
byte switchState_ONOFF = LOW;
int SWITCH_MODE = 2;
byte switchState_MODE = LOW;

int POT_VOL = A3;

int timeHour = 12;
int timeMin = 0;
int dateDay = 6;
int dateMonth = 9;
int alarmHour = 12;
int alarmMin = 1;
float radioFreq = 93.5;
bool isAlarmOn = false;
bool isRadioOn = false;

int volValue;
int lastVolValue;

bool timerActive = false;

enum alarmModeType {
  off,
  buzz,
  radio
}; 
alarmModeType alarmMode = off;

byte radioChar[] = {
  B00001,
  B00011,
  B00101,
  B01001,
  B01001,
  B01011,
  B11011,
  B11000
};
byte alarmChar[] = {
  B00000,
  B00100,
  B01110,
  B01110,
  B01110,
  B11111,
  B00100,
  B00000
};
byte volumeChar[] = {
  B00001,
  B00011,
  B00111,
  B11111,
  B11111,
  B00111,
  B00011,
  B00001
};

void setup() {
  lcd.init();
  lcd.clear();
  lcd.backlight();
  lcd.createChar(0, radioChar);
  lcd.createChar(1, alarmChar);
  lcd.createChar(2, volumeChar);

  pinMode(BTN_SET, INPUT_PULLUP);
  pinMode(BTN_TH, INPUT_PULLUP);
  pinMode(BTN_TM, INPUT_PULLUP);
  pinMode(BTN_DD, INPUT_PULLUP);
  pinMode(BTN_DM, INPUT_PULLUP);
  pinMode(BTN_AH, INPUT_PULLUP);
  pinMode(BTN_AM, INPUT_PULLUP);
  pinMode(BTN_RD, INPUT_PULLUP);
  pinMode(BTN_RU, INPUT_PULLUP);
  pinMode(BTN_RADIO, INPUT_PULLUP);
  pinMode(LED, OUTPUT);
  digitalWrite(LED, HIGH);

  pinMode(SWITCH_ONOFF, INPUT_PULLUP);
  pinMode(SWITCH_MODE, INPUT_PULLUP);

  lastVolValue = map(analogRead(POT_VOL), 0, 1023, 0, 10);

  displayHandler();
}

void loop() {
  checkAlarmMode();
  checkVolButton();

  if (timerActive) {
    if(!TMR.TimerActive()){
      if (timeMin >= 59) {
        timeMin = minuteHandler(timeMin);
        timeHour = hourHandler(timeHour);
        checkIsAlarmOn();
        displayHandler();
        TMR.TimerSet(minute);
      } else {
        timeMin = minuteHandler(timeMin);
        checkIsAlarmOn();
        displayHandler();
        TMR.TimerSet(minute);
      }
    }
  } else {
    TMR.TimerSet(minute);

    timerActive = true;
  }

  if (buttonState_RADIO == HIGH) {
    checkSetButton();
  }

  if (buttonState_SET == HIGH) {
    checkRadioButton();
    checkSnoozeButton();
  }

  if (buttonState_SET == LOW){
    if (buttonState_TM == HIGH && buttonState_DD == HIGH && buttonState_DM == HIGH && buttonState_AH == HIGH && buttonState_AM == HIGH && buttonState_RD == HIGH && buttonState_RU == HIGH){
      checkTHButton();
    }
    if (buttonState_TH == HIGH && buttonState_DD == HIGH && buttonState_DM == HIGH && buttonState_AH == HIGH && buttonState_AM == HIGH && buttonState_RD == HIGH && buttonState_RU == HIGH){
      checkTMButton();
    }
    if (buttonState_TH == HIGH && buttonState_TM == HIGH && buttonState_DM == HIGH && buttonState_AH == HIGH && buttonState_AM == HIGH && buttonState_RD == HIGH && buttonState_RU == HIGH){
      checkDDButton();
    }
    if (buttonState_TH == HIGH && buttonState_TM == HIGH && buttonState_DD == HIGH && buttonState_AH == HIGH && buttonState_AM == HIGH && buttonState_RD == HIGH && buttonState_RU == HIGH){
      checkDMButton();
    }
    if (buttonState_TH == HIGH && buttonState_TM == HIGH && buttonState_DD == HIGH && buttonState_DM == HIGH && buttonState_AM == HIGH && buttonState_RD == HIGH && buttonState_RU == HIGH){
      checkAHButton();
    }
    if (buttonState_TH == HIGH && buttonState_TM == HIGH && buttonState_DD == HIGH && buttonState_DM == HIGH && buttonState_AH == HIGH && buttonState_RD == HIGH && buttonState_RD == HIGH){
      checkAMButton();
    }
    if (buttonState_TH == HIGH && buttonState_TM == HIGH && buttonState_DD == HIGH && buttonState_DM == HIGH && buttonState_AH == HIGH && buttonState_AM == HIGH && buttonState_RU == HIGH){
      checkRDButton();
    }
    if (buttonState_TH == HIGH && buttonState_TM == HIGH && buttonState_DD == HIGH && buttonState_DM == HIGH && buttonState_AH == HIGH && buttonState_AM == HIGH && buttonState_RD == HIGH){
      checkRUButton();
    }
  }
}

//SET Button
unsigned long lastTimeButtonStateChanged_SET = 0;
byte lastButtonState_SET = LOW;
void checkSetButton() {
  if (millis() - lastTimeButtonStateChanged_SET > debounceDuration) {
    buttonState_SET = digitalRead(BTN_SET);
    if (buttonState_SET != lastButtonState_SET) {
      lastTimeButtonStateChanged_SET = millis();
      lastButtonState_SET = buttonState_SET;
    }
  }
};

//TH Button
unsigned long lastTimeButtonStateChanged_TH = 0;
byte lastButtonState_TH = LOW;
void checkTHButton() {
  if (millis() - lastTimeButtonStateChanged_TH > debounceDuration) {
    buttonState_TH = digitalRead(BTN_TH);
    if (buttonState_TH != lastButtonState_TH) {
      lastTimeButtonStateChanged_TH = millis();
      lastButtonState_TH = buttonState_TH;
      if (buttonState_TH == LOW) {
        timeHour = hourHandler(timeHour);
        displayHandler();
      }
    }
  }
};

//TM Button
unsigned long lastTimeButtonStateChanged_TM = 0;
byte lastButtonState_TM = LOW;
void checkTMButton() {
  if (millis() - lastTimeButtonStateChanged_TM > debounceDuration) {
    buttonState_TM = digitalRead(BTN_TM);
    if (buttonState_TM != lastButtonState_TM) {
      lastTimeButtonStateChanged_TM = millis();
      lastButtonState_TM = buttonState_TM;
      if (buttonState_TM == LOW) {
        timeMin = minuteHandler(timeMin);
        displayHandler();
      }
    }
  }
};

// DD Button
unsigned long lastTimeButtonStateChanged_DD = 0;
byte lastButtonState_DD = LOW;
void checkDDButton() {
  if (millis() - lastTimeButtonStateChanged_DD > debounceDuration) {
    buttonState_DD = digitalRead(BTN_DD);
    if (buttonState_DD != lastButtonState_DD) {
      lastTimeButtonStateChanged_DD = millis();
      lastButtonState_DD = buttonState_DD;
      if (buttonState_DD == LOW) {
        dateDay = dayHandler(dateDay, dateMonth);
        displayHandler();
      }
    }
  }
};

// DM Button
unsigned long lastTimeButtonStateChanged_DM = 0;
byte lastButtonState_DM = LOW;
void checkDMButton() {
  if (millis() - lastTimeButtonStateChanged_DM > debounceDuration) {
    buttonState_DM = digitalRead(BTN_DM);
    if (buttonState_DM != lastButtonState_DM) {
      lastTimeButtonStateChanged_DM = millis();
      lastButtonState_DM = buttonState_DM;
      if (buttonState_DM == LOW) {
        dateMonth = monthHandler(dateMonth);
        displayHandler();
      }
    }
  }
};

//AH Button
unsigned long lastTimeButtonStateChanged_AH = 0;
byte lastButtonState_AH = LOW;
void checkAHButton() {
  if (millis() - lastTimeButtonStateChanged_AH > debounceDuration) {
    buttonState_AH = digitalRead(BTN_AH);
    if (buttonState_AH != lastButtonState_AH) {
      lastTimeButtonStateChanged_AH = millis();
      lastButtonState_AH = buttonState_AH;
      if (buttonState_AH == LOW) {
        alarmHour = hourHandler(alarmHour);
        displayHandler();
      }
    }
  }
};

//AM Button
unsigned long lastTimeButtonStateChanged_AM = 0;
byte lastButtonState_AM = LOW;
void checkAMButton() {
  if (millis() - lastTimeButtonStateChanged_AM > debounceDuration) {
    buttonState_AM = digitalRead(BTN_AM);
    if (buttonState_AM != lastButtonState_AM) {
      lastTimeButtonStateChanged_AM = millis();
      lastButtonState_AM = buttonState_AM;
      if (buttonState_AM == LOW) {
        alarmMin = minuteHandler(alarmMin);
        displayHandler();
      }
    }
  }
};

// RD Button
unsigned long lastTimeButtonStateChanged_RD = 0;
byte lastButtonState_RD = LOW;
void checkRDButton() {
  if (millis() - lastTimeButtonStateChanged_RD > debounceDuration) {
    buttonState_RD = digitalRead(BTN_RD);
    if (buttonState_RD != lastButtonState_RD) {
      lastTimeButtonStateChanged_RD = millis();
      lastButtonState_RD = buttonState_RD;
      if (buttonState_RD == LOW) {
        radioFreq = radioFreqHandler(radioFreq, false);
        displayHandler();
      }
    }
  }
};

// RU Button
unsigned long lastTimeButtonStateChanged_RU = 0;
byte lastButtonState_RU = LOW;
void checkRUButton() {
  if (millis() - lastTimeButtonStateChanged_RU > debounceDuration) {
    buttonState_RU = digitalRead(BTN_RU);
    if (buttonState_RU != lastButtonState_RU) {
      lastTimeButtonStateChanged_RU = millis();
      lastButtonState_RU = buttonState_RU;
      if (buttonState_RU == LOW) {
        radioFreq = radioFreqHandler(radioFreq, true);
        displayHandler();
      }
    }
  }
};

// RADIO Button
unsigned long lastTimeButtonStateChanged_RADIO = 0;
byte lastbuttonState_RADIO = LOW;
void checkRadioButton() {
  if (millis() - lastTimeButtonStateChanged_RADIO > debounceDuration) {
    buttonState_RADIO = digitalRead(BTN_RADIO);
    if (buttonState_RADIO != lastbuttonState_RADIO) {
      lastTimeButtonStateChanged_RADIO = millis();
      lastbuttonState_RADIO = buttonState_RADIO;
      if (buttonState_RADIO == LOW) {
        if(isRadioOn && isAlarmOn){
          isAlarmOn = false;
        }
        radioHandler();
        displayHandler();
      }
    }
  }
};

// SNOOZE Button
unsigned long lastTimeButtonStateChanged_SNOOZE = 0;
byte lastbuttonState_SNOOZE = LOW;
void checkSnoozeButton() {
  if (millis() - lastTimeButtonStateChanged_SNOOZE > debounceDuration) {
    buttonState_SNOOZE = digitalRead(BTN_SNOOZE);
    if (buttonState_SNOOZE != lastbuttonState_SNOOZE) {
      lastTimeButtonStateChanged_SNOOZE = millis();
      lastbuttonState_SNOOZE = buttonState_SNOOZE;
      if (buttonState_SNOOZE == LOW) {
        isAlarmOn = false;
        if (isRadioOn) {
          isRadioOn = false;
        }
        displayHandler();
      }
    }
  }
};

// ALARM Mode
void checkAlarmMode() {
  switchState_ONOFF = digitalRead(SWITCH_ONOFF);
  switchState_MODE = digitalRead(SWITCH_MODE);

  if (switchState_ONOFF == LOW) {
    alarmMode = off;
  } else if (switchState_ONOFF == HIGH && switchState_MODE == LOW) {
    alarmMode = buzz;
  } else if (switchState_ONOFF == HIGH && switchState_MODE == HIGH){
    alarmMode = radio;
  }

  handleAlarmModeIndicators();
};

// VOL Button
unsigned long lastTimeVolValueChanged = 0;
void checkVolButton() {
  volValue = map(analogRead(POT_VOL), 0, 1023, 0, 10);
  if (volValue != lastVolValue) {
    lastTimeVolValueChanged = millis();
    lastVolValue = volValue;
    displayHandler();
  }
};

void checkIsAlarmOn() {
  if (alarmMode != off && timeHour == alarmHour && timeMin == alarmMin) {
    isAlarmOn = true;
    if (alarmMode == radio){
      isRadioOn = true;
    }
  } else {
    isAlarmOn = false;
  }
}

void handleAlarmModeIndicators() {
  if (alarmMode != off){
    digitalWrite(LED, HIGH);
  } else {
    digitalWrite(LED, LOW);
  }
}

int hourHandler(int hour) {
  if (hour <= 22) {
      hour ++;
    } else {
      hour = 0;
  }
  return hour;
}
int minuteHandler(int minute) {
  if (minute <= 58) {
      minute ++;
    } else {
      minute = 0;
  }
  return minute;
}
int dayHandler(int day, int month) {
  int maxValue = 31;
  
  if (month == 2) {
    maxValue = 28;
  }

  if (month == 4 || month == 6 || month == 9 || month == 11) {
    maxValue = 30;
  }
  
  if (day <= (maxValue - 1)) {
      day ++;
    } else {
      day = 1;
  }
  return day;
}
int monthHandler(int month) {
  if (month <= 11) {
      month ++;
    } else {
      month = 1;
  }
  return month;
}
float radioFreqHandler(float radio, bool up) {
  float maxValue = 108.5;
  float minValue = 88.0;

  if (up) {
    if (radio < maxValue) {
      radio = radio + 0.5;
    }
  } else {
    if (radio > minValue) {
      radio = radio - 0.5;
    }
  }
  return radio;
}

void radioHandler(){
  if (isRadioOn) {
    isRadioOn = false;
  } else {
    isRadioOn = true;
  }
}

void displayHandler(){
  lcd.clear();

  lcd.setCursor(0,0);
  lcd.print(parseDisplayValue(timeHour) + ":" + parseDisplayValue(timeMin));
  
  lcd.setCursor(7,0);
  lcd.print(parseDisplayValue(dateDay) + "/" + parseDisplayValue(dateMonth));

  lcd.setCursor(0,1);
  if (isAlarmOn && !isRadioOn){
    lcd.print(parseDisplayValue(alarmHour) + ":" + parseDisplayValue(alarmMin) + char(1));
  } else {
    lcd.print(parseDisplayValue(alarmHour) + ":" + parseDisplayValue(alarmMin));
  }

  lcd.setCursor(7,1);
  if (isRadioOn){
    lcd.print(String(radioFreq, 1) + char(0));
  } else {
    lcd.print(String(radioFreq, 1));
  }

  lcd.setCursor(13,1);
  lcd.print(char(2) + String(volValue));
};

String parseDisplayValue(int value) {
  if (value < 10){
   return "0" + String(value);
  } else {
    return String(value);
  }
}