/*
 * ----------------------------------------------------------------------------
 * "THE BEER-WARE LICENSE" (Revision 42):
 * <[email protected]> wrote this file.  As long as you retain this notice you
 * can do whatever you want with this stuff. If we meet some day, and you think
 * this stuff is worth it, you can buy me a beer in return.   Adi Clepcea
 *
 * https://www.instructables.com/Arduino-multiple-timer-Day-based-and-alarm-based/
 *
 * ----------------------------------------------------------------------------
 */

#include "Wire.h"
#include "math.h"
#include "EEPROM.h"
#include "LiquidCrystal_I2C.h"

#define LCD_I2C_ADDR  0x27
#define LCD_COLUMNS   16
#define LCD_LINES     2

LiquidCrystal_I2C lcd(LCD_I2C_ADDR, LCD_COLUMNS, LCD_LINES);

#define DS1307_I2C_ADDRESS 0x68
#define PIN_STG 8                         //left button
#define PIN_MENU 2                        //menu button
#define PIN_DR 10                         //right button
#define PIN_COMMAND_A A1                  //releu A
#define PIN_COMMAND_B A2                  //releu B
#define PIN_COMMAND_C A3                  //releu C
#define PIN_COMMAND_D A0                  //releu D
#define PIN_COMMAND_E 7                   //releu E
#define PIN_COMMAND_F 9                   //releu F
#define PIN_COMMAND_G 13                  //releu G
#define DESCHIS LOW
#define INCHIS HIGH


#define NR_ALARME 6
#define MAX_MENU_POS NR_ALARME*4+3        //trebuie sa fie NR_ALARME*4+3

byte menuPos=0;
byte subMenuPos = 0;
byte cursorPos = -1;
char linia1[17];
char linia2[17];
char linia[17];
boolean dataDirthy = false;
boolean oraDirthy = false;
long lastMillis=0;
boolean bAlarma = false;

typedef struct alarm{
   byte hour;
   byte minute;
   byte days;
   byte duration;
   byte relee;
   byte unitAlarma;
};

typedef struct ds1307_time{
   byte second;
   byte minute;
   byte hour;
   byte dayOfWeek;
   byte dayOfMonth;
   byte month;
   byte year;
};

struct ds1307_time time;

alarm* alarme = (alarm*) malloc(sizeof(alarm) * NR_ALARME);

byte relee[]={PIN_COMMAND_G, PIN_COMMAND_F,PIN_COMMAND_E,PIN_COMMAND_D,PIN_COMMAND_C,PIN_COMMAND_B,PIN_COMMAND_A};

byte decToBcd(byte val){
   return ((val/10*16)+(val%10));
}

byte bcdToDec(byte val){
   return ((val/16*10)+(val%16));
}

void setDateDs1307(struct ds1307_time time)
{
  Wire.beginTransmission(DS1307_I2C_ADDRESS);
  Wire.write(0);
  Wire.write(decToBcd(time.second));
  Wire.write(decToBcd(time.minute));
  Wire.write(decToBcd(time.hour));

  Wire.write(decToBcd(time.dayOfWeek));
  Wire.write(decToBcd(time.dayOfMonth));
  Wire.write(decToBcd(time.month));
  Wire.write(decToBcd(time.year));

  Wire.endTransmission();

}

void getDateDs1307(struct ds1307_time *time)
{
   Wire.beginTransmission(DS1307_I2C_ADDRESS);
   Wire.write(0);
   Wire.endTransmission();

   Wire.requestFrom(DS1307_I2C_ADDRESS,7);

   (*time).second = bcdToDec(Wire.read() & 0x7f);
   (*time).minute = bcdToDec(Wire.read());
   (*time).hour   = bcdToDec(Wire.read() & 0x3f);
   (*time).dayOfWeek = bcdToDec(Wire.read());
   (*time).dayOfMonth = bcdToDec(Wire.read());
   (*time).month  = bcdToDec(Wire.read());
   (*time).year   = bcdToDec(Wire.read());
}


void showMenu(){
  char *zile[7];
  zile[0]="L\0";
  zile[1]="M\0";
  zile[2]="M\0";
  zile[3]="J\0";
  zile[4]="V\0";
  zile[5]="S\0";
  zile[6]="D\0";
  if(menuPos==1){
    strcpy(linia1,"Data\0");
    strcpy(linia2,getDate(time));
    strcat(linia2," ");
    strcat(linia2,getZiuaSaptamaniiShort(time.dayOfWeek));
  }else if(menuPos==2){
    strcpy(linia1,"Ora\0");
    strcpy(linia2,getTime(time));
  }else if((menuPos+1)%4==0){  //incepand cu 3, din 4 in 4 avem ora alarmei
    strcpy(linia1,"Alarma ");
    linia1[7] = 48+(byte)((menuPos+1)/4)/10;
    linia1[8] = 48+(byte)(((menuPos+1)/4)-((menuPos+1)/4)/10*10);
    linia1[9]='\0';
    strcat(linia1," ora\0");
    strcpy(linia2,getFmt2Char((alarme[(menuPos+1)/4-1]).hour));
    strcat(linia2,":");
    strcat(linia2,getFmt2Char((alarme[(menuPos+1)/4-1]).minute));
    strcat(linia2,"    ");
  }else if((menuPos)%4==0){ //incepand cu 4, din 4 in 4 avem zielele alarmei
    strcpy(linia1,"Alarma ");
    linia1[7] = 48+(byte)((menuPos)/4)/10;
    linia1[8] = 48+(byte)((menuPos)/4-(menuPos)/4/10*10);
    linia1[9]='\0';
    strcat(linia1," zile\0");
    strcpy(linia2,"L");
    for(int i=6;i>=0;i--){
      if((alarme[(menuPos)/4-1]).days & (byte)ceil(pow(2,i))){
        strcat(linia2,"X");
      }else{
        strcat(linia2," ");
      }
      if(i>0)strcat(linia2,zile[7-i]);
    }
  }else if((menuPos-1)%4==0){  //incepand cu 5, din 4 in 4 avem durata alarmei
    strcpy(linia1,"Alarma ");
    linia1[7] = 48+(byte)((menuPos-1)/4)/10;
    linia1[8] = 48+(byte)((menuPos-1)/4-(menuPos-1)/4/10*10);
    linia1[9]='\0';
    strcat(linia1," durata\0");
    itoa(alarme[(menuPos-1)/4-1].duration,linia2,10);
    if(alarme[(menuPos-1)/4-1].unitAlarma){
      strcat(linia2," min");
    }else{
      strcat(linia2," sec");
    }
  }else if((menuPos-2)%4==0){   //incepand cu 6 din 4 in 4 avem releele activate de alarma
    strcpy(linia1,"Relee alarma ");
    linia1[13]= 48+(byte)((menuPos-2)/4)/10;
    linia1[14]= 48+(byte)((menuPos-2)/4-(menuPos-2)/4/10*10);
    linia1[15]='\0';
    strcpy(linia2,"");

    for(int i=6;i>=0;i--){
      char chBuff[2];
      chBuff[0]=6-i+65;
      chBuff[1]='\0';
      strcat(linia2,chBuff);

      if((alarme[(menuPos-2)/4-1]).relee & (byte)ceil(pow(2,i))){
        strcat(linia2,"X");
      }else{
        strcat(linia2," ");
      }
      //if(i>0)strcat(linia2,zile[7-i]);
    }
  }

}


//punem cursorul in functie de locatia noastra in sumbeniu
void showSubMenu(){
   if(menuPos==1 || menuPos==2){ //data sau ora
      switch(subMenuPos){
        case 1://zile
          cursorPos = 1;
          break;
        case 2://luni
          cursorPos = 4;
          break;
        case 3://ani
          cursorPos = 7;
          break;
        case 4://ziua saptamanii
          if(menuPos==1){//doar in cazul datei avem si ziua saptamanii
            cursorPos=12;
            break;
          }
        default:
          subMenuPos = 0;
          cursorPos = -1;
          break;
      }
   }else if((menuPos+1)%4==0){ //ora alarmelor
      switch(subMenuPos){
        case 1: //ora
          cursorPos = 1;
          break;
        case 2: //minutul
          cursorPos = 4;
          break;
        default:
          subMenuPos = 0;
          cursorPos = -1;
          break;
      }
   }else if((menuPos)%4==0){ //zilele alarmelor
     if(subMenuPos>0 && subMenuPos<8){
        cursorPos = subMenuPos*2-1;
     }else{
        subMenuPos = 0;
        cursorPos = -1;
     }
   }else if((menuPos-1)%4==0){ //durata alarmelor
     if(subMenuPos==1){
        cursorPos = 0;
     }else if(subMenuPos==2){
        cursorPos=4;
     }else{
        subMenuPos=0;
        cursorPos = -1;
     }
   }else if((menuPos-2)%4==0){ //releele alarmelor
     if(subMenuPos>0 && subMenuPos<8){
        cursorPos = subMenuPos*2-1;
     }else{
        subMenuPos = 0;
        cursorPos = -1;
     }
   }
}

void menu(){
  if((millis()-lastMillis)>150){
    if(digitalRead(PIN_MENU)==LOW && menuPos==0){
      menuPos=(menuPos+1)%(MAX_MENU_POS);
      showMenu();
    }else if(digitalRead(PIN_MENU)==LOW){
      subMenuPos++;
      showSubMenu();
    }
  }
  lastMillis=millis();
}

void readValues(){
  //citim valorile initiale din eeprom.
  //daca nu exista nimic scris in eeprom, consideram si orele si durata ca fiind 0 si scriem in EEPROM
  for(int i=0;i<NR_ALARME;i++){
     byte b = EEPROM.read(i*5+1);
     if(b==0xFF){
       b = 0;
       EEPROM.write(i*5+1,b);
     }
     (alarme[i]).hour = b & 31;      //pozitiile 1, 6, 11 etc.
     (alarme[i]).unitAlarma = b >> 6;

     b = EEPROM.read(i*5+2);
     if(b==0xFF){
       b = 0;
       EEPROM.write(i*5+2,b);
     }
     (alarme[i]).minute = b;    //pozitiile 2, 7, 12 etc.
     b = EEPROM.read(i*5+3);
     if(b==0xFF){
       b = 0;
       EEPROM.write(i*5+3,b);
     }
     (alarme[i]).days = b;      //pozitiile 3, 8, 13 etc.
     b = EEPROM.read(i*5+4);
     if(b==0xFF){
       b = 0;
       EEPROM.write(i*5+4,b);
     }
     (alarme[i]).duration = b;  //pozitiile 4, 9, 14 etc.
     b = EEPROM.read(i*5+5);
     if(b==0xFF){
       b = 0;
       EEPROM.write(i*5+5,b);
     }
     (alarme[i]).relee = b;  //pozitiile 5, 10, 15 etc.
  }
}



void setup(){

   pinMode(PIN_MENU,INPUT);    //btn stg
   pinMode(PIN_DR,INPUT);    //meniu
   pinMode(PIN_STG,INPUT);   //btnDr

   for(int i=0;i<7;i++){
     pinMode(relee[i],OUTPUT);
     digitalWrite(relee[i],DESCHIS);
   }
   digitalWrite(PIN_MENU,HIGH);
   digitalWrite(PIN_STG,HIGH);
   digitalWrite(PIN_DR,HIGH);
   attachInterrupt(0,menu,CHANGE);

   //alarmele
   readValues();

   //initializam DS_1307
   Wire.begin();
   lcd.init();
   lcd.backlight();
   Serial.begin(9600);
   struct ds1307_time time;
   time.second = 15;
   time.minute = 55;
   time.hour = 11;
   time.dayOfWeek = 5;
   time.dayOfMonth = 14;
   time.month = 3;
   time.year = 14;


   //setDateDs1307(time);

}

char* getFmt2Char(byte val){
  char *buf = "00\0";
  buf[0] = 48+val/10;
  buf[1] = 48+val%10;
  return buf;
}

char* getDate(struct ds1307_time time){
  char *buf="        \0";
  buf[0]=48+time.dayOfMonth/10;
  buf[1]=48+time.dayOfMonth%10;
  buf[2]='.';
  buf[3]=48+time.month/10;
  buf[4]=48+time.month%10;
  buf[5]='.';
  buf[6]=48+time.year/10;
  buf[7]=48+time.year%10;
  return buf;
}

char* getTime(struct ds1307_time time){
  char *buf="        \0";
  buf[0]=48+time.hour/10;
  buf[1]=48+time.hour%10;
  buf[2]=':';
  buf[3]=48+time.minute/10;
  buf[4]=48+time.minute%10;
  buf[5]=':';
  buf[6]=48+time.second/10;
  buf[7]=48+time.second%10;
  return buf;
}

char* getZiuaSaptamaniiShort(byte val){
  switch(val){
     case 1:
       return "Lun\0"; 	//Mon
       break;
     case 2:
       return "Mar\0";	//Tue
       break;
     case 3:
       return "Mie\0";	//Wen
       break;
     case 4:
       return "Joi\0"; //Thu
       break;
     case 5:
       return "Vin\0";	//Fri
       break;
     case 6:
       return "Sam\0";	//Sat
       break;
     case 7:
       return "Dum\0"; //Sun
       break;
     default:
       return "?\0";
       break;
  }
}

char* getZiuaSaptamanii(byte val){
  switch(val){
     case 1:
       return "Luni\0"; 	//replace with Monday if you want
       break;
     case 2:
       return "Marti\0";	//replace with Tuesday if you want
       break;
     case 3:
       return "Miercuri\0";	//replace with Wednesday if you want
       break;
     case 4:
       return "Joi\0";	//replace with Thursday if you want
       break;
     case 5:
       return "Vineri\0";	//replace with Friday if you want
       break;
     case 6:
       return "Sambata\0"; //replace with Saturday if you want
       break;
     case 7:
       return "Duminica\0"; //replace with Sunday if you want
       break;
     default:
       return "?\0";
       break;
  }
}

void minusData(){
   if(subMenuPos==1 && time.dayOfMonth>1){ //zilele
     time.dayOfMonth--;
   }else if(subMenuPos==2 && time.month>1){
     time.month--;
   }else if(subMenuPos==3 && time.year>1){
     time.year--;
   }else if(subMenuPos==4 && time.dayOfWeek>1){
     time.dayOfWeek--;
   }
   dataDirthy=true;
   showMenu();
}

void plusData(){
  if(subMenuPos==1){ //zilele aici utilizatorul trebuie sa aiba grija sa nu puna mai multe zile dact are luna, eu am limitat doar la 31
     time.dayOfMonth=(time.dayOfMonth+1)%32;
     if(time.dayOfMonth==0){
        time.dayOfMonth=1;
     }
   }else if(subMenuPos==2){
     time.month=(time.month+1)%13;
     if(time.month==0){
        time.month=1;
     }
   }else if(subMenuPos==3){
     time.year=(time.year+1)%99;
   }else if(subMenuPos==4){
     time.dayOfWeek=(time.dayOfWeek+1)%8;
     if(time.dayOfWeek==0){
       time.dayOfWeek=1;
     }
   }
   dataDirthy=true;
   showMenu();
}

void minusOra(){
   if(subMenuPos==1 && time.hour>0){ //zilele
     time.hour=time.hour-1;
   }else if(subMenuPos==2 && time.minute>0){
     time.minute=time.minute-1;
   }else if(subMenuPos==3 && time.second>1){
     time.second=time.second-1;
   }
   oraDirthy = true;
   showMenu();
}

void plusOra(){
  if(subMenuPos==1){ //zilele
     time.hour=(time.hour+1)%24;
   }else if(subMenuPos==2){
     time.minute=(time.minute+1)%60;
   }else if(subMenuPos==3){
     time.second=(time.second+1)%60;
   }
   oraDirthy = true;
   showMenu();
}

void minusAlarma(int pos){
   alarme[pos-1];
   if(subMenuPos==1 && alarme[pos-1].hour>0){ //zilele
     alarme[pos-1].hour--;
   }else if(subMenuPos==2 && alarme[pos-1].minute>0){
     alarme[pos-1].minute--;
   }
   showMenu();
}

void plusAlarma(byte pos){
   if(subMenuPos==1){ //zilele
     alarme[pos-1].hour=(alarme[pos-1].hour+1)%24;
   }else if(subMenuPos==2){
     alarme[pos-1].minute=(alarme[pos-1].minute+1)%60;
   }
   showMenu();
}

void minusZileAlarma(byte pos){

     Serial.println(alarme[pos-1].days);
     alarme[pos-1].days =alarme[pos-1].days ^ (byte)ceil(pow(2,(7-subMenuPos)));
     Serial.println(alarme[pos-1].days);

   showMenu();

}

void plusReleeAlarma(byte pos){
  minusReleeAlarma(pos);
}

void minusReleeAlarma(byte pos){

     Serial.println(alarme[pos-1].relee);
     alarme[pos-1].relee = alarme[pos-1].relee ^ (byte)ceil(pow(2,(7-subMenuPos)));
     Serial.println(alarme[pos-1].relee);

     showMenu();

}

void plusZileAlarma(byte pos){
  minusZileAlarma(pos);
}

void minusUnitAlarma(byte pos){
   plusUnitAlarma(pos);
}

void plusUnitAlarma(byte pos){
  if(subMenuPos==2){
    alarme[pos-1].unitAlarma=(alarme[pos-1].unitAlarma+1)%2;
  }
}

void minusDurataAlarma(byte pos){
   if(alarme[pos-1].duration>0 && subMenuPos==1){
      alarme[pos-1].duration--;
   }else if(subMenuPos==2){
     minusUnitAlarma(pos);
   }
   showMenu();
}

void plusDurataAlarma(byte pos){
  if(subMenuPos==1){
     alarme[pos-1].duration=(alarme[pos-1].duration+1)%240;
  }else if(subMenuPos==2){
     plusUnitAlarma(pos);
   }
  showMenu();
}

void saveValues(){
  for(int i=0;i<NR_ALARME;i++){
    EEPROM.write(i*5+1,(alarme[i]).hour | (alarme[i].unitAlarma << 6));
    EEPROM.write(i*5+2,(alarme[i]).minute);
    EEPROM.write(i*5+3,(alarme[i]).days);
    EEPROM.write(i*5+4,(alarme[i]).duration);
    EEPROM.write(i*5+5,(alarme[i]).relee);
  }
}

void applyNewDateHour(){
 if(dataDirthy || oraDirthy){
    if(oraDirthy){
      Serial.println("Cu ora");
      setDateDs1307(time);
    }else{ //daca ora nu s-a modificat o lasam si noi cum este
      Serial.println("Fara ora");
      struct ds1307_time tmp;
      getDateDs1307(&tmp);
      tmp.dayOfMonth = time.dayOfMonth;
	  tmp.dayOfWeek = time.dayOfWeek;
      tmp.month = time.month;
      tmp.year = time.year;
      setDateDs1307(tmp);
    }
    dataDirthy=false;
    oraDirthy = false;
  }
  saveValues();
}
long valAlarmaMin(byte index){
  return (alarme[index]).hour*3600L+(alarme[index]).minute*60L;
}
long valAlarmaMax(byte index){
  byte multipl = (alarme[index]).unitAlarma?60:1;
  return (alarme[index]).hour*3600L+(alarme[index]).minute*60L+multipl*(alarme[index]).duration;
}
void checkAlarms(){
    long valComp = time.hour*3600L+time.minute*60L+time.second;
    bAlarma=false;
    strcpy(linia,getTime(time));
    strcat(linia,";A");
    char cAl[4];
    long timeToFinish=0,vAlMax=0;
    boolean tipTimp=0;
    byte statusRelee[7];
    for(int i=0;i<7;i++){
      statusRelee[i]=0;
    }
    for(int i=0;i<NR_ALARME;i++){
       struct alarm alarma = alarme[i];
       vAlMax=valAlarmaMax(i);
       if(valComp>=valAlarmaMin(i) && valComp<=vAlMax){ //comparam valorile minime si maxime ale fiecarei alarme cu timpul actual
         if((byte)ceil(pow(2,7-time.dayOfWeek)) & (alarma).days){
           bAlarma=true;
           if((timeToFinish==0 && vAlMax-valComp>timeToFinish) || (vAlMax-valComp<timeToFinish)){//setam durata pana la sf primei alarme
             tipTimp = alarma.unitAlarma;
             if(tipTimp && vAlMax-valComp>=60){
               timeToFinish=(vAlMax-valComp)/60;
             }else{
               timeToFinish=vAlMax-valComp;
               tipTimp=0;
             }
           }
           for(int j=6;j>=0;j--){
              if((alarma).relee & (byte)(ceil(pow(2,j)))){
                statusRelee[j]=statusRelee[j] | 1;
              }
           }

           itoa((i+1),cAl,10);
           if(strlen(linia)<11){
             strcat(linia,cAl);
           }
         }
       }
    }
    for(int i=0;i<7;i++){
      if(statusRelee[i]){
        digitalWrite(relee[i],INCHIS);
      }else{
        digitalWrite(relee[i],DESCHIS);
      }
    }
    if(bAlarma){
      itoa(timeToFinish,cAl,10);
      strcat(linia," ");
      strcat(linia,cAl);
      if(tipTimp){
        strcat(linia,"m");
      }else{
        strcat(linia,"s");
      }
    }
}

void loop(){
   //daca avem meniul apasat aratam ceea ce ne trebuie si nu mai aratam data si ora curente
   if(menuPos>0){
     lcd.clear();
     lcd.setCursor(0,0);
     lcd.print(linia1);
     lcd.setCursor(0,1);
     lcd.print(linia2);
     if(digitalRead(PIN_STG)==LOW && menuPos){ //avem apasat minus
       delay(100);
       if(subMenuPos){
           if(menuPos==1){ //data
             minusData();
           }else if(menuPos==2){
             minusOra();
           }else if((menuPos+1)%4==0){
             minusAlarma((menuPos+1)/4);
           }else if((menuPos)%4==0){ //zile alarma
             minusZileAlarma((menuPos)/4);
           }else if((menuPos-1)%4==0){ //durata alarma
             minusDurataAlarma((menuPos-1)/4);
           }else if((menuPos-2)%4==0){ //relee alarma
             minusReleeAlarma((menuPos-2)/4);
           }
       }
       else{
          if(menuPos==1){
            applyNewDateHour();
          }
          menuPos--;
          showMenu();
       }
     }else if(digitalRead(PIN_DR)==LOW && menuPos){ //avem apasat plus
       delay(200);
       if(subMenuPos){
           if(menuPos==1){ //data
             plusData();
           }else if(menuPos==2){
             plusOra();
           }else if((menuPos+1)%4==0){  //ora alarma
             plusAlarma((menuPos+1)/4);
           }else if((menuPos)%4==0){ //zile alarma
             plusZileAlarma((menuPos)/4);
           }else if((menuPos-1)%4==0){ //durata alarma
             plusDurataAlarma((menuPos-1)/4);
           }else if((menuPos-2)%4==0){ //durata alarma
             plusReleeAlarma((menuPos-2)/4);
           }
       }
       else if(menuPos<MAX_MENU_POS-1){
          ++menuPos;
          Serial.print("Menu pos: ");
          Serial.print(menuPos);
          showMenu();
       }else{
          applyNewDateHour();
          menuPos=0;
       }
     }else if(subMenuPos>0 && cursorPos>-1){

//       Serial.println(cursorPos);

       lcd.cursor();
       lcd.setCursor(cursorPos,1);
     }else{

       lcd.noCursor();

     }

     delay(100);
     return;

   }

   lcd.clear();

   char buf[3];
   getDateDs1307(&time);
   Serial.print(getTime(time));
   Serial.print(" ");
   Serial.print(getDate(time));
   Serial.print(":");
   Serial.print("Ziua saptamanii: ");
   Serial.println(time.dayOfWeek, DEC);

   lcd.setCursor(0, 0);
   lcd.print(getZiuaSaptamanii(time.dayOfWeek));
   lcd.setCursor(8,0);
   lcd.print(getDate(time));
   if(bAlarma){
     lcd.setCursor(0,1);
     lcd.print(linia);
   }else{
     lcd.setCursor(4,1);
     lcd.print(getTime(time));
     lcd.setCursor(12,1);
     lcd.print("    ");
     lcd.noCursor();
   }
   checkAlarms();

   delay(1000);

}
GND5VSDASCLSQWRTCDS1307+
NOCOMNCVCCGNDINLED1PWRRelay Module
NOCOMNCVCCGNDINLED1PWRRelay Module