//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ 
#include <TimerOne.h>
#include <ezButton.h>
#include <ArduinoTrace.h>
#include <Wire.h>
#include <Adafruit_GFX.h>
#include <Adafruit_SSD1306.h>

//----------------- OLED ----------------------------------------------
#define SCREEN_WIDTH        128 // OLED display width, in pixels
#define SCREEN_HEIGHT        64 // OLED display height, in pixels
// Declaration for an SSD1306 display connected to I2C (SDA, SCL pins)
#define OLED_RESET            -1 // Reset pin # (or -1 if sharing Arduino reset pin)
Adafruit_SSD1306 display(SCREEN_WIDTH, SCREEN_HEIGHT, &Wire, OLED_RESET);

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// Clock Variables Declaration (7:27 as default time)
#define ON 1
#define OFF 0
bool ClockPoint = true;
unsigned char Update;
unsigned char halfsecond = 0;
unsigned char second;
unsigned char hour = 7;
unsigned char minute = 27;
char Time[]     = "  :  ";

// Alarm Variables Declaration (00:00 as default)
unsigned char alarm_hour=0;
unsigned char alarm_minute=0;

// IMPORTANT CONTROL VARIABLES THAT DEFINES WHAT SHOWS ON DISPLAY
#define TIME 1 
#define ALARM 0
bool displayMode = TIME;

// led variables
int rled = 12; // Pin PWN 11 para led rojo
// buzzeer variable
int buzzer1 = 13;
// switch variable
int switch1 = 8;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// Button Declarations
ezButton alarmSetBtn(4);  // create ezButton object that attach to pin 4;
ezButton timeSetBtn(5);  // create ezButton object that attach to pin 5;
ezButton hourBtn(6);  // create ezButton object that attach to pin 6;
ezButton minuteBtn(7);  // create ezButton object that attach to pin 7;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// Variables for miliseconds calculations
unsigned long pm1;
unsigned long pm2;

//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

// 'clk', 25x25px
const unsigned char epd_bitmap_clk [] PROGMEM = {
	0x00, 0x00, 0x00, 0x1c, 0x1c, 0x00, 0x3c, 0x1e, 0x00, 0x78, 0x0f, 0x00, 0x73, 0xe7, 0x00, 0x64, 
	0x13, 0x00, 0x48, 0x89, 0x00, 0x10, 0x84, 0x00, 0x20, 0x82, 0x00, 0x20, 0x82, 0x00, 0x20, 0x82, 
	0x00, 0x20, 0x42, 0x00, 0x20, 0x22, 0x00, 0x10, 0x04, 0x00, 0x08, 0x08, 0x00, 0x04, 0x10, 0x00, 
	0x03, 0xe0, 0x00
};

void setup(){
  // Set serial monitor frecuency
  Serial.begin(9600);

  //------ initialize and clear display  ---------------------
  if (!display.begin(SSD1306_SWITCHCAPVCC, 0x3C)) {
    Serial.println(F("SSD1306 allocation failed"));
   for (;;);
  }

  // Clear the buffer.
  display.clearDisplay();

  // Timer for clock init
  Timer1.initialize(500000);//timing for 500ms
  Timer1.attachInterrupt(TimingISR);//declare the interrupt serve routine:TimingISR

  // Debounce times and pin configuration for buttons
  alarmSetBtn.setDebounceTime(50);
  timeSetBtn.setDebounceTime(50); // set debounce time to 50 milliseconds
  hourBtn.setDebounceTime(50);
  minuteBtn.setDebounceTime(50);
  //buttons ports
  pinMode(4, INPUT_PULLUP);
  pinMode(5, INPUT_PULLUP);
  pinMode(6, INPUT_PULLUP);
  pinMode(7, INPUT_PULLUP);
  pinMode(8, INPUT_PULLUP);
  //Led pins in output mode
  pinMode(12, OUTPUT);

  display.setTextColor (WHITE);
  display.setTextSize(2);


}

void loop(){
  display.clearDisplay();
  Time[2]  = (ClockPoint==1) ? 58 :  32;

  // Update Clock
  if (Update == ON) TimeUpdate();
  // Always display time, clockpoint bool defines if dots 
  // show or not, no need for extra code in TimeUpdate func
  if(displayMode==TIME){
    Time[4]  = minute % 10 + 48;
    Time[3]  = minute / 10 + 48;
    Time[1]  = hour   % 10 + 48;
    Time[0]  = hour   / 10 + 48;
    //if mode is TIME the led is off
    draw_text(25, 1, "Time:", 2);
  }else{
    Time[4]  = alarm_minute % 10 + 48;
    Time[3]  = alarm_minute / 10 + 48;
    Time[1]  = alarm_hour   % 10 + 48;
    Time[0]  = alarm_hour   / 10 + 48;
    //if mode is ALARM led is on
    draw_text(25, 1, "Alarm:", 2);
  }
  if(digitalRead(switch1)){
    display.drawBitmap(100,-2,epd_bitmap_clk,17,17,WHITE);
  }

  draw_text(5, 25, Time, 4);

  // Button functions
  alarmSetBtn.loop();
  timeSetBtn.loop();
  hourBtn.loop();
  minuteBtn.loop();
  
  // Changing time with buttons
  incrementHours_Time();
  incrementMinute_Time();

  // Changing the alarm time with buttons
  incrementHours_Alarm();
  incrementMinute_Alarm();

  //alarm alert with buzzer
  alarmalert();

  // Making time into the right format
  hour%=24;
  minute%=60;
  alarm_hour%=24;
  alarm_minute%=60;

  display.display();
}

void draw_text(byte x_pos, byte y_pos, char *text, byte text_size) {
  display.setCursor(x_pos, y_pos);
  display.setTextSize(text_size);
  display.print(text);
}

// Clock functions and Calculations
void TimingISR(){
  halfsecond ++;
  Update = ON;
  if(halfsecond == 2){
    second ++;
    if(second == 60)
    {
      minute ++;
      if(minute == 60)
      {
        hour ++;
        if(hour == 24)hour = 0;
        minute = 0;
      }
      second = 0;
    }
    halfsecond = 0;
  }
  ClockPoint = !ClockPoint;
}
void TimeUpdate(void){
  Update = OFF;
}

//Button functions to change time
void incrementHours_Time(){
  enum {s0,s1,s2,s3};
  static int state=s0;
  switch(state)
  {
    case s0:
      if(timeSetBtn.getState()==LOW)
      {  
        state=s1;
        displayMode=TIME;
      }
    break;  
    case s1:
      if(hourBtn.getState()==LOW && timeSetBtn.getState()==LOW)
      {  
        hour++;
        pm1=millis();
        state=s2;
      }  
      else
      {
        state=s0;
      }  

    break;

    case s2:
      if(hourBtn.getState()==LOW && timeSetBtn.getState()==LOW)
      {   
        if((millis()-pm1)>800)
        {
          pm1=millis();
          state=s3;
        }
      }
      else
      {
        state=s0;
      }

    break;

    case s3:
      if(hourBtn.getState()==LOW && timeSetBtn.getState()==LOW)
      {   
        if((millis()-pm1)>250)
        {
          pm1=millis();
          hour++;
        }
      }
      else
      {
        state=s0;
      }
    break;
  }
}

void incrementMinute_Time(){
  enum {s0,s1,s2,s3};
  static int state=s0;
  switch(state)
  {
    case s0:
      if(timeSetBtn.getState()==LOW)
      {  
        state=s1;
        displayMode=TIME;
        
      }
    break;  
    case s1:
      if(minuteBtn.getState()==LOW && timeSetBtn.getState()==LOW)
      { 
        minute++;
        pm2=millis();
        state=s2;
      }  
      else
      {
        state=s0;
      }  

    break;

    case s2:
      if(minuteBtn.getState()==LOW && timeSetBtn.getState()==LOW)
      {   
        if((millis()-pm2)>800)
        {
          pm2=millis();
          state=s3;
        }
      }
      else
      {
        state=s0;
      }

    break;

    case s3:
      if(minuteBtn.getState()==LOW && timeSetBtn.getState()==LOW)
      {   
        if((millis()-pm2)>250)
        {
          pm2=millis();
          minute++;
        }
      }
      else
      {
        state=s0;
      }
    break;
  }
}

//Button functions to change the alarm time

void incrementHours_Alarm(){
  enum {s0,s1,s2,s3};
  static int state=s0;
  switch(state)
  {
    case s0:
      if(alarmSetBtn.getState()==LOW)
      {  
        state=s1;
        displayMode=ALARM;
      }
    break;  
    case s1:
      if(hourBtn.getState()==LOW && alarmSetBtn.getState()==LOW)
      {  
        alarm_hour++;
        pm1=millis();
        state=s2;
      }  
      else
      {
        state=s0;
      }  

    break;

    case s2:
      if(hourBtn.getState()==LOW && alarmSetBtn.getState()==LOW)
      {   
        if((millis()-pm1)>800)
        {
          pm1=millis();
          state=s3;
        }
      }
      else
      {
        state=s0;
      }

    break;

    case s3:
      if(hourBtn.getState()==LOW && alarmSetBtn.getState()==LOW)
      {   
        if((millis()-pm1)>250)
        {
          pm1=millis();
          alarm_hour++;
        }
      }
      else
      {
        state=s0;
      }
    break;
  }
}

void incrementMinute_Alarm(){
  enum {s0,s1,s2,s3};
  static int state=s0;
  switch(state)
  {
    case s0:
      if(alarmSetBtn.getState()==LOW)
      {  
        state=s1;
        displayMode=ALARM;
      }
    break;  
    case s1:
      if(minuteBtn.getState()==LOW && alarmSetBtn.getState()==LOW)
      { 
        alarm_minute++;
        pm2=millis();
        state=s2;
      }  
      else
      {
        state=s0;
      }  

    break;

    case s2:
      if(minuteBtn.getState()==LOW && alarmSetBtn.getState()==LOW)
      {   
        if((millis()-pm2)>800)
        {
          pm2=millis();
          state=s3;
        }
      }
      else
      {
        state=s0;
      }

    break;

    case s3:
      if(minuteBtn.getState()==LOW && alarmSetBtn.getState()==LOW)
      {   
        if((millis()-pm2)>250)
        {
          pm2=millis();
          alarm_minute++;
        }
      }
      else
      {
        state=s0;
      }
    break;
  }
}


void alarmalert(){
  //if the hour,minute is the same as the alarm hour,minute the switch is in 1 (on alarm)
  // put the buzzer in a specific frecuence
  if (hour==alarm_hour && minute==alarm_minute && digitalRead(switch1)){
      tone(buzzer1,2000);
      digitalWrite(rled,HIGH);
  }
  //if not the same, it return to a neutral state
  else{
    noTone(buzzer1);
    digitalWrite(rled,LOW);
  }
}