
using namespace std;

// #define SOCKET_FILENAME "/tmp/server.sock"
#define SOCKET_FILENAME "/var/www/html/server.sock"
int server = 0;
struct sockaddr_un server_addr, client_addr;
socklen_t clientlen = sizeof(client_addr);
int socketClient = 0, socketBuflen = 0, socketNread = 0;
// char *socketBuf;
char socketBuf[100] = {};


void bind_listen_socket(int &server, sockaddr_un &server_addr)
{
    // create socket
    server = socket(PF_UNIX, SOCK_STREAM, 0);
    // close(server);
    // unlink(SOCKET_FILENAME);
    if (!server) {
        perror("socket");
        exit(-1);
    }

    // call bind to associate the socket with our local address and
    // port
    if (bind(server, (const struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {
        perror("bind");
        exit(-1);
    }

    // convert the socket to listen for incoming connections
    if (listen(server, 0) < 0) {
        perror("listen");
        exit(-1);
    }

    system("chmod 777 /var/www/html/server.sock");
    // chmod("/var/www/html/server.sock", S_IRWXU); // enables owner to rwx file
    // puts("Listening to connection...");
    // std::cout << "Listening to connection..." << std::endl;

}
void signal_callback_handler(int signum)
{
    // std::cout << "signal_callback_handler..." << std::endl;
    // close server
    close(server);
    // remove the socket file
    unlink(SOCKET_FILENAME);
    // signal handled
    exit(0);
}
void checkCommunications()
{
    // TotalTimer = millis() - startTimer;
    // std::cout << "checkCommunications - " << TotalTimer << std::endl;
    unsigned int socketClient = accept(server, (struct sockaddr *)&client_addr, &clientlen);

    // got a request, close the socket
    close(server);
    unlink(SOCKET_FILENAME);

    // read a request
    std::fill_n(socketBuf, socketBuflen, 0);  // reset array
    socketNread = recv(socketClient, socketBuf, socketBuflen, 0);  // incoming data is captured and sent back as an acknowledgement 

    // startTimer = millis();

    int incomingCommand[200] = {};
    char * pch = {};
    char * incomingText[200] = {};
    char * incomingText2 = 0;
    short outgoingData = 0;

    pch = strtok(socketBuf,",");
    std::fill_n(incomingCommand, 200, 0);  // reset array
    short i = 0;
    while (pch != NULL)
    {
        incomingText[i] = pch;
        incomingCommand[i] = atoi(pch);
        // if (i == 4) {incomingText = pch;}
        // else {incomingCommand[i] = atoi(pch);}
        pch = strtok(NULL, ",");
        // pch = strtok(NULL, " ,.-");
        i++;
    }
    
    if ((incomingCommand[0] != 997) && (incomingCommand[0] != 998))
    {
        std::cout << "incomingCommand = ";
        for (short a=0; a < i; a++)  // get temps
        {
            // std::cout << incomingCommand[a] << ", ";
            std::cout << incomingText[a] << ", ";
        }
        // std::cout << incomingText << ", ";
        // std::cout << incomingText2 << ", ";
        std::cout << " " << endl;
    }

    switch (incomingCommand[0])
    {
        case 1:
            lights.adjustLightLevel(incomingCommand[1],incomingCommand[2]);  // adjust light mode brightness - modeID, level
        break;

        case 2:
            lights.lightLevelSliderStopped(incomingCommand[1],incomingCommand[2]);  // save light mode brightness - modeID, level
        break;

        case 11:
            lights.changeTime(incomingCommand[1],incomingCommand[2]);  // change light mode time - modeID, time
            lights.frontendResponse();

            std::fill_n(socketBuf, socketBuflen, 0);  // reset array
            strcpy(socketBuf, outgoingDataString.c_str());
            len = strlen(socketBuf);
            send(socketClient, socketBuf, len, 0);
            outgoingData = 1;
        break;

        case 15:
            // lights.resumeLightMode();  // resume to current light mode - modeNumber
        break;

        case 16:
        break;

        case 6:
            lights.deleteLightMode(incomingCommand[1]);  // delete light mode - modeID
            lights.frontendResponse();

            std::fill_n(socketBuf, socketBuflen, 0);  // reset array
            strcpy(socketBuf, outgoingDataString.c_str());
            len = strlen(socketBuf);
            send(socketClient, socketBuf, len, 0);
            outgoingData = 1;
        break;

        case 7:
        
            lights.addLightMode(incomingCommand[1]);  // add light mode - pinID
            lights.frontendResponse();

            std::fill_n(socketBuf, socketBuflen, 0);  // reset array
            strcpy(socketBuf, outgoingDataString.c_str());
            len = strlen(socketBuf);
            send(socketClient, socketBuf, len, 0);
            outgoingData = 1;
            // lights.addLightMode(incomingCommand[1],incomingCommand[2],incomingCommand[3],incomingText[4],incomingCommand[5],incomingCommand[6],incomingCommand[7],incomingCommand[8],incomingCommand[9],incomingCommand[10],incomingCommand[11]);  // add light mode - modeNumber, start, end, name
            // addLightMode(incomingCommand[1],incomingCommand[2],incomingCommand[3],incomingText);
        break;

        case 10:
            lights.enableLightMode(incomingCommand[1],incomingCommand[2]);  // enable/disable light mode - pinID, enabled
        break;

        case 3:
            ds18b20.changeTempCorF(incomingCommand[1]);  // change temp C or F
        break;

        case 4:
            ds18b20.deleteDS18B20(incomingCommand[1]);  // delete ds18b20 - ID
        break;

        case 12:
            ds18b20.resetDS18B20Min(incomingText[1]);  // reset ds18b20 min temp - address
        break;

        case 13:
            ds18b20.resetDS18B20Max(incomingText[1]);  // reset ds18b20 max temp - address
        break;

        case 14:
            ds18b20.saveDS18B20Name(incomingText[1],incomingText[2]);  // update ds18b20 name - address, name
        break;

        case 5:
            rtc.updateTimeFormat(incomingCommand[1]);  // time format 0 = 24hr, 1 = 12hr
        break;

        case 8:
            controller.updateRoboTankVersion(incomingCommand[1]);  // update Robo-Tank version - value
        break;

        case 30:
            probes.resetProbeMin(incomingCommand[1]);  // reset probe min value - address
        break;

        case 31:
            probes.resetProbeMax(incomingCommand[1]);  // reset probe max value - address
        break;

        case 32:
            probes.saveProbeName(incomingText[1],incomingText[2],incomingCommand[3]);  // update probe name - address, name
        break;

        case 33:
            // saveProbeName(incomingText[1],incomingText[2]);  // update probe name - address, name
            probes.startupProbes();  // new probe was added
        break;

        case 34:
            probes.startCalibration(incomingCommand[1],incomingCommand[2],incomingText[3]);  // start calibration - address, calType, buffer
        break;

        // case 35:
        //     probes.startCalibration(incomingCommand[1],incomingCommand[2]);  // delete calibration - address, calRunning
        // break;

        case 37:
            probes.deleteCalibration(incomingCommand[1]);  // delete calibration - address
        break;

        case 40:
            i2c.startupI2C();  // new pca9685 was added
            pca9685.startupPCA9685();
            controller.reconfigPorts();
        break;

        case 50:
            outlets.manuallySwitchOutlet(incomingCommand[1]);  // switch outlet - pinID
            outlets.outletResponse(incomingCommand[1]);

            std::fill_n(socketBuf, socketBuflen, 0);  // reset array
            strcpy(socketBuf, outgoingDataString.c_str());
            len = strlen(socketBuf);
            send(socketClient, socketBuf, len, 0);
            outgoingData = 1;
        break;

        case 51:
            outlets.resumeAutoMode(incomingCommand[1]);  // resume outlet to auto mode - pinID
        break;

        // case 51:
        //     outlets.sortOutletWidget(incomingCommand[1]);  // sort AC outlets - outletSortID
        // break;

        case 60:
            dcPorts.manuallySwitchDC(incomingCommand[1]);  // switch DC port - pinID            
            dcPorts.dcResponse(incomingCommand[1]);

            std::fill_n(socketBuf, socketBuflen, 0);  // reset array
            strcpy(socketBuf, outgoingDataString.c_str());
            len = strlen(socketBuf);
            send(socketClient, socketBuf, len, 0);
            outgoingData = 1;
        break;

        case 61:
            dcPorts.resumeAutoMode(incomingCommand[1]);  // resume DC ports to auto mode - pinID
        break;

        // case 61:
        //     dcPorts.sortDcWidget(incomingCommand[1]);  // sort DC ports - dcSortID
        // break;

        case 70:
            schedules.deleteSchedule(incomingCommand[1]);  // delete schedule - scheduleID
            schedules.frontendResponse();

            std::fill_n(socketBuf, socketBuflen, 0);  // reset array
            strcpy(socketBuf, outgoingDataString.c_str());
            len = strlen(socketBuf);
            send(socketClient, socketBuf, len, 0);
            outgoingData = 1;
        break;

        case 71:
            schedules.saveSchedule(incomingCommand[1],incomingCommand[2],incomingText[3],incomingCommand[4],incomingCommand[5],incomingCommand[6],incomingCommand[7],incomingCommand[8],incomingCommand[9],incomingCommand[10],incomingCommand[11],incomingCommand[12],incomingCommand[13],incomingCommand[14],incomingCommand[15],incomingText[16]);  // save edited schedule - modeNumber, start, end, name
            schedules.frontendResponse();

            std::fill_n(socketBuf, socketBuflen, 0);  // reset array
            strcpy(socketBuf, outgoingDataString.c_str());
            len = strlen(socketBuf);
            send(socketClient, socketBuf, len, 0);
            outgoingData = 1;
        break;

        case 72:
            schedules.addSchedule(0,incomingCommand[1],incomingCommand[2],incomingText[3],incomingCommand[4],incomingCommand[5],incomingCommand[6],incomingCommand[7],incomingCommand[8],incomingCommand[9],incomingCommand[10],incomingCommand[11],incomingCommand[12],incomingCommand[13],incomingCommand[14],incomingCommand[15],incomingText[16]);  // add light mode - modeNumber, start, end, name
            schedules.frontendResponse();

            std::fill_n(socketBuf, socketBuflen, 0);  // reset array
            strcpy(socketBuf, outgoingDataString.c_str());
            len = strlen(socketBuf);
            send(socketClient, socketBuf, len, 0);
            outgoingData = 1;
        break;

        case 73:
            schedules.enableSchedule(incomingCommand[1],incomingCommand[2]);  // enable schedule - scheduleID
        break;

        case 80:
            dosing.manualDose(incomingCommand[1],incomingText[2]);  // manually run dosing pump - pinID
        break;

        case 81:
            dosing.saveDisableRes(incomingCommand[1],incomingCommand[2]);  // save disable reservoir setting - pinID, setting
        break;

        case 82:
            dosing.saveReservoirSize(incomingCommand[1],incomingCommand[2]);  // save reservoir size - pinID, doseReservoirSize
        break;

        case 83:
            dosing.savePumpRate(incomingCommand[1],incomingCommand[2]);  // save pump rate - pinID, dosePumpRate
        break;

        case 84:
            dosing.saveStirrer(incomingCommand[1],incomingCommand[2]);  // save stirrer - pinID, value
        break;

        case 85:
            dosing.saveCalibrationAmount(incomingCommand[1],incomingCommand[2]);  // save calibration amount - pinID, doseCalibrateAmount
        break;

        case 86:
            dosing.refillReservoir(incomingCommand[1]);  // save calibration amount - pinID
        break;

        case 87:
            dosing.manualDoseCalibrate(incomingCommand[1]);  // save calibration amount - pinID
        break;

        case 88:
            dosing.doseCalibrationDone(incomingCommand[1]);  // calibration finished - pinID
        break;

        case 89:
            dosing.saveDoseInterval(incomingCommand[1],incomingCommand[2]);  // save minimum interval between doses - pinID, doseInterval
        break;

        case 90:
            dosing.moveDoseSpeedSlider(incomingCommand[1],incomingCommand[2]);  // adjust dosing pump speed - pinID, pumpSpeed
        break;

        case 91:
            dosing.stopDoseSpeedSlider(incomingCommand[1],incomingCommand[2]);  // save dosing pump speed - pinID, pumpSpeed
        break;

        case 92:
            for(short a=0; a < 17; a++ )  // download and extract all updates to be installed
            {
                schedules.addDoseGroup(incomingText[a+1]);
            }
            schedules.frontendResponse();

            std::fill_n(socketBuf, socketBuflen, 0);  // reset array
            strcpy(socketBuf, outgoingDataString.c_str());
            len = strlen(socketBuf);
            send(socketClient, socketBuf, len, 0);
            outgoingData = 1;
        break;

        case 93:
            dosing.saveResEmail(incomingCommand[1],incomingCommand[2]);  // save reservoir email alert - pinID, enable
        break;

        case 94:
            dosing.saveResAlert(incomingCommand[1],incomingCommand[2]);  // save reservoir dashboard alert - pinID, enable
        break;

        case 95:
            dosing.saveResAlertML(incomingCommand[1],incomingCommand[2]);  // save reservoir dashboard alert - pinID, value
        break;

        case 96:
            dosing.saveStirrerRuntime(incomingCommand[1],incomingCommand[2]);  // save stirrer runtime - pinID, value
        break;

        case 97:
            dosing.saveSettlingRuntime(incomingCommand[1],incomingCommand[2]);  // save settling runtime - pinID, value
        break;

        case 98:
            dosing.saveStirrerPort(incomingCommand[1],incomingCommand[2]);  // save settling runtime - stirrerID, dosePinID
        break;

        case 99:
            dosing.moveStirrerSpeedSlider(incomingCommand[1],incomingCommand[2]);  // adjust stirrer speed - pinID, stirrerSpeed
        break;

        case 100:
            dosing.stopStirrerSpeedSlider(incomingCommand[1],incomingCommand[2]);  // save stirrer speed - pinID, stirrerSpeed
        break;
        
        case 110:
            // update SMTP and email details - userID, server, provider, port, user, password, admin email, email 1, email 2, email 3, email 2 enabled, email 3 enabled, enable emails
            emails.updateSMTP(incomingCommand[1],incomingText[2],incomingCommand[3],incomingCommand[4],incomingText[5],incomingText[6],incomingText[7],incomingText[8],incomingText[9],incomingText[10],incomingCommand[11],incomingCommand[12],incomingCommand[13]);
        break;

        case 111:  // I/O pin updated, refresh pin varibles
            controller.update1wireBus(incomingCommand[1],incomingCommand[2]);  // update 1wire bus - enable/disable, GPIO
        break;

        case 112:  // I/O pin updated, refresh pin varibles
            controller.reconfigPorts();
        break;

        case 120:
            for(short a=0; a < incomingCommand[1]; a++ )  // download and extract all updates to be installed
            {
                rules.addRule(incomingText[a+1]);
            }
            
            rules.frontendResponse();
            std::fill_n(socketBuf, socketBuflen, 0);  // reset array
            strcpy(socketBuf, outgoingDataString.c_str());
            len = strlen(socketBuf);
            send(socketClient, socketBuf, len, 0);
            outgoingData = 1;
            // rules.addRule(incomingCommand[1],incomingCommand[2],incomingText[3],incomingCommand[4],incomingCommand[5],incomingCommand[6],incomingCommand[7],incomingCommand[8],incomingCommand[9],incomingCommand[10],incomingCommand[11],incomingCommand[12],incomingCommand[13],incomingCommand[14],incomingCommand[15]);  // add light mode - modeNumber, start, end, name
        break;

        case 121:
            rules.deleteRule(incomingCommand[1]);  // delete custom rule - ruleID
            
            rules.frontendResponse();
            std::fill_n(socketBuf, socketBuflen, 0);  // reset array
            strcpy(socketBuf, outgoingDataString.c_str());
            len = strlen(socketBuf);
            send(socketClient, socketBuf, len, 0);
            outgoingData = 1;
        break;

        case 122:
            rules.enableRule(incomingCommand[1]);  // enable custom rule - ruleID
        break;
        // default:
        // break;
        case 990:
            update.runUpdate();  // run update
        break;

        case 991:
            update.updateFinished();  // update finished
        break;

        case 992:
            update.userNotifiedUpdateComplete();  // user notified update complete 
        break;

        case 993:
            update.updateAutoUpdate(incomingCommand[1]);  // update auto update - value
        break;

        case 994:  // manual check for update
            update.checkForUpdate(1);  // update auto update
        break;

        case 995:
            rtc.saveNewDateTime(incomingCommand[1],incomingCommand[2],incomingCommand[3],incomingCommand[4],incomingCommand[5],incomingCommand[6],incomingCommand[7]);  // save new date and time - second, minute, hour, dayOfWeek, dayOfMonth, month, year
        break;

        case 997:  // using to send dosing pump status to update UI, need to replace with web socket sending command to frontend without request
            outgoingDataString = "{\"do\":[";
            dosing.compileDoseJson();

            outgoingDataString += "]}";

            std::fill_n(socketBuf, socketBuflen, 0);  // reset array
            strcpy(socketBuf, outgoingDataString.c_str());
            len = strlen(socketBuf);
            send(socketClient, socketBuf, len, 0);
            outgoingData = 1;

            // std::cout << "currentMillisTime = " << currentMillisTime << std::endl;
            // std::cout << "outgoingDataString = " << outgoingDataString << std::endl;
        break;

        case 998:
            outgoingDataString = "{\"probes\":[";
            probes.compileProbesJson();

            outgoingDataString += "],\"ds18b20\":[";
            ds18b20.compileDS18B20Json();

            outgoingDataString += "],\"dht\":[";
            ds18b20.compileDHT22Json();

            outgoingDataString += "],\"ac\":[";
            outlets.compileOutletJson();

            outgoingDataString += "],\"dc\":[";
            dcPorts.compileDcJson();

            outgoingDataString += "],\"s\":[";
            sensors.compileSensorJson();

            outgoingDataString += "],\"do\":[";  // dosing pump status
            dosing.compileDoseJson();

            outgoingDataString += "],\"in\":[";  // info
            outgoingDataString += "{\"h\":\"";  // health
            outgoingDataString += to_string(health);
            outgoingDataString += "\",\"u\":\"";
            outgoingDataString += to_string(update.getUpdateAvailable());
            outgoingDataString += "\",\"t\":\"";
            outgoingDataString += to_string(currentMillisTime);
            outgoingDataString += "\"}";

            outgoingDataString += "]}";

            std::fill_n(socketBuf, socketBuflen, 0);  // reset array
            strcpy(socketBuf, outgoingDataString.c_str());
            len = strlen(socketBuf);
            send(socketClient, socketBuf, len, 0);
            outgoingData = 1;

            // std::cout << "currentMillisTime = " << currentMillisTime << std::endl;
            // std::cout << "outgoingDataString = " << outgoingDataString << std::endl;
        break;

        case 999:
            systemSettings.restart();  // restart controller
        break;
    }
    // std::fill_n(socketBuf, socketBuflen, 0);  // reset array
    // socketBuf[0] = 't';
    // socketBuf[1] = 'e';
    // socketBuf[2] = 's';
    // socketBuf[3] = 't';

    // echo back to the client
    if (outgoingData == 0) {send(socketClient, socketBuf, socketNread, 0);}  // if no data is going back to frontend
    else {outgoingData = 0;}

    close(socketClient);

    // re-bind and listen on the socket
    bind_listen_socket(server, server_addr);
}