// used to compile            
//      g++ -o robotank robotank.cpp `mysql_config --cflags --libs` -lpthread -lquickmail -lcurl
//      g++ -o robotank robotank.cpp `mysql_config --cflags --libs` -lwiringPi -lpthread -lquickmail -lcurl -ljsoncpp

// used to manage service
//      sudo systemctl enable robotank.service
//      sudo systemctl disable robotank.service
//      sudo systemctl start robotank.service
//      sudo systemctl stop robotank.service
//      sudo journalctl -fu robotank.service

// install valgrind
//      sudo apt install snapd -y
//      sudo reboot
//      sudo snap install core
//      sudo snap install valgrind --classic

// used to test memory - total bytes allocated shows total allocated and deleted while running
//      valgrind --track-origins=yes ./robotank
//      valgrind --leak-check=full ./robotank

// check Pi temperature
//      vcgencmd measure_temp

// sudo wget -c https://www.robo-tank.ca/app/releases/Robo-Tank_v6_6.zip &> /dev/null && sudo unzip -o Robo-Tank_v6_6.zip -d /boot/ &> /dev/null && sudo bash /boot/robotank/install_robotank.sh

// sudo wget -c https://app.robo-tank.ca/public/releases/Robo-Tank_v6_6.zip &> /dev/null && sudo unzip -o Robo-Tank_v6_6.zip -d /boot/ &> /dev/null && sudo bash /boot/robotank/install_robotank.sh


unsigned long long currentMillisOffset = 0;
unsigned long long currentMillisTime = 0;
unsigned long long currentMillisTracker = 0;
int health = 0;
unsigned long long prevHealthMillis = 0;
int healthCount = 0;
unsigned long long prevMillisBlink = 0;
unsigned long long prevUpdateMillis = 0;
bool startupComplete = 0;
unsigned long long midnightMillis = 0;
unsigned long long nextMidnightMillis = 0;
float sleepTimeMainLoop = 1;
unsigned long long programStartMillis = 0;

unsigned long long supportID = 0;

bool testApp = 0;

auto startTimer = 0;
auto TotalTimer = 0;


#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <signal.h>

#include <cctype>
#include <random>
// c++ libraries
#include <stdio.h>      /* printf */
#include <math.h>       /* floor */
#include <stdlib.h>
#include <iomanip>      // std::setw
#include <unistd.h>
#include <fcntl.h>
#include <dirent.h>
#include <cstring>
#include <string.h>
#include <sstream>
#include <ctime>
#include <time.h>
#include <chrono>
#include <iostream>
#include <fstream>
// #include "jsoncpp/json/json.h"
// #include <wiringPi.h>
#include <sys/ioctl.h> // required for I2C device usage
#include <linux/i2c-dev.h> // required for constant definitions
#include <cstdlib>
// #include <thread>
#include <pthread.h>  // multithreading
#include <curl/curl.h>
#include <quickmail.h>		// quickmail email library
#include <vector>  // for decoding
#include </usr/include/mariadb/mysql.h>

// used to generate milliseconds
using Clock = std::chrono::steady_clock;
using std::chrono::time_point;
using std::chrono::duration_cast;
using std::chrono::milliseconds;
using namespace std::literals::chrono_literals;
time_point<Clock> startMillis;  

// program files
size_t len = 0;
std::string outgoingDataString = "";
#include "mysql.cpp"
#include "gpio.cpp"
// #include "logs.h"
#include "rtc.h"
#include "update.h"
#include "email.h"
#include "system.h"
#include "pca9685.h"
#include "i2c.h"
#include "probes.h"
#include "outlets.h"
#include "dcPorts.h"
#include "sensors.h"
#include "ds18b20.h"
// #include "dht22.h"
#include "lights.h"
#include "dosing.h"
#include "schedules.h"
#include "rules.h"
#include "controller.h"
#include "socket.cpp"
// #include "TEST.cpp"


using namespace std;

void *socketLoop(void *threadid) {  // loop for socket to communicate with php
    while(1)
    {
        // sleep(1);
	// std::cout <<"Hello World22222" << endl;
        checkCommunications();
    }
}
void *mainDS18B20Loop(void *threadid) {  // loop to check ds18b20 and pH circuits - thread 1
    while(1)
    {
        if (startupComplete == 1) 
        {
            ds18b20.checkTemps();
            ds18b20.saveDS18B20Charts();
            ds18b20.checkDHT22();
            probes.checkProbes();
            probes.saveProbeCharts();
	// std::cout <<"Hello World333333333" << endl;
            
            // if (testApp == 0) {
            //     i2c.scanI2Cbus();
            // }
        }
        sleep(1);
    }
}
void *mainLoop(void *threadid) {  // main program loop - thread 0
    while(1)
    {
        if (startupComplete == 1) 
        {
            rtc.getDateTime();
            // lights.monitorLightSchedules();
	        if (testApp == 0) {lights.monitorLightSchedules();}
            schedules.monitorSchedules();
            dosing.monitorDosingPumps();
            sensors.readSensors();
            rules.monitorRules();
        }

        if (currentMillisTime >= (prevHealthMillis + 1000)) 
        {
            health = healthCount;
            healthCount = 0;
            // Jul 10, 2021 = 195 Health @ 5000 usleep
            // Sept 8, 2021 = 195 Health @ 5000 usleep
            // Sept 8, 2021 = 52705 Health without delay
            // March 8, 2022 = 195 Health @ 5000 usleep - Pi4
            // March 8, 2022 = 186 Health @ 5000 usleep - Pi3
            // std::cout << "health = " << health << endl;
            prevHealthMillis = currentMillisTime;
        }

        
        if (currentMillisTime >= (programStartMillis + 5000))  // wait 20 seconds after program starts to monitor custom rules
        {
            if (currentMillisTime >= (prevUpdateMillis + 3600000))
            // if (currentMillisTime >= (prevUpdateMillis + 10000))
            {
                if (testApp == 0) {update.checkForUpdate(0);}
                // update.checkForUpdate(0);
                prevUpdateMillis = currentMillisTime;
            }
        }
        healthCount++;
	// std::cout <<"Hello World444444444444444444" << endl;
        usleep(5000);
    }
}
pthread_t threads[3] = {};   // define number of threads to run

void startThreads(){  // setup multiple threads
    int rc = 0;
    int i = 0;
   
    // create thread 1 for main loop
    // std::cout << "main() : creating thread, " << 0 << endl;
    rc = pthread_create(&threads[0], NULL, mainLoop, (void *)0);

    if (rc) {
        // std::cout << "Error:unable to create thread," << rc << endl;
        exit(-1);
    }

    // create thread for ds18b20 and pH circuits
    // std::cout << "main() : creating thread, " << 1 << endl;
    rc = pthread_create(&threads[1], NULL, mainDS18B20Loop, (void *)1);

    if (rc) {
        // std::cout << "Error:unable to create thread," << rc << endl;
        exit(-1);
    }

    // create thread for socket to communicate with php
    // std::cout << "main() : creating thread, " << 2 << endl;
    rc = pthread_create(&threads[2], NULL, socketLoop, (void *)2);

    if (rc) {
        // std::cout << "Error:unable to create thread," << rc << endl;
        exit(-1);
    }
}

void setup()
{
	std::cout <<"Hello World" << endl;

    // startMillis = Clock::now();  // startup millisecond counter
    // initiate wiringPi library
	// if(wiringPiSetup()<0){
	// 	// std::cout << "setup wiring pi failed"<<endl;
	// }
    // initialiseEpoch();

    // listen to SIGINT, SIGTERM, and SIGKILL
    signal(SIGINT, signal_callback_handler);
    signal(SIGTERM, signal_callback_handler);
    signal(SIGKILL, signal_callback_handler);

    // setup socket address structure
    memset(&server_addr, 0, sizeof(server_addr));
    server_addr.sun_family = AF_UNIX;
    strcpy(server_addr.sun_path, SOCKET_FILENAME);

    server = socket(PF_UNIX, SOCK_STREAM, 0);  
    close(server);  // close stream if open
    unlink(SOCKET_FILENAME);
    // bind and listen on the socket file
    bind_listen_socket(server, server_addr);

    // allocate buffer
    socketBuflen = 1024;
    // socketBuf = new char[socketBuflen+1];

    startThreads();  // setup multiple threads

    startupComplete = 0;
    if (testApp == 0) {
        i2c.startupI2C();  // setup I2C devices
        rtc.startupRTC();
        i2c.scanI2Cbus();
    }
    
    std::cout << "-currentMillisTime  - " << currentMillisTime << std::endl;
    MYSQL_RES *res;
    MYSQL_ROW row;
    
    short pinID = 0;    
    std::string query = "SELECT pinID FROM pinsAvailable WHERE driverID = 2 AND gpio = 5";
            
    connectMysql();
    mysql_query(conn, query.c_str());  // run query
    res = mysql_use_result(conn);

    while ((row = mysql_fetch_row(res)) != NULL)
    {
        pinID = atoi(row[0]);
    }
    mysql_free_result(res);
    mysql_disconnect();

    systemSettings.logActivity(1,6,pinID,2,"");  // log action

    connectMysql();
    query = "SELECT tempCorF, outletSortID, dcSortID, supportID, updateRan FROM systemSettings WHERE userID = 1";  // check if temperature should be celcuis or fehrenheit
    mysql_query(conn, query.c_str());  // run query
    res = mysql_use_result(conn);

    while ((row = mysql_fetch_row(res)) != NULL)
    {
        ds18b20.updateTempCorF(atoi(row[0]));  // get temperature format
        // dht22.updateTempCorF(atoi(row[0]));  // get temperature format
        outlets.changeOutletSortID(atoi(row[1]));  // get outlet sortID
        dcPorts.changeDcSortID(atoi(row[2]));  // get DC port sortID
        supportID = atoi(row[3]);  // get supportID
        update.updateUpdateAvailable(atoi(row[4]));
    }
    mysql_free_result(res);
    mysql_disconnect(); 

    controller.startupController();
    update.checkIfUpdating();
    
    if (testApp == 0) {
        emails.startupEmails();
        emails.sendEmail("Robo-Tank was Restarted","Message from Robo-Tank Aquarium Controller");
    }
    if (testApp == 0) {pca9685.startupPCA9685();}
    // pca9685.startupPCA9685();
    outlets.startupACoutlets();
    dcPorts.startupDCports();
    dosing.startupDosing();
    ds18b20.startupDHT22();
    sensors.startupSensors();
    probes.startupProbes();

    // setPWMFreq(50);
    lights.readLightModes();
    schedules.readSchedules();
    rules.startupRules();

    // create txt file for front end to find so it knows we c++ program is running again
    ofstream MyFile("/var/www/html/reconnected.txt");
    MyFile.close();

    if (supportID == 0)  // if supportID isn't valid generate one
    {
        using u32    = uint_least32_t; 
        using engine = std::mt19937;

        std::random_device os_seed;
        const u32 seed = os_seed();

        engine generator(seed);
        std::uniform_int_distribution< u32 > distribute(1000000, 1316134911);

        supportID = distribute(generator);
        
        connectMysql();
        std::string query = "UPDATE systemSettings SET supportID = '" + to_string(supportID) + "' WHERE userID = 1";
        mysql_query(conn, query.c_str()); // run query
        mysql_disconnect(); 

        // std::cout << "supportID = " << supportID << std::endl;
    }

    startupComplete = 1;
    
    std::cout << "supportID           - " << supportID << std::endl;
    std::cout << "startupComplete     - " << startupComplete << std::endl;
    
    std::cout << "startupComplete     - " << millis() << std::endl;
    std::cout << "-currentMillisTime  - " << currentMillisTime << std::endl;
    std::cout << "" << std::endl;
    std::cout << "" << std::endl;
    std::cout << "" << std::endl;
}

int main(void)//(int argc, char **argv)
{
	setup();  // main setup function
	while(1){
        // use this to send emails so other threads can't pause if email timesout for 30 seconds
	// std::cout <<"Hello World" << endl;
        sleep(1);
	}
}

