#include <FS.h>                   //this needs to be first, e folosit de salvarea credentialelor pe FS
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <WiFiManager.h>        //https://github.com/tzapu/WiFiManager
#include <ArduinoJson.h>          //https://github.com/bblanchon/ArduinoJson pentru salvarea credentialelor in eprom
#include <WiFiClient.h> 
#include <OneWire.h>
#include <ESP8266HTTPClient.h>
#include <ESP8266httpUpdate.h>

#define DEBUG 0 //0=off 1=on
#define PIR D1 //  pinul de date D1 la care se conecteaza PIRul

bool ARM,lastPIR;
unsigned int interval_alarma = 60000; // la ce interval sa trimita notificarile 60 sec = 60000 in cazul in care a re doua alarme consecutive
unsigned long t_alarma=0;
const unsigned long interval_update=1000*60*5; // face update la 5 min 
unsigned long t_update=0;

char mqtt_user[9]; //le va lua din FS
char mqtt_devid[5]; //le va lua din FS
char mqttPassword[15]; //urmatoarele le va genera cu functia gen
char mqttSUB[22];
char espName[13];
char mqttESP[22];
char mqttALM[24];
char mqttPIR[22];
char mqttLWT[22];
char mqttSTA[24];

const String model = "NodeMCU PIR";
const String ver = "v2.1.1";
const char* mqttServer = "mqtt.clickhome.ro";
const int mqttPort = 1883;
String mqttMessage;

WiFiClient espClient;
PubSubClient client(espClient);
bool readConfigFile();

void generate_vars(){
  strcpy (mqttPassword, "8219CH");
  strcat (mqttPassword, mqtt_user);

  strcpy (espName, mqtt_user);
  strcat (espName, "-");
  strcat (espName, mqtt_devid);

  strcpy (mqttSUB, "cmnd/");
  strcat (mqttSUB, espName);
  strcat (mqttSUB, "/ESP");

  strcpy (mqttESP, "stat/");
  strcat (mqttESP, espName);
  strcat (mqttESP, "/ESP");

  strcpy (mqttPIR, "stat/");
  strcat (mqttPIR, espName);
  strcat (mqttPIR, "/PIR");

  strcpy (mqttALM, "stat/");
  strcat (mqttALM, espName);
  strcat (mqttALM, "/ALARM");

  strcpy (mqttLWT, "tele/");
  strcat (mqttLWT, espName);
  strcat (mqttLWT, "/LWT");

  strcpy (mqttSTA, "tele/");
  strcat (mqttSTA, espName);
  strcat (mqttSTA, "/STATE");
}

String ipToString(IPAddress ip){
  String s="";
  for (int i=0; i<4; i++)
    s += i  ? "." + String(ip[i]) : String(ip[i]);
  return s;
}
String getMacAddress() {
  byte mac[6];
  WiFi.macAddress(mac);
  String cMac = "";
  for (int i = 0; i < 6; ++i) {
    cMac += String(mac[i],HEX);
    if(i<5)
    cMac += "-";
  }
  cMac.toUpperCase();
  return cMac;
}

void reconectez() {
  while (String(mqtt_user)==""){
    #if DEBUG
    Serial.println("Invalid user!");
    #endif
    delay(99999999);
  }
  
  // ma conectez la mqtt server
  while (!client.connected()) {
    client.setServer(mqttServer, mqttPort);
    #if DEBUG
    Serial.print("AtloopTempting MQTT connection...");
    #endif
    // Incerc sa ma reconectez cu LWT din 5 in 5 secunde
    if (client.connect(espName, mqtt_user, mqttPassword, mqttLWT, 1, 1, "Offline")) {
      #if DEBUG
      Serial.println("connected");
      #endif
      client.publish(mqttLWT,"Online",TRUE);
        // trimit informatii utile cand ma conectez
        String esp_info = "{\"ESPMac\":\"";
        esp_info += getMacAddress();
        esp_info += "\",\"IPAddress\":\"";
        esp_info += ipToString(WiFi.localIP());    
        esp_info += "\"}";       
        String netinfo = "{\"Module\":\"";
        netinfo += String (model);
        netinfo += "\",\"Version\":\"";
        netinfo += String (ver);
        netinfo += "\"}";            
        
        client.publish(mqttESP, netinfo.c_str(),TRUE);
        #if DEBUG
        Serial.println(netinfo);
        #endif
        client.publish(mqttESP, esp_info.c_str(),TRUE);
        #if DEBUG
        Serial.println(esp_info);
        #endif
    
        client.subscribe(mqttSUB);
    } else {
      #if DEBUG
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println(" try again in 60 seconds");
      #endif
      // Wait 60 seconds before retrying
      delay(60000);
    }
  }
}


void setup()
{
  pinMode(PIR,INPUT_PULLUP);

  #if DEBUG
  Serial.begin(115200);
  #endif

  // Mount the filesystem
  bool result = SPIFFS.begin();
  #if DEBUG
  //Serial.println("SPIFFS opened: " + result);
  #endif
  
  readConfigFile(); //citesc user si devid din memorie
  generate_vars(); //genereaza topicurile de mqtt in baza mqtt_user si mqtt_devid

  // ma conectez la AP via wifi
   WiFiManager wifi;
   wifi.setConfigPortalTimeout(120); // a timeout so the ESP doesn't hang waiting to be configured, for instance after a power failure
   wifi.setTimeout(180); // sta AP 3 minute apoi se reseteaza din nou
   if (!wifi.autoConnect("ClickHome")) {
     #if DEBUG
     Serial.println("timeout - going to sleep");
     #endif
  }
  delay(200);

  reconectez();
  client.setCallback(getMessage); //setez functia care parseaza mesajele venite prin mqtt

  String json = "{\"ARM\":\"";
  json += String (ARM);
  json += "\"}";
  client.publish(mqttPIR, json.c_str(),TRUE);

  update_state(); //facem update la mqtt cu Uptime

}

void getMessage(char* topic, byte* payload, unsigned int length) {
  float t = 0;
  mqttMessage="";
  #if DEBUG
  Serial.print("Message arrived in topic: ");
  Serial.println(topic);
  Serial.print("Message:");
  #endif
  for (int i = 0; i < length; i++) {
    mqttMessage += (char)payload[i];
  }
  #if DEBUG
  Serial.println(mqttMessage);
  #endif
 
// procedura de software update via WEB
   if (mqttMessage == "update")
   {
        String msg="Software update: ";
        t_httpUpdate_return ret; 
        //ESPhttpUpdate.rebootOnUpdate(false);
        ret = ESPhttpUpdate.update("http://update.clickhome.ro/pir/arduino.bin");  
        switch(ret) {
           case HTTP_UPDATE_FAILED:
               msg.concat(" eroare:");
               msg.concat(ESPhttpUpdate.getLastError());
               msg.concat(" motiv:");
               msg.concat(ESPhttpUpdate.getLastErrorString().c_str());
               break;
           case HTTP_UPDATE_NO_UPDATES:
               msg.concat(" no update.");
               break;
           case HTTP_UPDATE_OK:
               msg.concat(" success.");
               break;
        }
        #if DEBUG
        Serial.println(msg);
        #endif
   }
  
    if (mqttMessage == "stare")
    {
      // citesc starea si o trimit
      String json = "{\"ARM\":\"";
      json += String (ARM);
      json += "\"}";
      #if DEBUG
      Serial.println(json);
      #endif
      client.publish(mqttPIR, json.c_str(),TRUE);
    }
  
    if (mqttMessage == "ARM" || mqttMessage == "DISARM")
    {
      ARM = (mqttMessage=="ARM") ? true : false;
      String json = "{\"ARM\":\"";
      json += String (ARM);
      json += "\"}";
      client.publish(mqttPIR, json.c_str(),TRUE);
    }
  
    // reset la ESP
    if (mqttMessage == "reset")
    {
        String lastwords="Am fost resetat ...";
        client.publish(mqttPIR, lastwords.c_str(), TRUE);
        delay (3000);
        ESP.reset();
        delay (5000);
    }

}

bool readConfigFile() {
  // this opens the config file in read-mode
  File f = SPIFFS.open("/config.json", "r");
  
  if (!f) {
    #if DEBUG
    Serial.println("Configuration file not found");
    #endif
    return false;
  } else {
    // we could open the file
    size_t size = f.size();
    // Allocate a buffer to store contents of the file.
    std::unique_ptr<char[]> buf(new char[size]);

    // Read and store file contents in buf
    f.readBytes(buf.get(), size);
    // Closing file
    f.close();
    // Using dynamic JSON buffer which is not the recommended memory model, but anyway
    // See https://github.com/bblanchon/ArduinoJson/wiki/Memory%20model
    DynamicJsonBuffer jsonBuffer;
    // Parse JSON string
    JsonObject& json = jsonBuffer.parseObject(buf.get());
    // Test if parsing succeeds.
    if (!json.success()) {
      #if DEBUG
      Serial.println("JSON parseObject() failed");
      #endif
      return false;
    }
    #if DEBUG
    json.printTo(Serial);
    #endif
    strcpy(mqtt_user, json["mqtt_user"]);
    strcpy(mqtt_devid, json["mqtt_devid"]);
  }
  return true;
}

void update_state(){ //facem update la mqtt cu Uptime
    String json = "{\"Uptime\":";
    json += (int)millis()/3600000; //exprimat in ore
    json += "}";
    client.publish(mqttSTA, json.c_str(),FALSE);
}

void loop()
{
  if (!client.connected()) 
  {
    reconectez();
  }
  client.loop();

  //facem update la $interval_update cu uptime
  if (millis() - t_update > interval_update){
    update_state();
    t_update=millis();
  }
  //end update
  
  if (ARM==1){ //daca e armat
    if (digitalRead(PIR)!=lastPIR){ //daca s-a schimbat valoarea 
      if (digitalRead(PIR)==1){ //daca am alarma
        if (millis() - t_alarma > interval_alarma){ //daca a trecut timpul de la alarma anterioara
          String json = "{\"PIR\":1}";
          client.publish(mqttALM, json.c_str(),FALSE);
          t_alarma=millis();
          #if DEBUG
          Serial.print("Trimis mqtt alarm:");
          #endif
        }
      }
      #if DEBUG
      Serial.print("Direct PIN:");
      Serial.println (digitalRead(PIR));
      #endif
      lastPIR=digitalRead(PIR);
    }
  }
}

