//#include <ea_malloc.h>
//#include <ll.h>

//=================================
//
//==================================
// inluded libraries

#include <Wire.h>
#include <Adafruit_MCP23X17.h>
#include <mbed.h>
#include <mbed_mktime.h>
#include <Arduino_H7_Video.h>
#include <Arduino_GigaDisplay_GFX.h>
#include <Arduino_GigaDisplayTouch.h>
#include <ArduinoGraphics.h>
#include <lvgl.h>
//#include "lv_conf.h"
//#include <KVstore.h>
//#include <kvstore_global_api.h>
#include <SD.h>

#include "Logo.h"
#include "Melodies.h"

#include <Arduino_USBHostMbed5.h>
#include <DigitalOut.h>
#include <FATFileSystem.h>
//#include <List.hpp>

//Fonts
#include <Fonts/FreeSans12pt7b.h>
#include <Fonts/FreeSans18pt7b.h>
#include <Fonts/FreeSans24pt7b.h>

Arduino_H7_Video Display(800, 480, GigaDisplayShield);
Arduino_GigaDisplayTouch touchDetector;
GigaDisplay_GFX DisplayX;


#define C_CYAN    0x07FF
#define C_RED     0xf800
#define C_BLUE    0x001F
#define C_GREEN   0x07E0
#define C_MAGENTA 0xF81F
#define C_WHITE   0xffff
#define C_BLACK   0x0000
#define C_YELLOW  0xFFE0

//Modes
#define mainMode 0
#define playMode 1
#define optionsMode 2
#define ntpMode 3 
#define wifiMode 4
#define testMode 5

int mode = mainMode;  // default
boolean mode24 = true;  // 24 hr mode default

//USB config - does not work
USBHostMSD msd;
mbed::FATFileSystem usb("USB");
//mbed::DigitalOut otg(PB_14,0);
const char* melodiesFileName = "/usb/Melodies.txt";

//RTC config
mbed::AnalogIn mcuADCVref(ADC_VREF);
mbed::AnalogIn rtcADCVbat(ADC_VBAT);
uint32_t mcuVref, rtcVbat;
int16_t RTC_CalibrationValue = -200 ;  // Gaining 10 secs per 12 hours

// SD config
const int chipSelect = 4;
String WiFiFileName = "WiFi.txt";
String MelodiesFileName = "Mel.txt";

//definitions
Adafruit_MCP23X17 mcp;

int playBut [] = {0,1,2,3,4,5,6,7};
int playPipe [] = {8,9,10,11,12,13,14,15};
int fanPin = 10;

PinName fanSpeedPin = digitalPinToPinName(D9);   //PWM pin


int freq = 25000;   // for PWM fan
int dutycycle = 50;  // as a percentage - default
int volume = 4;
//int pwmValues[] = {0,60,65,70,75,80,85,90,95,100}; // original
int pwmValues[] = {0,30,35,40,50,60,70,80,90,100};  // volume steps

//  Melody params
const int MAX_MELODIES = 10;
const int MAX_TOKENS = 100;
int numMels;
int     melNumList[MAX_MELODIES];
String  melNameList[MAX_MELODIES];
int     melBeatList[MAX_MELODIES];


String melQ1;
String melQ2;
String melQ3;
String melQ4;
String melH;



int melNum = 1;  //default
String melName;
int melBeat;


//WiFi params
String WiFi_SSID;
String WiFi_PW;
int WiFi_GMT;
int WiFi_ST;

//Time params
int Thour = 0;
int Tmin = 0;
int Tsec = 0;
int Tday = 0;
int Tdate = 0;
int Tmonth = 0;
int Tyear = 0;
int ThourP = 0;
int TminP = 0;
int TsecP = 0;
int TdayP = 0;
int TdateP = 0;
int TmonthP = 0;
int TyearP = 0;

//===========================================Buttons defs
int but1[] = {0, 420, 120, 60};
int but2[] = {340, 420, 120, 60};
int but3[] = {680,420, 120, 60};
int selBut;
int prevButNum = 0;
uint8_t contacts;
int thisX = 0, thisY=0, prevX=0, prevY=0;

bool cbSel [] = {true, false, false, true};
bool rbSel [] = {true, false, false, false};

// =============================================== FixRTC
class FixRTC 
{
public:
    FixRTC() {
        LL_RCC_LSE_Disable();
        LL_RCC_LSE_SetDriveCapability(LL_RCC_LSEDRIVE_HIGH);
        LL_RCC_LSE_Enable();
    }
};

FixRTC fixrtc __attribute__ ((init_priority (101)));

extern RTC_HandleTypeDef RTCHandle;

void LSEcalibration(int16_t LSEcalibrationvalue) 
{
  Serial.println("RTC Calibration");
  if (LSEcalibrationvalue > 0) 
  {
    if (HAL_RTCEx_SetSmoothCalib(&RTCHandle, RTC_SMOOTHCALIB_PERIOD_32SEC, RTC_SMOOTHCALIB_PLUSPULSES_SET, (uint32_t)(512-LSEcalibrationvalue)) != HAL_OK) 
	  {
      Serial.println("Error calibrating RTC on +ve: "+ String(RTC_CalibrationValue));
    }
  }
  else 
  {
    if (HAL_RTCEx_SetSmoothCalib(&RTCHandle, RTC_SMOOTHCALIB_PERIOD_32SEC, RTC_SMOOTHCALIB_PLUSPULSES_RESET, (uint32_t)abs(LSEcalibrationvalue)) != HAL_OK) 
	  {
      Serial.println("Error calibrating RTC on -ve: "+ String(RTC_CalibrationValue));    
    }
  }
}

// ============================================= Setup
void setup() 
{
  // graphics
  
  Image Logo(ENCODING_RGB16, (uint8_t *) Logo2, 558, 400);
  
  Display.begin();
  Display.beginDraw();
  Display.image(Logo, (Display.width() - Logo.width())/2, (Display.height() - Logo.height())/2);
  //Display.drawRGBBitmap(0,0,Logo,Logo.width(),Logo.height());
  Display.endDraw();
  delay(10000);
  Display.beginDraw();
  Display.clear();
  Display.endDraw();
  

  // put your setup code here, to run once:
  Serial.begin(115200);
  delay(2000);
  Serial.println("Starting.....");

  if(!mcp.begin_I2C())
  {
     Serial.println("ERROR starting MCP");
  }
  else
  {
    Serial.println("MCP OK");
  };  // use default I2C address 0x20


  // confgure MCP pins
  for(int i =0; i < 8; i++)
  {
    mcp.pinMode(i,INPUT_PULLUP);
    
  }
  for (int i =8; i < 16; i++)
  {
    mcp.pinMode(i,OUTPUT);
  }

// configure fan pins

setPWM(freq,dutycycle);


// start RTC

//setTime(2025, 10,9,10,22,0, true);
// set RTC resolution
 mcuVref = __LL_ADC_CALC_VREFANALOG_VOLTAGE(mcuADCVref.read_u16(), ADC_RESOLUTION_16B);
 // adjust calibration
 LSEcalibration(RTC_CalibrationValue);
 //getTime();
 RTCvoltage();

  // mount USB
 //loadUSBdata();
 //setup CD card
 initSDcard();


 extractWiFi();
 
 extractMelodyList();

 melName = melNameList[melNum];
 melBeat = melBeatList[melNum];
 
 Serial.println("Selected Melody: "+String(melNum)+":"+melName+" [" +String(melBeat)+"]");
 extractCurrentMelodies(melNum);
 
 
 fanON(true);
 //playMelody(melQ1, melBeat);
 playHour(melH,melBeat,2);
 if( mode != testMode) fanON(false);

 DisplayX.begin();
 if (touchDetector.begin()) 
 {
    Serial.println("Touch controller init - OK");
 } 
  else 
  {
    Serial.println("Touch controller init - FAILED");
    while (1)
      ;
  }


 //mainScreen();
  //timeServerTime();

}


// =======================================================Loop
void loop() 
{
  prevButNum = 0;

  if(mode == mainMode) mainScreen();
  if(mode == playMode) playScreen();
  if(mode == optionsMode) optionsScreen();
  if(mode == ntpMode) timeServerScreen();
  if(mode == wifiMode) WiFiSetup();
  
 // playButton();
 // getTime();
    delay(10);

}


// ========================================= setPWM
void setPWM(int freq, int dutycycle)
{
  int period = 1000000/freq; // uSec
  int pw = period*dutycycle/100; //uSec

  Serial.println("PWM period:" +String(period)+" freq:"+String(freq)+"    DutyCycle:"+String(dutycycle)+"%");
 mbed::PwmOut* pwm = new mbed::PwmOut(fanSpeedPin);
 pwm->period_us(period);
 pwm->pulsewidth_us(pw);
 //mbed::PwmOut pwm(fanSpeedPin);
 //pwm.period(1.0/freq);
 //pwm.write(dutycycle/100.0);
  

}

// ========================================== get Date/Time
void getTime()
{
  tm t;
  char buffer[32];

  _rtc_localtime(time(NULL), &t, RTC_4_YEAR_LEAP_YEAR_SUPPORT);
  strftime(buffer, 32, "%Y-%m-%d %k:%M:%S", &t);
  //Serial.println("Buffer: "+String(buffer));
  TyearP = Tyear;  //save past values
  TmonthP = Tmonth;
  TdateP = Tdate;
  TdayP = Tday;
  ThourP = Thour;
  TminP = Tmin;
  TsecP = Tsec;

  Tyear = t.tm_year +1900;
  Tmonth = t.tm_mon +1;
  Tdate = t.tm_mday;
  Tday = t.tm_wday;
  Thour = t.tm_hour;
  Tmin = t.tm_min;
  Tsec = t.tm_sec;
 // Serial.println("Time: "+String(Tyear)+":"+String(Tmonth)+":"+String(Tdate)+"::"+String(Thour)+":"+String(Tmin)+":"+String(Tsec));
  //Serial.println("Day: "+String(TdayP)+":"+String(Tday));
}

// ===================================setDate/Time
void setTime(int year, int month, int date, int hour, int min, int sec, bool local)
{
  const char* months[] = {"Jan", "Feb", "Mar", "Apr","May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"};
  tm t;
  char s_month[5];
  if(local)
  {
    sscanf(__DATE__,"%s %d %d", s_month, &date, &year);
    sscanf(__TIME__,"%d:%d:%d", &hour, &min, &sec);
    Serial.println("From build: "+String(__DATE__ )+ " :: "+ String(__TIME__));

    Serial.println("Default Time: "+String(year)+":"+String(month)+":"+String(date)+"::"+String(hour)+":"+String(min)+":"+String(sec));

    for(int i=0; i < 12; i++)
    {
      if(strcmp(s_month, months[i]) ==0) month = i+1;
    }
  }
  
    t.tm_sec = (sec);            // 0-59
    t.tm_min = (min);           // 0-59
    t.tm_hour = (hour);          // 0-23
    t.tm_mday = (date);          // 1-31
    t.tm_mon = (month-1);           // 0-11  "0" = Jan, -1
    t.tm_year = year - 1900;
  set_time(mktime(&t));      // set RTC clock
  Serial.println("Time set: "+String(year)+":"+String(month)+":"+String(date)+"::"+String(hour)+":"+String(min)+":"+String(sec));
}

// =  =======================================RTCvolatge
void RTCvoltage()
{
     rtcVbat = __HAL_ADC_CALC_DATA_TO_VOLTAGE(mcuVref, rtcADCVbat.read_u16(), ADC_RESOLUTION_14B);
     Serial.println("RTC Battery: "+String(rtcVbat)+" mV");
}

//============================================ Load USB data
void loadUSBdata()
{
  int count = 0;
  pinMode(PA_15, OUTPUT);
  digitalWrite(PA_15, HIGH);
  delay(2000);
  
  while (!msd.connect() && count < 10)
  {
    Serial.println("Waiting for USB device: "+String(count)+ "  Error: "+String(msd.connect()));
    delay(1000);
    count++;
  }
  if(!msd.connected() )
  {
    Serial.println("Error, msd failed to connect");
    return;
  }
  int err = usb.mount(&msd);
  if(err)
  {
    Serial.println("Error mounting USB");
  }

  FILE *file = fopen(melodiesFileName, "r");
  if (!file) 
  {
    Serial.println("Failed to open file.");
    return;
  }
  Serial.println("File opened: "+String(melodiesFileName));

  fclose(file);
  usb.unmount();
}

// ================================= Extract WiFi data
void extractWiFi()
{
  int numLines = sizeof(mels)/sizeof(mels[0]);
  String tokens[MAX_TOKENS];
  File myFile;
  boolean OK = false;
  String line = "";
  byte c;
  // check for SD stored data
  myFile = SD.open(WiFiFileName);
  if(myFile)
  {
    while (myFile.available()) 
    {
        c = myFile.read();
        line = line +String(char(c));
        Serial.write(c);
    }
      // close the file:
      myFile.close();
      delay(1000);

      int startPos = line.indexOf(":")+1;
      Serial.println("WiFi: "+line.substring(startPos));
      //getTokens(line.substring(startPos), ',', tokens);
      int numTokens = splitString(line.substring(startPos), ',',tokens);
      Serial.println("No of Tokens: "+String(numTokens));
      WiFi_SSID = tokens[0];
      WiFi_PW = tokens[1];
      WiFi_GMT = tokens[2].toInt();
      WiFi_ST = tokens[3].toInt();
      Serial.println("From SD :"+WiFi_SSID +"  " + WiFi_PW+"  " + String(WiFi_GMT)+"  " + String(WiFi_ST));
      OK = true;

  } 
  else 
  {
    // if the file didn't open, print an error:
    Serial.println("Error opening: "+WiFiFileName);
  }

  if(!OK)    // get it from program
  {
    for(int i=0; i < numLines; i++)
    {
      line = String(mels[i]);
      
      if(line.startsWith("WiFi"))
      {
        int startPos = line.indexOf(":")+1;
        Serial.println("WiFi: "+line.substring(startPos));
        //getTokens(line.substring(startPos), ',', tokens);
        int numTokens = splitString(line.substring(startPos), ',',tokens);
        Serial.println("No of Tokens: "+String(numTokens));
        WiFi_SSID = tokens[0];
        WiFi_PW = tokens[1];
        WiFi_GMT = tokens[2].toInt();
        WiFi_ST = tokens[3].toInt();
        Serial.println("From Program :"+WiFi_SSID +"  " + WiFi_PW+"  " + String(WiFi_GMT)+"  " + String(WiFi_ST));

        break;
      }
      
    }
  }
  
  

}
//======================================== Split String
int splitString(String input, char delimiter, String tokens[]) 
{
  int tokenCount = 0;
  int startIndex = 0;
  int delimiterIndex;

  while ( tokenCount < MAX_TOKENS) 
  {
    delimiterIndex = input.indexOf(delimiter, startIndex);
    if (delimiterIndex == -1) 
    {
      // No more delimiters; add the rest of the string
      tokens[tokenCount++] = input.substring(startIndex);
      break;
    }
    else
    {
      tokens[tokenCount++] = input.substring(startIndex, delimiterIndex);
      startIndex = delimiterIndex + 1;
    }
  }
  //Serial.print("Split: "+input+" >> ");
  //for(int i=0; i < tokenCount; i++) Serial.print(tokens[i]+":");
  //Serial.println();
  return tokenCount;
}
//================================= Extract lits of melodies

void extractMelodyList()
{
  int numLines = sizeof(mels)/sizeof(mels[0]);
  String tokens[MAX_TOKENS];
  int numTokens;
  int n = -1;
  numMels = 0;
  boolean OK = false;
  boolean melFound = false;
  File myFile;
  String line = "";
  //boolean  lineComplete = false;
  

  Serial.println("Extracting melodies list from SD");
  myFile = SD.open(MelodiesFileName);
  if(myFile)
  {
      while (myFile.available()) 
      {
        byte c = myFile.read();
        if(c == 13)        // cr = terminate line
        {
           //lineComplete = true;
           melFound = decodeLine(line,n);
           if(melFound) 
           {
              n++;
              //numMels++;
           }
           line = "";
        }
        else if(c == 10)    // ignore
        {     

        }
        else               // add char to line
        {
          line = line +String(char(c));
        }
        
      }
      if(line.length() > 0) 
      {
        melFound = decodeLine(line,n);
        if(melFound) 
        {
              n++;
              //numMels++;
         }
      }

    // close the file:
      myFile.close();
      delay(1000);
      OK = true;
  }
  else
  {
    Serial.println("SD card failed for Melodies List: "+MelodiesFileName);
  }

  if(!OK)
  {
    Serial.println("Extracting melodies list from program...No of Lines: "+String(numLines));

    for(int i=0; i < numLines; i++)
    {
      
      String line = String(mels[i]);
      //Serial.println("i="+String(i)+":"+line);
      
      
      if(line.startsWith("M:"))
      {
        n++;
        numMels++;
        int startPos = line.indexOf(":")+1;
        //Serial.println("M:" +line.substring(startPos));
        //getTokens(line.substring(startPos), ',', tokens);
          numTokens = splitString(line.substring(startPos),',',tokens);
          melNumList[n] = tokens[0].toInt();
          melNameList[n] = tokens[1];
          melBeatList[n]= tokens[2].toInt();
        Serial.print("Melody List:  [numMels="+String(numMels)+"  n="+String(n)+"] ");
        for(int j=0; j < numTokens; j++) Serial.print(String(tokens[j])+",");
        Serial.println();
        
      }
      
    }
  }
  
}

//==================================Decode Line
boolean decodeLine(String line, int n)
{
  String tokens[MAX_TOKENS];
  int numTokens;
  int nn = n;
  boolean found = false;
  
      if(line.startsWith("M:"))
      {
        nn++;
        numMels++;
        int startPos = line.indexOf(":")+1;
        //Serial.println("M:" +line.substring(startPos));
        //getTokens(line.substring(startPos), ',', tokens);
          numTokens = splitString(line.substring(startPos),',',tokens);
          melNumList[nn] = tokens[0].toInt();
          melNameList[nn] = tokens[1];
          melBeatList[nn]= tokens[2].toInt();
          found = true;
        Serial.print("Melody List:  [numMels="+String(numMels)+"  nn="+String(nn)+"] :: "+String(melNumList[nn])+":"+melNameList[nn]+":"+String(melBeatList[nn])+">> ");
        for(int j=0; j < numTokens; j++) Serial.print(String(tokens[j])+",");
        Serial.println();
        
      }
      return found;
}
// ================================ ectract current Melodies

void extractCurrentMelodies(int melNum)
{
  extractMelody(melNum,"Q1");
  extractMelody(melNum,"Q2");
  extractMelody(melNum,"Q3");
  extractMelody(melNum,"Q4");
  extractMelody(melNum,"H");
}

//================================= Extract Chosen Melody
void extractMelody(int n, String cycle)
{
  int numLines = sizeof(mels)/sizeof(mels[0]);
  String tokens[MAX_TOKENS];
  String m = "";
  String ns = String(n);
  int numTokens;
  File myFile;
  boolean OK = false;
  String line = "";

  myFile = SD.open(MelodiesFileName);
  Serial.println("Opening SD card for melody details: File: "+MelodiesFileName+ "> "+String(myFile)+"  n="+String(n)+"  Cycle="+cycle);
  if(myFile)
  {
      while (myFile.available()) 
      {
        byte c = myFile.read();
        if(c == 13)        // cr = terminate line
        {
           
           decodeMelody(line,n, cycle);
           
           line = "";
        }
        else if(c == 10)    // ignore
        {     

        }
        else               // add char to line
        {
          line = line +String(char(c));
        }
        
      }
      if(line.length() > 0) 
      {
        decodeMelody(line,n, cycle);
        
      }

    // close the file:
        myFile.close();
        delay(200);
        OK = true;
  }
  else
  {
    Serial.println("SD card failed for Melody: ["+String(n)+":"+cycle+"] "+MelodiesFileName);
  }

  if(!OK)
  {
    for(int i=0; i < numLines; i++)
    {
      String line = String(mels[i]);
      
      if(line.startsWith("M"+ns+cycle+":"))
      {
        
        int startPos = line.indexOf(":")+1;
        //Serial.println("M"+ns+cycle+":" +line.substring(startPos));
        
        numTokens = splitString(line.substring(startPos), ',', tokens);
        
        for(int j = 0; j < numTokens; j++)
        {
            m = m + tokens[j];
        }
        //Serial.println("Melody "+String(n)+cycle+": "+m);
        
        if(cycle.startsWith("Q1"))
        {
              melQ1 = m;
        } 
        else if(cycle.startsWith("Q2"))
        {
            melQ2 = m;
        } 
        else if(cycle.startsWith("Q3"))
        {
            melQ3 = m;
        } 
        else if(cycle.startsWith("Q4"))
        {
            melQ4 = m;
        }
        else if(cycle.startsWith("H")) 
        {
            melH = m;
        }

      }

    }
  }

}

//================================= Decode line to melody
void decodeMelody(String line, int n, String cycle)
{
  String ns = String(n);
  String m = "";
  int numTokens;
  String tokens[MAX_TOKENS];

      if(line.startsWith("M"+ns+cycle+":"))
      {
        
        int startPos = line.indexOf(":")+1;
        //Serial.println("M"+ns+cycle+":" +line.substring(startPos));
        
        numTokens = splitString(line.substring(startPos), ',', tokens);
        
        for(int j = 0; j < numTokens; j++)
        {
            m = m + tokens[j];
        }
        //Serial.println("Melody "+String(n)+cycle+": "+m);
        
        if(cycle.startsWith("Q1"))
        {
              melQ1 = m;
        } 
        else if(cycle.startsWith("Q2"))
        {
            melQ2 = m;
        } 
        else if(cycle.startsWith("Q3"))
        {
            melQ3 = m;
        } 
        else if(cycle.startsWith("Q4"))
        {
            melQ4 = m;
        }
        else if(cycle.startsWith("H")) 
        {
            melH = m;
        }

      }
}


//=============================== Play melody
void playMelody(String melody, int beat)
{
   int n = melody.length();
   int note;
   delay(5000);



   Serial.print("Playing: "+melName+" ["+String(n)+"] ");
   for (int i=0; i < n; i++)
   {
      note = melody.charAt(i) -'0';
      Serial.print(String(note) +" ");
      mcp.digitalWrite(15-note, HIGH);
      delay(beat);
      mcp.digitalWrite(15-note,LOW);
   }
   Serial.println();
}
//================================== Play Hour Chime
void playHour(String melody, int beat, int hour)
{
   int n = melody.length();
   int note;
   delay(1000);
   int repeat = hour;
   if(repeat > 12) repeat = repeat - 12;
   Serial.print("Playing Hour: ");
   for(int j=0; j < repeat; j++)
   {
    for (int i=0; i < n; i++)
    {
        note = melody.charAt(i)-'0';
        Serial.print(String(note) +" ");
        mcp.digitalWrite(15-note, HIGH);
        delay(beat);
        mcp.digitalWrite(15-note,LOW);
        delay(beat);
    }
   }
   Serial.println();
}

unsigned long rgbToHex(byte r, byte g, byte b)
{
  //unsigned long col = ((unsigned long)r << 16) | ((unsigned long)g << 8) | b;
  unsigned long red5 = (r >>3) & 0x1F;
  unsigned long green6 = (g >> 2) & 0x3F;
  unsigned long blue5 = (b >> 3) & 0x1F;
  unsigned long col = (red5 << 11) | (green6 << 5) | blue5;

 // Serial.print("Colour r="+String(r)+" g="+String(g)+" b="+String(b)+ "hex: ");
 // Serial.println(col, HEX);
  return col;
}

void initSDcard()
{
  if(!SD.begin(chipSelect))
  {
      Serial.println("SD card initialization failed");
  }
  else
  {
      Serial.println("SD card OK");
  }
}

