using namespace std;


class rtc
{
    public:
        rtc();
		void startupRTC();  // run at startup
        unsigned long long tmConvert_t(int YY, short MM, short DD, short hh, short mm, short ss);  // convert time/date to millis
        uint8_t decToBcd(uint8_t val);  // Convert normal decimal numbers to binary coded decimal
        uint8_t bcdToDec(uint8_t val);  // Convert binary coded decimal to normal decimal numbers
        void saveNewDateTime(short second, short minute, short hour, short dayOfWeek, short dayOfMonth, short month, short year);  // set ds1307 and ds3231 time/date
        void getDateTime();  // get date and time
        void updateTime();  // update next day and midnight millis
        void updateTimeFormat(short format);  // update time format from frontend
        short getCurrentDay();  // get currentDay
        short getCurrentMonth();  // get currentMonth
        short getCurrentYear();  // get currentYear
        short getCurrentWeekday();  // get currentWeekday
    private:
		int rtcI2Cfile = 0;  // used when reading and writing to I2C bus
        uint8_t _bcd2dec(uint8_t bcd);
        short timeFormat = 0;  // 24hr or 12hr
        short second, minute, hour, dayOfWeek, dayOfMonth, month, year;  // used when processing data from rtc
        short nextCurrentDay = 0;
        short nextCurrentMonth = 0;
        short nextCurrentYear = 0;
        short currentDay = 0;
        short currentMonth = 0;
        short currentYear = 0;
        short currentWeekday = 0;
        struct tm result;  // used to store date/time values from network
        time_t t = time(NULL);  // used to store date/time values from network
};
rtc::rtc(){}

short rtc::getCurrentDay() {return currentDay;}  // get currentDay
short rtc::getCurrentMonth() {return currentMonth;}  // get currentMonth - month is 0-11 so we add one to get month 1-12 or Jan - Dec
short rtc::getCurrentYear() {return currentYear;}  // get currentYear
short rtc::getCurrentWeekday() {return currentWeekday;}  // get currentWeekday

unsigned long long rtc::tmConvert_t(int YY, short MM, short DD, short hh, short mm, short ss)  // convert time/date to millis
{
    unsigned long long convertedMillis = 0;
    result.tm_sec = ss;
    result.tm_min = mm;
    result.tm_hour = hh;
    result.tm_year = YY - 1900;
    result.tm_mon = MM;
    result.tm_mday = DD;
    convertedMillis = std::mktime(&result);
    convertedMillis = convertedMillis * 1000;
    return convertedMillis; 
}

uint8_t rtc::decToBcd(uint8_t val)  // Convert normal decimal numbers to binary coded decimal
{
  return ( (val/10*16) + (val%10) );
}

uint8_t rtc::bcdToDec(uint8_t val)  // Convert binary coded decimal to normal decimal numbers
{
  return ( (val/16*10) + (val%16) );
}

void rtc::saveNewDateTime(short second, short minute, short hour, short dayOfWeek, short dayOfMonth, short month, short year)  // set ds1307 and ds3231 time/date
{
	char buffer[8];
	buffer[0] = 0x00;  // target register
	buffer[1] = decToBcd(second);
	buffer[2] = decToBcd(minute);
	buffer[3] = decToBcd(hour);
	buffer[4] = decToBcd(dayOfWeek);
	buffer[5] = decToBcd(dayOfMonth);
	buffer[6] = decToBcd(month);
	buffer[7] = decToBcd(year);

	int length = 8;       // number of bytes, including address
	write(rtcI2Cfile, buffer, length); // initiate write
    delay(100);
    getDateTime();
}

void rtc::updateTime()  // update next day and midnight millis
{    
    midnightMillis = tmConvert_t(currentYear,currentMonth,currentDay,0,0,00);

    // used to calculate next midnight millis
    nextCurrentDay = currentDay + 1;
    nextCurrentMonth = currentMonth;
    nextCurrentYear = currentYear;

    // if month has 31 days change next day and month to next month
    if (((currentMonth == 0) || (currentMonth == 2) || (currentMonth == 4) || (currentMonth == 6) || (currentMonth == 7) || (currentMonth == 9) || (currentMonth == 11)) && (nextCurrentDay == 32))
    {
        nextCurrentDay = 1;
        nextCurrentMonth = nextCurrentMonth + 1;
    }

    // if month has 30 days change next day and month to next month
    if (((currentMonth == 3) || (currentMonth == 5) || (currentMonth == 8) || (currentMonth == 10)) && (nextCurrentDay == 31))  // 30 days
    {
        nextCurrentDay = 1;
        nextCurrentMonth = nextCurrentMonth + 1;
    }

    // if leap year allow February to have 29 days
    if (((currentYear == 2024) || (currentYear == 2028) || (currentYear == 2032) || (currentYear == 2036)) && (currentMonth == 1) && (nextCurrentDay == 30))  // 29 days - leap years up to 2039
    {
        nextCurrentDay = 1;
        nextCurrentMonth = nextCurrentMonth + 1;
    }
    else if ((currentMonth == 1) && (nextCurrentDay == 29))  // go to next month if not a leap year
    {
        nextCurrentDay = 1;
        nextCurrentMonth = nextCurrentMonth + 1;
    }

    // if last day of year go to next year
    if (nextCurrentMonth == 12) {nextCurrentMonth = 0; nextCurrentYear = nextCurrentYear + 1;}  // end of year

    // get next midnight milliseconds based on above
    nextMidnightMillis = tmConvert_t(nextCurrentYear,nextCurrentMonth,nextCurrentDay,0,0,00);

    // std::cout << "currentMillisTime - " << currentMillisTime << std::endl;

    // std::cout << "-midnightMillis     - " << midnightMillis << std::endl;
    // std::cout << "-nextMidnightMillis - " << nextMidnightMillis << std::endl;
    // std::cout << "-currentDay           - " << currentDay << " - " << dayOfMonth << std::endl;
    // std::cout << "-currentMonth         - " << currentMonth << " - " << month-1 << std::endl;
    // std::cout << "-currentYear          - " << currentYear << " - " << year+2000 << std::endl;
    // std::cout << "-currentWeekday       - " << currentWeekday << " - " << dayOfWeek-1 << std::endl;
    // std::cout << "-nextCurrentDay       - " << nextCurrentDay << std::endl;
    // std::cout << "-nextCurrentMonth     - " << nextCurrentMonth << std::endl;
    // std::cout << "-nextCurrentYear      - " << nextCurrentYear << std::endl;
    // std::cout << "-current Time            - " << currentHour << ":" << currentMinute << ":" << currentSecond << std::endl;
}

void rtc::getDateTime()  // get date and time
{
    currentMillisTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    localtime_r(&t, &result);
    currentDay = result.tm_mday;  // 1-31
    currentMonth = result.tm_mon;  // 0 = January
    currentYear = result.tm_year + 1900;
    // std::cout << "getDateTime currentMillisTime - " << currentMillisTime << std::endl;
    currentWeekday = result.tm_wday;  // 0 = Sunday
    updateTime();
}

void rtc::updateTimeFormat(short format)  // update time format from frontend
{
    timeFormat = format;
    connectMysql();
    std::string query = "UPDATE systemSettings SET timeFormat = '" + to_string(timeFormat) + "'";
    mysql_query(conn, query.c_str());  // run query
    mysql_disconnect(); 
}

void rtc::startupRTC()  // run at startup
{
    std::cout << " " << std::endl;
    std::cout << "startupRTC --------------------------------" << std::endl;

    MYSQL_RES *res;
    MYSQL_ROW row;

    // check if rtc is connected
    connectMysql();
    std::string query = "SELECT timeFormat FROM systemSettings WHERE userID = 1";  // get rtc type being used and time format
    mysql_query(conn, query.c_str());  // run query
    res = mysql_use_result(conn);
    
    while ((row = mysql_fetch_row(res)) != NULL)
    {
        timeFormat = atoi(row[0]);  // get time format
    }
    mysql_free_result(res);
    mysql_disconnect();

    std::cout << "timeFormat       = " << timeFormat << std::endl;

    currentMillisTime = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::system_clock::now().time_since_epoch()).count();
    localtime_r(&t, &result);
    currentDay = result.tm_mday;  // 1-31
    currentMonth = result.tm_mon;  // 0 = January
    currentYear = result.tm_year + 1900;
    currentWeekday = result.tm_wday;  // 0 = Sunday
	std::cout << "" << std::endl;

    getDateTime();
    updateTime();
    programStartMillis = currentMillisTime;
    prevMillisBlink = currentMillisTime;
    prevHealthMillis = currentMillisTime;

    std::cout << "-currentMillisTime  - " << currentMillisTime << std::endl;
    std::cout << "-midnightMillis     - " << midnightMillis << std::endl;
    std::cout << "-nextMidnightMillis - " << nextMidnightMillis << std::endl;
    std::cout << "currentWeekday      - " << currentWeekday << endl;
}

rtc rtc;