#include "MQTT.h"
#define MDNS_SERVICENAME ""

WiFiUDP MQTT::udp;
const char* MQTT::months[]={"Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"};

AsyncWebServer* MQTT::server = new AsyncWebServer(80);
AsyncWebSocket* MQTT::ws = new AsyncWebSocket("/ws");

bool MQTT::saveConfigFlag=false;
WiFiManagerParameter custom_hostname("hostname", "Hostname", "", 40);
WiFiManagerParameter custom_mqtt_server("server", "mqtt server", "", 40);
WiFiManagerParameter custom_mqtt_port("port", "mqtt port", "",6);
WiFiManagerParameter custom_mqtt_topic("topic", "topic", "", 40);
WiFiManagerParameter custom_deftemp("deftemp", "Default Temperature", "", 6);
WiFiManagerParameter custom_enbuff("enBuff", "Enable heatBuffer", "", 6);
WiFiManagerParameter custom_ntpServer("ntpServer", "NTP Server", "", 40);

MQTT::MQTT(){

}

void MQTT::begin(){
    saveConfigFlag = false;
    Settings::getInstance().loadSettings(); // Load dataset from persistent storage
    wm.setSaveConfigCallback(MQTT::saveConfigCallback);
    custom_hostname.setValue(Settings::prefs.hostname,40);
    custom_mqtt_server.setValue(Settings::prefs.mqtt_server,40);
    custom_mqtt_port.setValue(String(Settings::prefs.mqtt_port).c_str(),6);
    custom_mqtt_topic.setValue(Settings::prefs.mqtt_topic,40);
    custom_deftemp.setValue(String(Settings::prefs.defTemp).c_str(),6);
    custom_enbuff.setValue(String(Settings::prefs.enBuff).c_str(),6);
    custom_ntpServer.setValue(String(Settings::prefs.ntpServer).c_str(),40);
    wm.setCleanConnect(true);
    wm.setConnectRetries(5);
    wm.setWiFiAutoReconnect(true);
    wm.setMinimumSignalQuality(20);
    WiFi.persistent(true);
    WiFi.setAutoConnect(true);
    WiFi.setAutoReconnect(true);
    wm.addParameter(&custom_hostname);
    wm.addParameter(&custom_mqtt_server);
    wm.addParameter(&custom_mqtt_port);
    wm.addParameter(&custom_mqtt_topic);
    wm.addParameter(&custom_deftemp);
    wm.addParameter(&custom_enbuff);
    wm.addParameter(&custom_ntpServer);
    psclient.setClient(client);
    psclient.setServer(Settings::prefs.mqtt_server,Settings::prefs.mqtt_port);
    psclient.setCallback(receiveTopic);
    WiFi.setHostname(custom_hostname.getValue()); //define hostname
    wm.setConfigPortalBlocking(false);
    if(wm.autoConnect("TempReglerAP")){
        //if you get here you have connected to the WiFi
        Serial.println("connected...yeey :)");
        strcpy(glblData.myIP,WiFi.localIP().toString().c_str());
        wm.setHttpPort(8080);
        wm.startWebPortal();
        wifiConnected = true;
    }
    else 
    {
        wm.startWebPortal();
        Serial.println("non blocking config portal running on Port 80");
    }
    if(wifiConnected){
        setenv("TZ", TZ_INFO, 1);             // Zeitzone  muss nach dem reset neu eingestellt werden
        tzset();
        configTzTime(TZ_INFO, Settings::prefs.ntpServer); // ESP32 Systemzeit mit NTP Synchronisieren
        ArduinoOTA.setHostname(Settings::prefs.hostname);
        ArduinoOTA
        .onStart([]() {
        String type;
        if (ArduinoOTA.getCommand() == U_FLASH)
            type = "sketch";
        else // U_SPIFFS
            type = "filesystem";

        // NOTE: if updating SPIFFS this would be the place to unmount SPIFFS using SPIFFS.end()
        Serial.println("Start updating " + type);
        })
        .onEnd([]() {
            Serial.println("\nEnd");
        })
        .onProgress([](unsigned int progress, unsigned int total) {
            Serial.printf("Progress: %u%%\r", (progress / (total / 100)));
        })
        .onError([](ota_error_t error) {
            Serial.printf("Error[%u]: ", error);
            if (error == OTA_AUTH_ERROR) Serial.println("Auth Failed");
            else if (error == OTA_BEGIN_ERROR) Serial.println("Begin Failed");
            else if (error == OTA_CONNECT_ERROR) Serial.println("Connect Failed");
            else if (error == OTA_RECEIVE_ERROR) Serial.println("Receive Failed");
            else if (error == OTA_END_ERROR) Serial.println("End Failed");
        });
        ArduinoOTA.begin();
        
        server->on("/", MQTT::serveData);
        server->on("/override", MQTT::serveOverride);
        server->on("/data", MQTT::serveData);
        server->begin();
        if (MDNS.begin(Settings::prefs.hostname)) {
            Serial.println("mDNS responder started");
            // Add service to MDNS-SD
            MDNS.addService(MDNS_SERVICENAME, "tcp", 80);
            MDNS.addService("http", "tcp", 80);
        }
        reconnect();
    }
}
void MQTT::saveConfigCallback(void){
    Serial.println("SAVE");
    saveConfigFlag = true;
}

void MQTT::saveConfigToFlash(void){
    strcpy(Settings::prefs.mqtt_server,custom_mqtt_server.getValue());
    Settings::prefs.mqtt_port = atoi(custom_mqtt_port.getValue());
    strcpy(Settings::prefs.hostname,custom_hostname.getValue());
    strcpy(Settings::prefs.mqtt_topic,custom_mqtt_topic.getValue());
    strcpy(Settings::prefs.ntpServer,custom_ntpServer.getValue());
    Settings::prefs.defTemp = atof(custom_deftemp.getValue());
    if(custom_enbuff.getValue()[0] != '0'){
        Settings::prefs.enBuff = true;
    }else{
        Settings::prefs.enBuff = false;
    }
    Settings::getInstance().saveSettings(); // Save dataset to persistent storage
}

void MQTT::receiveTopic(char* topic, byte* payload, unsigned int length) {
    glblData.outTemp = String((char*)payload).toFloat();
  }

void MQTT::reconnect(void) {
    // Loop until we're reconnected
    Serial.print("Attempting MQTT connection...");
    // Attempt to connect
    if (psclient.connect(Settings::prefs.hostname)) {
        psclient.subscribe("weatherStation/tempAmb");
        Serial.println("connected");
        
    } else {
        Serial.print("failed, rc=");
        Serial.print(psclient.state());
    }
}

void MQTT::loop(void){
    static uint32_t resetCnt = 0;
    unsigned long now = millis();
    if(now-lastStatusUpdate > 15000){   //update wifi status every 15secs.
        lastStatusUpdate = now;
        if(WiFi.isConnected()){
            resetCnt = 0;
            if(WiFi.getMode() == WIFI_MODE_STA){
                glblData.wifiMode = WIFIMODE_STA;
            }else{
                glblData.wifiMode = WIFIMODE_AP;
            }
            int32_t rssi = WiFi.RSSI();
            if(rssi > -55){
                glblData.wifiStrength = WIFISTRENGTH_HIGH;
            }else if(rssi > -75){
                glblData.wifiStrength = WIFISTRENGTH_MED;
            }else if(rssi > -90){
                glblData.wifiStrength = WIFISTRENGTH_LOW;
            }else{
                glblData.wifiStrength = WIFISTRENGTH_OFF;
            }
            if(!psclient.connected()){
                glblData.wifiStrength = WIFISTRENGTH_ERROR;
                if(now-lastReconnectTry > 60000){
                    lastReconnectTry = now;
                    reconnect();
                }
            }
        }else if(WiFi.getMode() == WIFI_MODE_STA){
            resetCnt = 0;
            glblData.wifiStrength = WIFISTRENGTH_OFF;
            WiFi.disconnect(true);
            delay(200);
            WiFi.enableSTA(true);
            wm.connectWifi("", "", true);
        }else{
            if(wm.getWiFiIsSaved()){
                resetCnt++;
                if(resetCnt > 5*5){
                    Serial.println("Resetting WiFi AP...");
                    ESP.restart();
                }
            }
            glblData.wifiMode = WIFIMODE_AP;
            switch(glblData.wifiStrength){
            case WIFISTRENGTH_HIGH:
                glblData.wifiStrength = WIFISTRENGTH_LOW;
                break;
            case WIFISTRENGTH_MED:
                glblData.wifiStrength = WIFISTRENGTH_HIGH;
                break;
            case WIFISTRENGTH_LOW:
                glblData.wifiStrength = WIFISTRENGTH_MED;
                break;
            default:
                glblData.wifiStrength = WIFISTRENGTH_LOW;
                break;
            }
            
        }
    }
    psclient.loop();
    ArduinoOTA.handle();
    wm.process();
    if(saveConfigFlag){
        Serial.println("Save Settings");
        saveConfigFlag = false;
        saveConfigToFlash();
        Serial.println("disconnect mqtt to activate new settings");
        psclient.disconnect();
    }
}


void MQTT::publish(const char* msg){
    psclient.publish(Settings::prefs.mqtt_topic,msg);
}
void MQTT::publish(const char* msg, unsigned int len){
    psclient.publish(Settings::prefs.mqtt_topic,msg,len);
}
void MQTT::publish(const char* msg, bool retained){
    psclient.publish(Settings::prefs.mqtt_topic,msg,retained);
}
void MQTT::publish_sub(const char* subtopic, const char* msg, bool retained){
    char newtopic[60];
    strcpy(newtopic,Settings::prefs.mqtt_topic);
    strcat(newtopic,"/");
    strcat(newtopic,subtopic);
    psclient.publish(newtopic,msg, retained);
}


/*
void MQTT::handleWebSocketMessage(void *arg, uint8_t *data, size_t len){
    data[len] = 0; //ensure termination!
    Serial.printf("WebSocket Msg '%s'\n", data);
}

void MQTT::handleWebSocket(AsyncWebSocket *server, AsyncWebSocketClient *client, AwsEventType type, void *arg, uint8_t *data, size_t len) {
     switch (type) {
       case WS_EVT_CONNECT:
         Serial.printf("WebSocket client #%u connected from %s\n", client->id(), client->remoteIP().toString().c_str());
         client->text(makeStatusJSON());
         client->text(makeTimerJSON());
         break;
       case WS_EVT_DISCONNECT:
         Serial.printf("WebSocket client #%u disconnected\n", client->id());
         break;
       case WS_EVT_DATA:
         handleWebSocketMessage(arg, data, len);
         break;
       case WS_EVT_PONG:
       case WS_EVT_ERROR:
         break;
     }
   }
*/


void MQTT::serveOverride(AsyncWebServerRequest *request){
    String sending;
    sending = "{";
    if(Settings::prefs.enBuff){
        if(request->hasParam("sw")){
            int ovr = request->getParam("valve")->value().toInt();
            if(ovr == 1){
                glblData.override = OVR_ALWAYSON;
                sending = "{\"Status\": \"Success\", ";
            }else if(ovr == 0){
                glblData.override = OVR_ALWAYSOFF;
                sending = "{\"Status\": \"Success\", ";
            }else{
                glblData.override = OVR_NONE;
                sending = "{\"Status\": \"Success\", ";
            }

        }
    }else{
        sending = "{\"Status\": \"Not Enabled\", ";
        glblData.override = OVR_NONE;
    }
    switch(glblData.override){
        case OVR_ALWAYSOFF:
        sending += "\"Override\": \"Always OFF\"}";
        break;
        case OVR_ALWAYSON:
        sending += "\"Override\": \"Always ON\"}";
        break;
        case OVR_NONE:
        sending += "\"Override\": \"NONE\"}";
        break;
    }
    request->send(404, "text/plain", sending);
}

void MQTT::serveData(AsyncWebServerRequest *request){
    String sending;
    sending = "{\"Temp[degC]\": ";
    sending += glblData.temp;
    sending += ", \"pressure[hPa]\": ";
    sending += glblData.seaLevelPress;
    sending += ", \"rHum[%]\": ";
    sending += glblData.hum;
    sending += ", \"Heating\": ";
    sending += glblData.heating;
    sending += ", \"Set Temp[degC]\": ";
    sending += glblData.settemp;
    sending += " }";
    Serial.println("Sending website...");
    request->send(200, FPSTR(HTTP_HEAD_JSON), sending);
    Serial.println("Sent website...");
}



void MQTT::sendStatus(void){
    tm timeinfo;
    char buffer[35];
    strftime(buffer, sizeof(buffer), "%FT%T%z", &glblData.bootTime);
    publish_sub("Boot-Time",buffer,true);
    publish_sub("hostname", Settings::prefs.hostname, true);
    publish_sub("IP", glblData.myIP, true);
    publish_sub("NTP-Server", Settings::prefs.ntpServer, true);
    if(!getLocalTime(&timeinfo)){
        strcpy(buffer, "00-00-0000T00:00:00+0000");
    }else{
        strftime(buffer, sizeof(buffer), "%FT%T%z", &timeinfo);
    }
    publish_sub("timestamp",buffer,true);
    snprintf(buffer, sizeof buffer, "%0.1f", glblData.temp);
    publish_sub("Temp[degC]",buffer,true);
    snprintf(buffer, sizeof buffer, "%0.0f", glblData.seaLevelPress);
    publish_sub("pressure[hPa]",buffer,true);
    snprintf(buffer, sizeof buffer, "%0.0f", glblData.hum);
    publish_sub("rHum[%]",buffer,true);
    snprintf(buffer, sizeof buffer, "%s", glblData.heating?"true":"false");
    publish_sub("Heating",buffer,true);
    snprintf(buffer, sizeof buffer, "%0.1f", glblData.settemp);
    publish_sub("Set Temp[degC]",buffer,true);
    snprintf(buffer, sizeof buffer, "%d", WiFi.RSSI());
    publish_sub("RSSI [dBm]",buffer,true);

}

void MQTT::remoteLog(const char* str, int32_t num){
    udp.beginPacket(Settings::prefs.mqtt_server,13377);
    struct tm timeinfo;
    getLocalTime(&timeinfo);
    udp.printf("<13>%s %02d %02d:%02d:%02d %s Log: %s %d",months[timeinfo.tm_mon],timeinfo.tm_mday,timeinfo.tm_hour,timeinfo.tm_min,timeinfo.tm_sec,Settings::prefs.hostname,str,num);
    udp.endPacket();
  }

  void MQTT::remoteLog(const char* str){
    udp.beginPacket(Settings::prefs.mqtt_server,13377);
    struct tm timeinfo;
    getLocalTime(&timeinfo);
    udp.printf("<13>%s %02d %02d:%02d:%02d %s Log: %s",months[timeinfo.tm_mon],timeinfo.tm_mday,timeinfo.tm_hour,timeinfo.tm_min,timeinfo.tm_sec,Settings::prefs.hostname,str);
    udp.endPacket();
  }
  
  void MQTT::remoteLog(const char* str,const char* str2){
    udp.beginPacket(Settings::prefs.mqtt_server,13377);
    struct tm timeinfo;
    getLocalTime(&timeinfo);
    udp.printf("<13>%s %02d %02d:%02d:%02d %s Log: %s %s",months[timeinfo.tm_mon],timeinfo.tm_mday,timeinfo.tm_hour,timeinfo.tm_min,timeinfo.tm_sec,Settings::prefs.hostname,str,str2);
    udp.endPacket();
  }
  
  void MQTT::remoteLogf(const char* format, ...){
    udp.beginPacket(Settings::prefs.mqtt_server,13377);
    struct tm timeinfo;
    getLocalTime(&timeinfo);
    char loc_buf[256];
    char * temp = loc_buf;
    va_list arg;
    va_start(arg, format);
    int len = snprintf(temp, sizeof(loc_buf), "<13>%s %02d %02d:%02d:%02d %s Log: ", months[timeinfo.tm_mon],timeinfo.tm_mday,timeinfo.tm_hour,timeinfo.tm_min,timeinfo.tm_sec,Settings::prefs.hostname);
    if(len < (int)sizeof(loc_buf)){
      len += vsnprintf(temp+len, sizeof(loc_buf)-len, format, arg);
    }
    va_end(arg);
    if(len >= (int)sizeof(loc_buf)){  // comparation of same sign type for the compiler
      udp.write((uint8_t*)temp, len);
      udp.print("...Memory issue at log buffer!");
      udp.endPacket();
      return;
    }
    udp.write((uint8_t*)temp, len);
    udp.endPacket();
    return;
  }