#include <Arduino.h>
#include <HardwareSerial.h> //connexion TX-RX du MHZ19

#include <NTPClient.h> //pour se connecter au net et récupérer l'heure et la date
#include <WiFi.h>      //connexion de l'ESP au wifi
#include <WiFiUdp.h>
#include <PubSubClient.h> //pour le MQTT

#include "MHZ19.h"          //capteur Winsen MHZ19-C
#include <Adafruit_SCD30.h> //capteur Sensirion SCD30
#include "SPIFFS.h"         //pour l'utilisation de la mémoire de l'ESP32

#define BAUDRATE 9600 //vitesse de transfert utilisée pour le MHZ19 et la connexion série avec le pc

const int periode = 60000;      //variable fréquence des mesures
const int temps_choix = 2000;  //temps pour faire un choix dans la fonction choix_utilisateur avant de passer à la boucle
unsigned long getDataTimer = 0; //variable pour fréquence des mesures

//Paramètres du WIFI
const char *ssid = "cohabit";
const char *password = "lewifidecohabit";

//Paramètres du serveur MQTT
char mqtt_server[] = "192.168.0.242"; //adresse IP du serveur MQTT
#define MQTT_USER "capteurs"                  //nom d'utilisateur vide par défaut
#define MQTT_PASS "Fablab"                  //pas de mot de passe par défaut

//Pour la connexion au serveur MQTT en tant que client
WiFiClient espClient;
PubSubClient MQTTclient(espClient);

//Variables pour l'horodatage
String heure;
String date;
unsigned long epochTime; //temps Unix
unsigned long timestamp; //nom du fichier enregistré sur l'ESP = temps Unix de l'horodatage

/* Variables lecture fichier */
const int BUFFER_SIZE = 13;
char buffer[BUFFER_SIZE];

/* variables capteurs*/
//taux de CO2 ppm et température °C du capteur MHZ19
int CO2_mhz19;
int16_t Temp_mhz19;
//taux de CO2 ppm, température °C et humidité relative du capteur SCD30
int CO2_scd30;
float Temp_scd30;
float Humidite_scd30;
//tableaux de variables pour récupérer les données du capteur S8
//byte CO2req_S8[] = {0xFE, 0x04, 0x00, 0xl, 0x00, 0x01, 0xD5, 0xC5};
//byte CO2out_S8[] = {0, 0, 0, 0, 0, 0, 0};

//Choix du serveur NTP pour récupérer l'heure, 7200s=2h pour être dans le bon fuseau horaire et 60000=60s est le * taux de rafraichissement
WiFiUDP ntpUDP;
NTPClient temps(ntpUDP, "fr.pool.ntp.org", 7200, 60000);

MHZ19 mhz19; //création objet mhz19 pour l'utilisation du capteur du même nom

HardwareSerial mhz19_serial(2); //connexion du capteur MHZ19 sur U2_TXD et U2_RXD
//HardwareSerial s8_serial(0);    //connexion du capteur S8 sur U0_TXD et U0_RXD



//s8_serial.begin(9600, SERIAL_8N1, RXD2, TXD2); //peut être possibilité de redéfinir les pins et que ça passe mais possibilité de juste perdre la console plutot que redéfinir ça




Adafruit_SCD30 scd30; //création de l'objet scd30 pour l'utilisation du capteur du même nom. Connexion sur broches SDA->21 et SCL->22 de l'ESP

// void RequestCO2_S8()
// {
//   while (!s8_serial.available())
//   {
//     s8_serial.write(CO2req_S8, 7);
//     delay(50);
//   }
//   int timeout = 0;
//   while (s8_serial.available() < 7)
//   {
//     timeout++;
//     if (timeout > 10)
//     {
//       while (s8_serial.available())
//         s8_serial.read();
//       break;
//     }
//     delay(50);
//   }

//   for (int i = 0; i < 7; i++)
//   {
//     CO2out_S8[i] = s8_serial.read();
//   }
// }

// unsigned long CO2count()
// {
//   int high = CO2out_S8[3];
//   int low = CO2out_S8[4];
//   unsigned int val = high * 256 + low;
//   return val;
// }

void MQTTsend() //tester la communication avec le serveur MQTT
{
  static int cpt = 0;
  cpt++;
  String reponse = "test MQTT n°" + (String)cpt;
  MQTTclient.publish("test", reponse.c_str());
}

void MQTTconnect() //connexion au serveur MQTT
{
  while (!MQTTclient.connected())
  {
    Serial.print("Connexion MQTT ...");
    String clientId = "TestClient-";
    clientId += String(random(0xffff), HEX); //connexion avec un ID client aléatoire

    if (MQTTclient.connect(clientId.c_str(), MQTT_USER, MQTT_PASS))
    {
      Serial.println("Connecté !");
    }
    else //si échec de connexion, affichage erreur
    {
      Serial.print("Echec, rc =");
      Serial.print(MQTTclient.state());
      Serial.println("Nouvelle tentative dans 5 secondes");
      delay(5000);
    }
  }
}

void horodatage() //Fonction permettant de relever l'heure et la date
{
  //Exemple pompé sur le site https://randomnertutorials.com/esp8266-nodemcu-date-time-ntp-client-server-arduino avec toutes les explications
  temps.update();                   //récupération des données temps
  heure = temps.getFormattedTime(); //récupération de l'heure au format heure:minutes:secondes

  epochTime = temps.getEpochTime();              //récupération du temps Unix
  struct tm *ptm = gmtime((time_t *)&epochTime); //structure contenant toutes les infos de temps (date, heure, ...)

  int jour = ptm->tm_mday;
  int mois = ptm->tm_mon + 1;
  int annee = ptm->tm_year + 1900;
  date = String(annee) + "-" + String(mois) + "-" + String(jour); //construction de la variable date au format aaaa-mm-jj
}

void lecture(String nom_fichier) //lire et extraire les données d'un fichier sur la mémoire SPIFFS
{
  File fichier = SPIFFS.open(nom_fichier, "r");
  Serial.println(fichier.readString());
  fichier.close();
}

void listFilesInDir(File dir, int numTabs = 1) //lister les fichiers et dossiers dans la mémoire SPIFFS
{
  while (true)
  {
    File entry = dir.openNextFile();
    if (!entry)
    {
      Serial.println("Y'a pu rien !");
      break;
    }

    for (uint8_t i = 0; i < numTabs; i++) //ajout d'un tab pour la mise en page de l'arborescence
    {
      Serial.print('\t');
    }
    Serial.print(entry.name());
    if (entry.isDirectory())
    {
      Serial.println("/");
      listFilesInDir(entry, numTabs + 1);
    }
    else
    {
      Serial.print("\t\t");
      Serial.println(entry.size());
    }
    entry.close();
  }
}

void init_wifi() //Initialisation et connexion du Wifi
{
  WiFi.begin(ssid, password);
  Serial.print("Attente de connexion ...");
  while (WiFi.status() != WL_CONNECTED)
  {
    Serial.print(".");
    delay(100);
  }
  Serial.println("\n");
  Serial.println("Connexion etablie");
  Serial.print("Adresse IP :"); //affichage de l'adresse IP indiquant que l'ESP est bien connecté
  Serial.println(WiFi.localIP());
}

void init_SPIFFS() //initialisation du système de fichier dans la mémoire SPIFFS
{
  SPIFFS.begin();
  Serial.println("Initialisation système de fichier");
  if (SPIFFS.begin(true))
  {
    Serial.println("SPIFFS monté correctement");
  }
  else
  {
    Serial.println("Erreur d'initialisation du SPIFFS");
  }
}

void data_SPIFFS() //Récupération des informations sur l'état de la mémoire SPIFFS
{
  unsigned int espace_total = SPIFFS.totalBytes();
  unsigned int espace_utilise = SPIFFS.usedBytes();

  Serial.println("Espace total : " + String(espace_total) + " byte");
  Serial.println("Espace utilisé : " + String(espace_utilise) + " byte");

  File dir = SPIFFS.open("/");
  listFilesInDir(dir);
}

void formatage() //formater la mémoire SPIFFS
{
  bool formate = SPIFFS.format();
  if (formate)
  {
    Serial.println("Mémoire formatée avec succès");
  }
  else
  {
    Serial.println("Formatage raté");
  }
}

void choix_utilisateur() //l'utilisateur choisit ce qu'il souhaite faire
{
  String num_fichier;
  String choix = "";
  Serial.println("Tapez le numéro en fonction de votre choix :");
  Serial.println("1 \t Lancer la mesure et envoyer les données au serveur MQTT");
  Serial.println("2 \t Afficher les données de la mémoire de l'ESP");
  Serial.println("3 \t ! Formater la mémoire de l'ESP !");
  Serial.println("4 \t Etat de la mémoire de l'ESP");
  Serial.println("5 \t Calibration CO2 du capteur MHZ19 prenant comme ref l'air actuel = 400 ppm");
  getDataTimer = millis();

  while (1)
  {
    if (millis() - getDataTimer >= temps_choix) //après t = temps_choix = 60s, on entre dans la boucle
    {
      break;
    }
    if (Serial.available() > 0)
    {
      choix = Serial.readString();
      if (choix == "1")
      {
        Serial.println("1"); //pour aller directement dans la boucle pour mesurer et envoyer les données
        break;
      }
      else if (choix == "2")
      {
        Serial.println("Quel fichier ?");
        while (1)
        {
          if (Serial.available() >= 11) //pour attendre que 11 caractères soient tapés (taille d'un nom de fichier au format temps Unix)
          {
            num_fichier = Serial.readString();
            Serial.println(num_fichier);
            lecture(num_fichier);
            break;
          }
        }
      }
      else if (choix == "3")
      {
        Serial.println("3");
        formatage();
      }
      else if (choix == "4")
      {
        data_SPIFFS();
      }
      else if (choix == "5")
      {
        mhz19.calibrate(); //fonction de calibration de la biblio du MHZ19 ... lire la doc !!
        Serial.println("Calibration ...");
        delay(10000);
        CO2_mhz19 = mhz19.getCO2();
        Serial.print("CO2 =");
        Serial.println(CO2_mhz19);
      }
      else //si l'utilisateur tape sur n'importe quelle touche autre que 1 à 5 -> affichage du menu des choix
      {
        Serial.println("Tapez le numéro en fonction de votre choix :");
        Serial.println("1 \t Lancer la mesure et envoyer les données au serveur MQTT");
        Serial.println("2 \t Afficher les données de la mémoire de l'ESP");
        Serial.println("3 \t ! Formater la mémoire de l'ESP !");
        Serial.println("4 \t Etat de la mémoire de l'ESP");
        Serial.println("5 \t Calibration CO2 du capteur MHZ19 prenant comme ref l'air actuel = 400 ppm");
      }
      getDataTimer = millis();
    }
    delay(200);
  }
}

void setup()
{
  Serial.begin(BAUDRATE);
  delay(1000);

  init_wifi();

  MQTTclient.setServer(mqtt_server, 1883);

  //initialisation de l'objet temps permettant de se connecter au serveur donnant la date et l'heure
  delay(1000);
  temps.begin();

  if (!scd30.begin()) //détection et initialisation du scd30
  {
    Serial.println("échec de connexion au SCD30");
    while (1)
    {
      delay(10);
      if (scd30.begin()) break;
    }
  }
  Serial.println("SCD30 trouvé !");

  delay(1000);

  mhz19_serial.begin(BAUDRATE);
  mhz19.begin(mhz19_serial); //initialisation du MHZ19
  mhz19.autoCalibration();   // Turn auto calibration ON (OFF autoCalibration(false))

  //s8_serial.begin(BAUDRATE);

  horodatage();
  delay(1000);

  init_SPIFFS();
  delay(1000);

  choix_utilisateur();
}

void loop()
{
  if (millis() - getDataTimer >= periode) //mesure toutes les minutes
  {
    if (!MQTTclient.connected())
    {
      MQTTconnect();
    }
    //note: getCO2() default is command "CO2 Unlimited". This returns the correct CO2 reading even
    //if below background CO2 levels or above range (useful to validate sensor). You can use the
    //usual documented command with getCO2(false)

    CO2_mhz19 = mhz19.getCO2();          // Request CO2 (as ppm)
    Temp_mhz19 = mhz19.getTemperature(); // Request Temperature (as Celsius)

    if (Temp_mhz19 == -273)
    {
      Serial.println("Redémarrage de l'ESP");
      MQTTclient.publish("test", "Redémarrage de l'ESP");
      delay(1000);
      ESP.restart();
    }
    else
    {
      
      if (scd30.dataReady())
      {
        if (!scd30.read())
        {
          Serial.println("Erreur de lecture des données du SCD30");
          MQTTclient.publish("test", "Erreur de lecture des données du SCD30");
        }
        else
        {
          Temp_scd30 = scd30.temperature; //renommage de variables ... parce que !
          Humidite_scd30 = scd30.relative_humidity;
          CO2_scd30 = scd30.CO2;
        }
      }
      // Serial.print("coucou3");
      // RequestCO2_S8();
      // unsigned int CO2_s8 = CO2count();
      // Serial.print("coucou");
      // for (int i = 0; i < 7; i++)
      // {
      //   Serial.print(CO2out_S8[i], HEX);
      // }
      // Serial.print("coucou2");
      // Serial.println("\nCO2 :" + String(CO2_s8));

      horodatage(); //appel de la fonction horodatage

      //   File fichier = SPIFFS.open("/" + String(timestamp), "a+");
      //   if (!fichier)
      //   {
      //     Serial.println("Echec de création du fichier");
      //     return;
      //   }
      //   else
      //   {
      //     fichier.println(date + " - " + heure + " - " + CO2 + " - " + Temp);
      //     fichier.close();
      //     Serial.println(timestamp);
      //   }
      //   getDataTimer = millis();
      // }
      //MQTTsend();
      String req = date + " - " + heure + " - MHZ19 " + String(Temp_mhz19) + " " + String(CO2_mhz19) + " - SCD30 " + String(Temp_scd30) + " " + String(CO2_scd30) + " " + String(Humidite_scd30);
      MQTTclient.publish("test", req.c_str()); //utilisation de c_str pour reformater le texte en tableau char avec le \0 à la fin
      Serial.println(req);                     //affichage ...
      getDataTimer = millis();
    }
  }
}
