#include <Wire.h>
#include <EEPROM.h>
#include <RTClib.h>
#include <neoPixel.h>
#include <lilParser.h>
#include <colorObj.h>
#include <blinker.h>

#define NUM_COLORS  24

#define NUM_LEDS    2
#define LED_PIN     3
#define HOUR_LED    1
#define DECMIN_LED  0


enum        mainComs  { noCommand,
                        help,
                        time,
                        stime,
                        sdate,
                        setHour,
                        setMin,
                        setSec,
                        reset,
                        gColor,
                        sColor,
                        gCode
                      };

                      
lilParser   mParser;
//RTC_PCF8523 rtc;
RTC_DS1307 rtc;       // The clock chip Wokw]i uses.
DateTime    drawtime;

colorObj    colors[NUM_COLORS];
neoPixel    theNeoPixel(NUM_LEDS,LED_PIN);

blinker  aBlinker;

void setup() {

  Serial.begin(57600);
  Serial.println("Serial online.");
  Serial.println("Color clock : This is built around 6 colors.");
  Serial.println("Blue  Purple  Red   Orange  Yellow   Green");
  Serial.println("So Blue can be..  0:00   6:00   12:00  18:00");
  Serial.println("Purple can be..   1:00   7:00   13:00  19:00");
  Serial.println("And that's the upper LED. The lower LED?");
  Serial.println("The lower LED is decaMinutes.");
  Serial.println("So Blue is 0 min, Purple is 10 min, Red is 20 min ...");
  if (! rtc.begin()) {
    Serial.println("Couldn't find RTC");
    Serial.flush();
    while (1);
  }
  
  theNeoPixel.begin();
  theNeoPixel.setPixelColor(0,&black);
  theNeoPixel.setPixelColor(1,&black);
  theNeoPixel.show();

  mParser.addCmd(help,"?");
  mParser.addCmd(time,"time");
  mParser.addCmd(stime,"settime");
  mParser.addCmd(sdate,"setdate");
  mParser.addCmd(setHour,"hour");
  mParser.addCmd(setMin,"minute");
  mParser.addCmd(setSec,"sec");
  mParser.addCmd(reset,"reset");
  mParser.addCmd(gColor,"gcolor");
  mParser.addCmd(sColor,"scolor");
  mParser.addCmd(gCode,"gcode");

  aBlinker.setOnOff(true);
  doReset();     // Added because Wokwi has no EEPROM that saves the colors.
  readParams();
  checkTime(true);
}


// **********************************************************************
// Param handleing
// **********************************************************************


void readParams(void) {

  RGBpack paramBlock[NUM_COLORS];
  
  EEPROM.get(0,paramBlock);
  for (int i=0;i<NUM_COLORS;i++) {
    colors[i].setColor(&(paramBlock[i]));
  }
}


void saveParams(void)  {
  
  RGBpack paramBlock[NUM_COLORS];

  for (int i=0;i<NUM_COLORS;i++) {
    paramBlock[i] = colors[i].packColor();
  }
  EEPROM.put(0,paramBlock);
}



// **********************************************************************
// Checking time
// **********************************************************************


// So, taking a page from Dash's code book.. I decide to use my first multi dimensional array as a hash table.
//
//                      0  1  2  3  4  5  6  7  8  9  10 11 12 13 14 15 16 17 18 19 20 21 22 23 <- hour number / index
//                      B  P  R  O  Y  G  B  P  R  O  Y  G  B  P  R  O  Y  G  B  P  R  O  Y  G  <- hour color

byte hashTable[6][24] { 0, 0, 0, 0, 6, 6, 6, 6, 6, 6, 12,12,12,12,12,12,18,18,18,18,18,18,0, 0,    /* blue     */
                        1, 1, 1, 1, 1, 7, 7, 7, 7, 7, 7, 13,13,13,13,13,13,19,19,19,19,19,19,1,    /* purple   */
                        2, 2, 2, 2, 2, 2, 8, 8, 8, 8, 8, 8, 14,14,14,14,14,14,20,20,20,20,20,20,   /* red      */
                        21,3, 3, 3, 3, 3, 3, 9, 9, 9, 9, 9, 9, 15,15,15,15,15,15,21,21,21,21,21,   /* orange   */
                        22,22,4, 4, 4, 4, 4, 4, 10,10,10,10,10,10,16,16,16,16,16,16,22,22,22,22,   /* yellow   */
                        23,23,23,5, 5, 5, 5, 5, 5, 11,11,11,11,11,11,17,17,17,17,17,17,23,23,23    /* green    */
                      };


void checkTime(bool justDoIt) {
  
  DateTime  now;
  byte      decaMin;
  byte      decaMinIndex;
  
  now = rtc.now();
  if((now.minute()!=drawtime.minute())||(now.hour()!=drawtime.hour())||justDoIt) {  // If times have changed, or need to..
    decaMin = now.minute()/10;
    decaMinIndex = hashTable[decaMin][now.hour()];
    theNeoPixel.setPixelColor(HOUR_LED,&(colors[now.hour()]));
    theNeoPixel.setPixelColor(DECMIN_LED,&(colors[decaMinIndex]));
    theNeoPixel.show();
    drawtime = now;
  }
}



// **********************************************************************
// Command parsing stuff
// **********************************************************************


void doShowTime(void) {

   DateTime now = rtc.now();
   Serial.print("\tTime : ");
   Serial.print(now.hour());Serial.print(":");
   Serial.print(now.minute());Serial.print(":");
   Serial.print(now.second());
   Serial.print("    ");
   Serial.print("Date : ");
   Serial.print(now.month());Serial.print("/");
   Serial.print(now.day());Serial.print("/");
   Serial.print(now.year());
}


void doSetTime() {

   byte  newHour;
   byte  newMin;
   byte  newSec;
   
   if (mParser.numParams()==3) {
      newHour = atoi(mParser.getParam());
      newMin = atoi(mParser.getParam());
      newSec = atoi(mParser.getParam());
      if ((newHour>=0 && newHour< 24)&&(newMin>=0 && newMin< 59)&&(newSec>=0 && newSec< 59)) {
         DateTime now = rtc.now();
         rtc.adjust(DateTime(now.year(),now.month(),now.day(),newHour,newMin,newSec));
         checkTime(true);
      } else {
         Serial.println("Error: One of your entered parameters is out of whack. I'm too lazy to figure out which one though.");
      }
   } else {
      Serial.println("Error: You need to add three values to this command, hour minutes seconds.");
   }
}


void doSetDate() {

   int   newYear;
   byte  newMonth;
   byte  newDay;
   
   if (mParser.numParams()==3) {
      newYear = atoi(mParser.getParam());
      newMonth = atoi(mParser.getParam());
      newDay = atoi(mParser.getParam());
      if (newMonth>0&&newMonth<=12) {
         DateTime now = rtc.now();
         rtc.adjust(DateTime(newYear,newMonth,newDay,now.hour(),now.minute(),now.second()));
      } else {
         Serial.print("Error: C'mon! What kind of month number is ");
         Serial.print(newMonth);
         Serial.println(" ?");
      }
   } else {
      Serial.println("Error: You need to enter year month day, three values, to set the date.");
   }
}


void doSetHour() {

   byte  newVal;
  
   if (mParser.numParams()) {
      newVal = atoi(mParser.getParam());
      if (newVal>=0&&newVal<24) {
         DateTime now = rtc.now();
         rtc.adjust(DateTime(now.year(),now.month(),now.day(),newVal,now.minute(),now.second()));
         checkTime(true);
      } else {
         Serial.println("Error: You need to enter an hour vlaue from 0..23 to set an hour.");
      }
   } else {
      Serial.println("Error: You also need to tell me what hour to set the clock..");
   }
}


void doSetMin() {

  byte  newVal;
  
   if (mParser.numParams()) {
      newVal = atoi(mParser.getParam());
      if (newVal>=0&&newVal<60) {
         DateTime now = rtc.now();
         rtc.adjust(DateTime(now.year(),now.month(),now.day(),now.hour(),newVal,now.second()));
         checkTime(true);
      } else {
         Serial.println("Error: Minutes go from 0..59.");
      }
   } else {
      Serial.println("Error: You forgot to tell me what minute to set this to.");
   }
}


void doSetSec() {
  
   byte  newVal;
  
   if (mParser.numParams()) {
      newVal = atoi(mParser.getParam());
      if (newVal>=0&&newVal<60) {
         DateTime now = rtc.now();
         rtc.adjust(DateTime(now.year(),now.month(),now.day(),now.hour(),now.minute(),newVal));
         checkTime(true);
      } else {
         Serial.println("Error: Seconds go from 0..59.");
      }
   } else {
      Serial.println("Error: You forgot to tell me what second to set this to.");
   }
}


void doReset() {

   colors[0].setColor(0,0,255);
   colors[1].setColor(140,0,255);
   colors[2].setColor(255,0,0);
   colors[3].setColor(255,100,0);
   colors[4].setColor(255,255,0);
   colors[5].setColor(0,255,0);
   colors[6].setColor(0,0,255);
   colors[7].setColor(140,0,255);
   colors[8].setColor(255,0,0);
   colors[9].setColor(255,100,0);
   colors[10].setColor(255,255,0);
   colors[11].setColor(0,255,0);
   colors[12].setColor(0,0,255);
   colors[13].setColor(140,0,255);
   colors[14].setColor(255,0,0);
   colors[15].setColor(255,100,0);
   colors[16].setColor(255,255,0);
   colors[17].setColor(0,255,0);
   colors[18].setColor(0,0,255);
   colors[19].setColor(140,0,255);
   colors[20].setColor(255,0,0);
   colors[21].setColor(255,100,0);
   colors[22].setColor(255,255,0);
   colors[23].setColor(0,255,0);
   saveParams();
   checkTime(true);
}


void doGetHourColor(void) {

  int   hourVal;
  
  if (mParser.numParams()==1) {
    hourVal = atoi(mParser.getParam());
  } else {
    hourVal = drawtime.hour();
  }
  if (hourVal>=0&&hourVal<24) {
    Serial.print("Hour ");Serial.print(hourVal);Serial.print(" RGB Value : ");
    Serial.print(colors[hourVal].getRed());
    Serial.print(" ");Serial.print(colors[hourVal].getGreen());
    Serial.print(" ");Serial.println(colors[hourVal].getBlue());
  } else {
    Serial.print("Error: Hour number is out of bounds : ");Serial.println(hourVal);
  }
}


void doSetHourColor(void) {

   int   hourVal;
   int   redVal;
   int   greenVal;
   int   blueVal;
  
   if (mParser.numParams()==4) {
      hourVal = atoi(mParser.getParam());
      if (hourVal>=0 && hourVal<24) {
         redVal = atoi(mParser.getParam());
         greenVal = atoi(mParser.getParam());
         blueVal = atoi(mParser.getParam());
         colors[hourVal].setColor(redVal,greenVal,blueVal);
         saveParams();
         checkTime(true);
      } else {
         Serial.println("Error: Only values between 0..23 please.");
      }
   } else {
      Serial.println("Error: This comand is looking for four values. hour red green blue. (colors got fro 0..255)");
  }
}


void doGetColorList(void) {

   Serial.println();
   Serial.println("// ******** Auto generated code listing ********");
   Serial.println();
   for (byte i=0;i<NUM_COLORS;i++) {
      Serial.print("\tcolors[");
      Serial.print(i);
      Serial.print("].setColor(");
      Serial.print(colors[i].getRed());
      Serial.print(",");
      Serial.print(colors[i].getGreen());
      Serial.print(",");
      Serial.print(colors[i].getBlue());
      Serial.println(");");
   }
   Serial.println();
   Serial.println("// ********         End listing         ********");
}


void checkParse(void) {

  char  inChar;
  int   command;
  
  if (Serial.available()) {
    inChar = Serial.read();
    Serial.print(inChar);
    command = mParser.addChar(inChar);
    switch (command) {                       
      case noCommand      : break;
      case help           : 
        Serial.println("Commands:");
        Serial.println("   time prints the currnet time.");
        Serial.println("   settime followed by 3 numbers hour, minute, second, sets the current time.");
        Serial.println("   setdate followed by 3 numbers year, month, day, sets the curnent date.");
        Serial.println("   hour followed by a number 0..23. Sets the hour.");
        Serial.println("   minute followed by a number 0..59. Sets the minutes.");
        Serial.println("   sec followed by a number 0..59. Sets the seconds.");
        Serial.println("   reset will reset all colors to defaults.");
        Serial.println("   gcolor reads back the current hour's color.");
        Serial.println("   scolor followed by 4 numbers hour,R,G,B sets that hour's color.");
        Serial.println("   gcode will print a listing of your color settings to be used as the new defualt color list.");
        Serial.println();
      break;
      case time     : doShowTime();   break;
      case stime    : doSetTime();    break;
      case sdate    : doSetDate();    break;
      case setHour  : doSetHour();    break;
      case setMin   : doSetMin();     break;
      case setSec   : doSetSec();     break;
      case reset    : doReset();      break;
      case gColor   : doGetHourColor();   break;
      case sColor   : doSetHourColor();   break;
      case gCode    : doGetColorList();   break;
      default       : 
        Serial.println("I really don't know what your looking for.");
        Serial.println("Try typing ? for a list of commands.");
        Serial.println();
      break;
    }
  } 
}



// **********************************************************************
// Main loop()
// **********************************************************************


void loop() {     // During loop..

   idle();
   checkTime(false);
   checkParse();
}
GND5VSDASCLSQWRTCDS1307+