#include "WiFi.h"
#include "AsyncUDP.h"
#include "ArduinoJson.h"
#include "WebServer.h"
#include "FirebaseESP32.h"

const char * ssid = "SSID";
const char * password = "PASSWORD";

AsyncUDP udp;
IPAddress IPadresa;
WebServer server(3388);

const unsigned char* DiscoveryPacket = reinterpret_cast<const unsigned char *>( "HelloLocalHomeSDK" );
const char * deviceId = "deviceid123";

int LED = 26;
int LED_OnOff = 25;
int LED_Pause = 17;
int LED_Run = 16;

/*********** Domaci spinac ************/

int button = 12;
int button2 = 13;

bool vypinacOnOff = 0;   // counter for the number of button presses
bool stavTlacitka = 0;         // current state of the button
bool posledniStavTlacitka = 0;

bool vypinacRun = 0;
bool stavTlacitka2 = 0;
bool posledniStavTlacitka2 = 0;

/************* FIREBASE *************/
#include "addons/TokenHelper.h"
#include "addons/RTDBHelper.h"

#define API_KEY "API_KEY"     // pro anonymního uživatele
#define DATABASE_URL "https://<project_id>-default-rtdb.europe-west1.firebasedatabase.app/"

FirebaseData fbdo;
FirebaseAuth auth;
FirebaseConfig config;

unsigned long prevTime = 0;
unsigned long prevTimeWiFiControl = 0;
unsigned long tim = 0;
bool bool_OnOff = false;
bool bool_OnOffPrev = false;
bool bool_Run = false;
bool bool_RunPrev = false;
bool bool_Pause = false;
bool bool_PausePrev = false;

bool zmenaLocal_OnOff = 0;
bool zmenaLocal_Run = 0;
bool zmenaLocal_Pause = 0;
bool zmenaFirebase_OnOff = 0;
bool zmenaFirebase_Run = 0;
bool zmenaFirebase_Pause = 0;
bool zmenaVypinac_OnOff = 0;
bool zmenaVypinac_Run = 0;

int STauto = 10;
int StartCyklu = 0;
int CasCyklu1 = 15000;
int CasCyklu2 = 10000;
int CasCyklu3 = 7500;
bool PausePoCyklu = false;
bool NextCyklus2 = false;
bool NextCyklus3 = false;
bool NextCyklusKonec = false;

void setup()
{
    Serial.begin(115200);

    pinMode(LED, OUTPUT);
    pinMode(LED_OnOff, OUTPUT);
    pinMode(LED_Pause, OUTPUT);
    pinMode(LED_Run, OUTPUT);
    digitalWrite(LED, LOW);

    pinMode(button, INPUT_PULLUP);    
    pinMode(button2, INPUT_PULLUP);
    
    WiFi.mode(WIFI_STA);
    WiFi.begin(ssid, password);
    if (WiFi.waitForConnectResult() != WL_CONNECTED) {
        Serial.println("WiFi Failed");
        while(1) {
            delay(1000);
        }
    }

    firebaseLogin();
    udpLoop();

    server.on("/", HTTP_ANY, handleSmartDevice); // HTTP_ANY je POST i GET
    server.begin();
    Serial.println("SERVER BEGIN!!");   
}

void loop()
{
/************** Kontrola pripojeni a reconnect ****************/
  tim = millis();
  if ((WiFi.status() != WL_CONNECTED) && (tim - prevTimeWiFiControl > 10000)) {
    Serial.println("Reconnecting to WiFi");
    WiFi.disconnect();
    WiFi.reconnect();
    prevTimeWiFiControl = tim;
  }

/************** Domaci spinac ***************/  
  stavTlacitka = digitalRead(button);
  if(STauto < 21)stav_tlacitka(stavTlacitka, posledniStavTlacitka, vypinacOnOff);
  stavTlacitka2 = digitalRead(button2);
  if(STauto == 20 || (STauto > 29 && bool_Pause == true && bool_Run == false))stav_tlacitka(stavTlacitka2, posledniStavTlacitka2, vypinacRun);

  if (vypinacOnOff != bool_OnOff){
    zmenaVypinac_OnOff = true;
    bool_OnOff = vypinacOnOff;
    Serial.println("Vypinac OnOff zmena");
  }
  if (vypinacRun != bool_Run){
    zmenaVypinac_Run = true;
    bool_Run = vypinacRun;
    Serial.println("Vypinac Run zmena");
  }

  server.handleClient();
  delay(20);

/************** Firebase ***************/ 
  if(zmenaLocal_OnOff == false && zmenaLocal_Run == false && zmenaLocal_Pause == false && zmenaVypinac_OnOff == false && zmenaVypinac_Run == false){
    if (Firebase.ready() && ((tim - prevTime > 500) || prevTime == 0)) {
      prevTime = tim;
    
      if (Firebase.RTDB.getJSON(&fbdo, "/washer")){
  
          StaticJsonDocument < 200 > doc;
          deserializeJson(doc, fbdo.jsonString());
          
          bool_OnOff = doc["OnOff"]["on"];
          bool_Run = doc["StartStop"]["isRunning"];
          bool_Pause = doc["StartStop"]["isPaused"];

      /************** Zjisteni presne zmeny ***************/
          if (bool_OnOff != bool_OnOffPrev){ // OnOff
            if (STauto > 21){ // zakazana zmena v prubehu cyklu
              bool_OnOff = true;
              Firebase.RTDB.setBool(&fbdo, "/washer/OnOff/on", true);
              Serial.println("Prepis OnOff do Firebase true -> Nepovolena zmena OnOff ve stavu 30+");
            } else {
              zmenaFirebase_OnOff = true;
              Serial.println("Firebase OnOff zmena");
            }
          }
          if (bool_Run != bool_RunPrev){  // isRunning
            if (STauto < 11){ // zakazana zmena pokud neni pracka pripravena k prani
              bool_Run = false;
              Firebase.RTDB.setBool(&fbdo, "/washer/StartStop/isRunning", false);
              Serial.println("Prepis Run do Firebase false -> Nepovolena zmena Run ve stavu 10");
            } else if(STauto > 29 && bool_Pause == false && bool_Run == false){ // zakazana zmena v prubehu cyklu
              bool_Run = true;
              Firebase.RTDB.setBool(&fbdo, "/washer/StartStop/isRunning", true);
              Serial.println("Prepis Run do Firebase true -> Nepovolena zmena Run ve stavu 30+");
            } else {
              zmenaFirebase_Run = true;
              Serial.println("Firebase Run zmena");
            }
          }
          if (bool_Pause != bool_PausePrev){  // isPaused
            if (STauto < 21 ){  // zakazana zmena pokud nebezi cykly
              bool_Pause = false;
              Firebase.RTDB.setBool(&fbdo, "/washer/StartStop/isPaused", false);
              Serial.println("Prepis Pause do Firebase -> Nepovolena zmena kdyz nebezi cyklus");
            } else {
              zmenaFirebase_Pause = true;
              Serial.println("Firebase Pause zmena");
            }
          }
     
      }
      else {
        Serial.println("Error pri cteni z databaze");
        Serial.println(fbdo.errorReason());
      }   

     }}

    /************** Zjisteni prikazu a vykonani prislusne cinnosti ***************/
    if(zmenaLocal_OnOff == true || zmenaFirebase_OnOff == true || zmenaVypinac_OnOff == true){
      STautomat();
      Serial.println(STauto);
      Serial.println("Local, Firebase nebo Doma zmena v OnOff");
      if (zmenaLocal_OnOff == true || zmenaVypinac_OnOff == true){      // pokud je zmena v Local - update firebase
        Firebase.RTDB.setBool(&fbdo, "/washer/OnOff/on", bool_OnOff);
        Serial.println("Prepis OnOff do Firebase");
        }
      zmenaLocal_OnOff = false;
      zmenaFirebase_OnOff = false;
      zmenaVypinac_OnOff = false;
      Serial.println("");
    }
    if(zmenaLocal_Run == true || zmenaFirebase_Run == true || zmenaVypinac_Run == true){
      STautomat();
      Serial.println(STauto);
      Serial.println("Local, Firebase nebo Doma zmena v Run");
      if (zmenaLocal_Run == true || zmenaVypinac_Run == true){        // pokud je zmena v Local - update firebase
        Firebase.RTDB.setBool(&fbdo, "/washer/StartStop/isRunning", bool_Run);
        Serial.println("Prepis Run do Firebase");
        }
      zmenaLocal_Run = false;
      zmenaFirebase_Run = false;
      zmenaVypinac_Run = false;
      Serial.println("");
    }
    if(zmenaLocal_Pause == true || zmenaFirebase_Pause == true){
      ovladani_LED_Pause();
      STautomat();
      Serial.println(STauto);
      Serial.println("Local nebo Firebase zmena v Pause");
      if ((zmenaLocal_Pause == true) && STauto > 21){      // pokud je zmena v Local - update firebase
        Firebase.RTDB.setBool(&fbdo, "/washer/StartStop/isPaused", bool_Pause);
        Serial.println("Prepis Pause do Firebase");
        }
      zmenaLocal_Pause = false;
      zmenaFirebase_Pause = false;
      Serial.println("");
    }

    if (bool_Run == true && STauto > 21) {  // casovace pro cykly
      if((tim - StartCyklu > CasCyklu1) && STauto == 30){
        NextCyklus2 = true;
        StartCyklu = tim;
        Serial.println("NextCyklus po 30");
        STautomat();
        Serial.println(STauto);
      } else if((tim - StartCyklu > CasCyklu2) && STauto == 31){
        NextCyklus3 = true;
        StartCyklu = tim;
        Serial.println("NextCyklus po 31");
        STautomat();
        Serial.println(STauto);
      } else if((tim - StartCyklu > CasCyklu3) && STauto == 32){
        NextCyklusKonec = true;
        Serial.println("NextCyklus po 32");
        STautomat();
        Serial.println(STauto);
      }
  }
}

void udpLoop(){
  if(udp.listen(3311)) {
    udp.onPacket([](AsyncUDPPacket packet) {
      Serial.print("Delka packetu: ");
      Serial.print(packet.length());
      Serial.print(" Data: ");
      Serial.write(packet.data(), packet.length());
      Serial.println();
    
      if (compare(packet.data(),DiscoveryPacket) == 0){
        digitalWrite(LED, HIGH);    // HIGH == probehlo IDENTIFY
        packet.printf(deviceId,packet.remotePort());
        Serial.print("Google Home IP a port: ");
        Serial.print(packet.remoteIP());
        Serial.print(":");
        Serial.print(packet.remotePort());
        Serial.println();
        }
      });
  }
}


/************** Pro porovnani discovery packetu ***************/
int compare(unsigned char * a, const unsigned char * b){
    int i;
    for(i=0;i<17;i++){
    if(a[i]!=b[i]){
        return 1;
        }
    }
    return 0;
}



void handleSmartDevice() {
 
  Serial.println("HTTP handle");
  Serial.println(server.arg("plain")); // vypsání přijaté zpráva
  StaticJsonDocument < 200 > doc;
  deserializeJson(doc, server.arg("plain"));
    
/************ Decoder zpravy ****************/
  if (server.arg("plain").indexOf("{""\"on\":") >= 0){
  bool_OnOff = doc["on"];}
  else if (server.arg("plain").indexOf("{""\"isRunning\":") >= 0){
  bool_Run = doc["isRunning"];}
  else if (server.arg("plain").indexOf("{""\"isPaused\":") >= 0){
  bool_Pause = doc["isPaused"];}

/************** Zjisteni presne zmeny ***************/
  if (bool_OnOff != bool_OnOffPrev){ // OnOff
    if (STauto > 21){ // zakazana zmena v prubehu cyklu
      bool_OnOff = true;
      Serial.println("Nepovolena Local zmena OnOff ve stavu 30+");
    } else {
      zmenaLocal_OnOff = true;
      Serial.println("Local OnOff zmena");
    }
  }
  if (bool_Run != bool_RunPrev){ // isRunning
    if (STauto < 11){ // zakazana zmena pokud neni pracka pripravena k prani
      bool_Run = false;
      Serial.println("Nepovolena Local zmena Run ve stavu 10");
    } else if(STauto > 29 && bool_Pause == false && bool_Run == false){ // zakazana zmena v prubehu cyklu
      bool_Run = true;
      Serial.println("Nepovolena Local zmena Run ve stavu 30+");
    } else {
      zmenaLocal_Run = true;
      Serial.println("Local Run zmena");
    }
  }
  if (bool_Pause != bool_PausePrev){ // isPaused
    if (STauto < 21 ){  // zakazana zmena pokud nebezi cykly
      bool_Pause = false;
      Serial.println("Nepovolena Local zmena kdyz nebezi cyklus");
    } else {
      zmenaLocal_Pause = true;
      Serial.println("Local Pause zmena");
    }
  }
  
  server.send(200, "text/html", "OK");
}

void stav_tlacitka(bool tlacitko, bool &posledniStavTlacitka, bool &stavSvetla){

  if (tlacitko != posledniStavTlacitka) {
    if (tlacitko == false) {
      if(stavSvetla == false) stavSvetla=true; 
      else stavSvetla = false;
    }}
  posledniStavTlacitka = tlacitko;
}

void firebaseLogin(){

  config.api_key = API_KEY;
  config.database_url = DATABASE_URL;

  if (Firebase.signUp(&config, &auth, "", "")){
    Serial.println("Prihlaseni do Firebase");
  }
  else{
    Serial.println("CHYBA prihlaseni do Firebase");
    Serial.printf("%s\n", config.signer.signupError.message.c_str());
  }

  Firebase.begin(&config, &auth);
  Firebase.reconnectWiFi(true);
}

void ovladani_LED_OnOff(){

  if (bool_OnOff == true) {
    digitalWrite(LED_OnOff,HIGH);
    } else {
    digitalWrite(LED_OnOff,LOW);
  }
 
  /********** Výpis při změně *************/
  
  if(bool_OnOff!=bool_OnOffPrev){
    Serial.print("OnOff: ");
    Serial.println(bool_OnOff);
  }

  vypinacOnOff = bool_OnOff;
  bool_OnOffPrev = bool_OnOff;
}

void ovladani_LED_Run(){

  if (bool_Run == true) {
    digitalWrite(LED_Run,HIGH);
    } else {
    digitalWrite(LED_Run,LOW);
  }

  /********** Výpis při změně *************/

  if(bool_Run!=bool_RunPrev){
    Serial.print("isRunning: ");
    Serial.println(bool_Run);
    }  
  vypinacRun = bool_Run;
  bool_RunPrev = bool_Run;
}

void ovladani_LED_Pause(){

  if (bool_Pause == true) {
    digitalWrite(LED_Pause,HIGH);
  } else {
    digitalWrite(LED_Pause,LOW);
  }

  /********** Výpis při změně *************/
  if(bool_Pause!=bool_PausePrev){
    Serial.print("isPaused: ");
    Serial.println(bool_Pause);
  }
  bool_PausePrev = bool_Pause;
}

void STautomat(){
  switch (STauto)
  {
case 10:  // Vypnuto
    if (bool_OnOff == true) {
      ovladani_LED_OnOff();
      STauto = 20;
    }
    else if(bool_Run != bool_RunPrev || bool_Pause != bool_PausePrev){
      ovladani_LED_OnOff();
      bool_Run = false;
      bool_Pause = false;
      zmenaLocal_Run = false;
      zmenaFirebase_Run = false;
      zmenaVypinac_Run = false;
      zmenaLocal_Pause = false;
      zmenaFirebase_Pause = false;
      ovladani_LED_Run();
      ovladani_LED_Pause();
      STauto = 10;
    }
    break;

   case 20: // Pripraveno k prani
    if (bool_Run == true) {
      bool_Pause = false;
      Firebase.RTDB.setBool(&fbdo, "/washer/StartStop/isPaused", false);
      ovladani_LED_Run();
      ovladani_LED_Pause();
      StartCyklu = tim;
      STauto = 30;
    }
    else if (bool_OnOff == false) {
      ovladani_LED_OnOff();
      STauto = 10;
    }
    else {
      STauto = 20;
    }
    break;

  case 30:  // Cyklus 1
    if (NextCyklus2 == true){
      Serial.println("Z 30 do 31, NextCyklus je true ");
      NextCyklus2 = false;
      if(bool_Pause == true){
        NextCyklus2 = true;   // true pro moznost unpause v dalsim cyklu
        bool_Run = false;
        vypinacRun = false;
        Firebase.RTDB.setBool(&fbdo, "/washer/StartStop/isRunning", false);
        ovladani_LED_Run();
        ovladani_LED_Pause();
        Serial.println("Z 30 do 31 je pause");
      }
      StartCyklu = tim;
      STauto = 31;
    }
    break;

  case 31:  // Cyklus 2
    if (bool_Pause == true && bool_Run == true && NextCyklus2 == true){
      NextCyklus2 = false;
      StartCyklu = tim;
      bool_Pause = false;
      Firebase.RTDB.setBool(&fbdo, "/washer/StartStop/isPaused", false);
      ovladani_LED_Run();
      ovladani_LED_Pause();
      Serial.println("Zruseni pause v 31");
      STauto = 31;
    }
    if (NextCyklus3 == true){
      Serial.println("Z 31 do 32, NextCyklus je true");
      NextCyklus3 = false;
      if(bool_Pause == true){  
        NextCyklus3 = true;   // true pro moznost unpause v dalsim cyklu
        bool_Run = false;
        vypinacRun = false;
        Firebase.RTDB.setBool(&fbdo, "/washer/StartStop/isRunning", false);
        ovladani_LED_Run();
        ovladani_LED_Pause();       
        Serial.println("Z 31 do 32 je pause");
      }
      StartCyklu = tim;
      STauto = 32;
    }
    break;

  case 32:  // Cyklus 2
    if (bool_Pause == true && bool_Run == true && NextCyklus3 == true){
      NextCyklus3 = false;
      StartCyklu = tim;
      bool_Pause = false;
      Firebase.RTDB.setBool(&fbdo, "/washer/StartStop/isPaused", false);
      ovladani_LED_Run();
      ovladani_LED_Pause();
      Serial.println("Zruseni pause v 32");
      STauto = 32;
    }
    if (NextCyklusKonec == true){    
      bool_Run = false;
      vypinacRun = false;
      Firebase.RTDB.setBool(&fbdo, "/washer/StartStop/isRunning", false);
      ovladani_LED_Run();
      ovladani_LED_Pause(); 
      Serial.println("Z 32 do 20, NextCyklus je true ");
      if(bool_Pause == true){
        Serial.println("Z 32 do 20 je pause");
      }
      NextCyklusKonec = false;
      STauto = 20;
    }
    break;
  }
}
