#include <FS.h> // librarie folosita de salvarea credentialelor pe FS
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <WiFiManager.h> // librarie folosita pentru salvarea credentialelor WIFI in eprom
#include <ArduinoJson.h> // librarie folosita pentru salvarea credentialelor MQTT in eprom
#include <WiFiClient.h> 
#include <OneWire.h>
#include <DallasTemperature.h>
#include <ESP8266HTTPClient.h>
#include <ESP8266httpUpdate.h>

// variabilele user si devid sunt incarcate din eprom folosind FS
char mqtt_user[9];
char mqtt_devid[5];

// variabile generate cu functia generate_vars, folosind user si devid
char mqttPassword[15];
char mqttSUB[22];
char espName[13];
char mqttESP[22];
char mqttTEMP[23];
char mqttLWT[22];

const String model = "NodeMCU Dallas";
const String ver = "v2.0.2";
const char* mqttServer = "mqtt.clickhome.ro";
const int mqttPort = 1883;
long loopTimer = 900000; // by default trimite temperatura la 15 minute
long lastMsg = 0;
float loopTemp = 0;
int inPin = 5;
String mqttMessage;

#define ONE_WIRE_BUS D1  // pinul de date folosit la nodeMCU este D1
OneWire oneWire(ONE_WIRE_BUS);
DallasTemperature sensors(&oneWire);
WiFiClient espClient;
PubSubClient client(espClient);

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 (mqttTEMP, "stat/");
  strcat (mqttTEMP, espName);
  strcat (mqttTEMP, "/TEMP");

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

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() {
  // daca nu am credentialele MQTT, nu fac nimic, delay 1000 de ore
  while (String(mqtt_user)=="") {
    Serial.println("User MQTT invalid!");
    delay(3600000000);
  }
  // ma conectez la mqtt server
  while (!client.connected()) {
    client.setServer(mqttServer, mqttPort);
    Serial.print("AtloopTempting MQTT connection...");
    // Incerc sa ma reconectez cu LWT din 5 in 5 secunde
    if (client.connect(espName, mqtt_user, mqttPassword, mqttLWT, 1, 1, "Offline")) {
      Serial.println("connected");
      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);
      Serial.println(netinfo);
      client.publish(mqttESP, esp_info.c_str(),TRUE);
      Serial.println(esp_info);
      client.publish(mqttESP, "15 minute",TRUE); // trimit intervalul default
      client.subscribe(mqttSUB);
    } 
    else {
      Serial.print("failed, rc=");
      Serial.print(client.state());
      Serial.println("Incerc din nou in 60 de secunde");
      delay(60000);
    }
  }
}
 
void setup() {
  Serial.begin(115200);
  //generez variabilele
  readFS();
  WiFiManagerParameter custom_mqtt_user("user", "mqtt user", mqtt_user, 9);
  WiFiManagerParameter custom_mqtt_devid("devid", "mqtt devid", mqtt_devid, 5);
  // ma conectez la AP via wifi
  WiFiManager wifi;
  // ESP-ul asteapta ca sa fie configurat 2 minute
  wifi.setConfigPortalTimeout(120); 
  // sta AP 3 minute apoi se reseteaza din nou
  wifi.setTimeout(180);
  if (!wifi.autoConnect("ClickHome")) {
    Serial.println("timeout - going to sleep"); // EU aici nu am inteles ce se intampla ... poate ii punem un delay!!
  }
  delay(200);
  //citesc datele salvate anterior pe FS
  strcpy(mqtt_user, custom_mqtt_user.getValue());
  strcpy(mqtt_devid, custom_mqtt_devid.getValue());
  //genereaza topicurile de mqtt in baza mqtt_user si mqtt_devid
  generate_vars(); 
  reconectez();
  //setez functia care parseaza mesajele venite prin mqtt
  client.setCallback(getMessage);
  pinMode(inPin, INPUT);
  sensors.begin();
}

void getMessage(char* topic, byte* payload, unsigned int length) {
  float t = 0;
  mqttMessage="";
  Serial.print("Mesaj primit pe topicul: ");
  Serial.println(topic);
  Serial.print("Mesaj:");
  for (int i = 0; i < length; i++) {
    mqttMessage += (char)payload[i];
  }
  Serial.println(mqttMessage);
 
  // daca primeste 'temp' pe mqtt, trimite temperatura 
  if (mqttMessage == "temp") {
    Serial.println("Trimit temperatura");
    sensors.setResolution(12);
    sensors.requestTemperatures(); // Send the command to get Temperatures
    t = sensors.getTempCByIndex(0);
      String json = " {\"Temp\":";
      json += String (t);
      json += "}";
      client.publish(mqttTEMP, json.c_str(),TRUE);
      Serial.println(json);
  }  
  
  // daca primeste 'reset' pe mqtt, isi da reset
  if (mqttMessage == "reset") {
    String lastwords="Am fost resetat ...";
    client.publish(mqttESP, lastwords.c_str(), TRUE);
    delay (3000);
    ESP.reset();
    delay (5000);
  }
  
  // daca primeste 'update' isi face 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/senzor/dallas/arduino.bin");  
    //ret = ESPhttpUpdate.update("update.clickhome.ro", 80, "/senzor/dallas/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;
    }
    Serial.println(msg);
  }
  
  // daca primeste valoarea in minute seteaza loopTimer si-l trimite in DB
  if (mqttMessage == "debug") {
    loopTimer=30000;
    client.publish(mqttESP, " {\"LoopInterval\":\"30 secunde\"}",TRUE);   
   }
   if (mqttMessage == "10") {
     loopTimer=600000;   
     client.publish(mqttESP, " {\"LoopInterval\":\"10 minute\"}",TRUE);
   }
   if (mqttMessage == "15") {
     loopTimer=900000;
     client.publish(mqttESP, " {\"LoopInterval\":\"15 minute\"}",TRUE);   
   }
   if (mqttMessage == "30") {
     loopTimer=1800000;
     client.publish(mqttESP, " {\"LoopInterval\":\"30 de minute\"}",TRUE);   
   }   
   if (mqttMessage == "60") {
     loopTimer=3600000;
     client.publish(mqttESP, " {\"LoopInterval\":\"o ora\"}",TRUE);   
   }
   if (mqttMessage == "stop") {
     loopTimer=3600000000; // setez sa nu trimita valori, 1000 de ore
     client.publish(mqttESP, " {\"LoopInterval\":\"oprit\"}",TRUE);
   }  
}

void readFS() {
  if (SPIFFS.begin()) {
    //Serial.println("mounted file system");
    if (SPIFFS.exists("/config.json")) {
      //file exists, reading and loading
      //Serial.println("reading config file");
      File configFile = SPIFFS.open("/config.json", "r");
      if (configFile) {
        //Serial.println("opened config file");
        size_t size = configFile.size();
        // Allocate a buffer to store contents of the file.
        std::unique_ptr<char[]> buf(new char[size]);
        configFile.readBytes(buf.get(), size);
        DynamicJsonBuffer jsonBuffer;
        JsonObject& json = jsonBuffer.parseObject(buf.get());
        //json.printTo(Serial);
        if (json.success()) {
          //Serial.println("\nparsed json");
          strcpy(mqtt_user, json["mqtt_user"]);
          strcpy(mqtt_devid, json["mqtt_devid"]);
        } 
        else {
          //Serial.println("failed to load json config");
        }
      }
    }
  } 
  else {
    //Serial.println("failed to mount FS");
  }
}

void loop() {
  if (!client.connected()) {
    reconectez();
  }
  client.loop();
  long now = millis();
  if (now - lastMsg > loopTimer) {
    lastMsg = now;
    sensors.setResolution(12);
    sensors.requestTemperatures();
    loopTemp = sensors.getTempCByIndex(0);
    Serial.println(loopTemp);
    if((loopTemp > -20) && (loopTemp <60)) {
      String val = " {\"Temp\":";
      val += String (loopTemp);
      val += "}";
      client.publish(mqttESP, val.c_str(),TRUE);
    }
  }
}