#include "WiFiEsp.h"      // https://github.com/bportaluri/WiFiEsp
#include "WiFiEspUdp.h"   // est inclus dan la librairie WiFiEsp
#include <TimeLib.h>      // https://github.com/PaulStoffregen/Time
#include <Timezone.h>     // https://www.arduinolibraries.info/libraries/timezone

// on défini une 2eme com serie en D2 et D8 pour communiquer avec l'ESP
HardwareSerial ESP(D2, D8); // RX, TX 


char ssid[] = "XXXX";                          // Votre réseau wifi
char pass[] = "YYYY";                          // Votre mdp du réseau wifi
int status = WL_IDLE_STATUS;                   // l'état du réseau wifi

char timeServer[] = "fr.pool.ntp.org";         // le pool de serveur ntp interrogé  
unsigned int localPort = 8888;                 // port d'écoute des packets UDP

const int NTP_PACKET_SIZE = 48;                // L'horodatage NTP se trouve dans les 48 premiers octets du message
const int UDP_TIMEOUT = 2000;                  // délai d'attente pour l'arrivée d'un paquet UDP
const int timeZone = 0;                        // la timezone 0 pour UTC 1 POUR utc (+1 paris)

byte packetBuffer[NTP_PACKET_SIZE];            // tampon pour contenir les paquets entrants et sortants

// on instance l'UDP 
WiFiEspUDP Udp;

// variable pour stocker l'heure précédente
time_t prevDisplay = 0;

// pour le changement d'heure 
TimeChangeRule Heure_ete = {"ETE", Last, Sun, Mar, 2, +120};       // Heure d'été
TimeChangeRule Heure_hiver = {"HIV", Last, Sun, Nov, 2, +60};      // Heure d'hiver
Timezone Chg_heure(Heure_hiver, Heure_ete);

TimeChangeRule *tcr;

// fonction pour afficher l'heure et la date UTC puis actuel Hiver ou été, changera tout seul.
void digitalClockDisplay() {
  Serial.println("****************");   
  // time_t utc = 1580063396; // pour tester l'hiver, timestamp de 26/1/2020 18:29:56  
  // time_t utc = 1593195871; // pour tester l'été, timestamp de 26/6/2020 18:24:31
  time_t utc = prevDisplay;   // pour l'heure actuelle
  time_t local = Chg_heure.toLocal(utc, &tcr);
  Serial.println();
  printDateTime(utc, "UTC");
  printDateTime(local, tcr -> abbrev);  
}

// constuction de la date
void printDateTime(time_t t, const char *tz) {
    char buf[32];
    char m[4];    
    strcpy(m, monthShortStr(month(t)));
    sprintf(buf, "%.2d:%.2d:%.2d %s %.2d %s %d %s",
        hour(t), minute(t), second(t), dayShortStr(weekday(t)), day(t), m, year(t), tz);
    Serial.println(buf);
}

// pour permettre le chg d'heure
time_t compileTime() {
    const time_t FUDGE(10);    
    const char *compDate = __DATE__, *compTime = __TIME__, *months = "JanFebMarAprMayJunJulAugSepOctNovDec";
    char chMon[4], *m;
    tmElements_t tm;

    strncpy(chMon, compDate, 3);
    chMon[3] = '\0';
    m = strstr(months, chMon);
    tm.Month = ((m - months) / 3 + 1);

    tm.Day = atoi(compDate + 4);
    tm.Year = atoi(compDate + 7) - 1970;
    tm.Hour = atoi(compTime);
    tm.Minute = atoi(compTime + 3);
    tm.Second = atoi(compTime + 6);
    time_t t = makeTime(tm);
    return t + FUDGE;           
}

void setup() {
  Serial.begin(115200);               // On démmarre la com série avec le pc a 115200 baud
  ESP.begin(115200);                  // On démmarre la com série avec l'ESP a 115200 baud
  WiFi.init(&ESP);                    // On initialisle la connection au réseau  

  // On test si on a le réseau à travers l'esp
  if (WiFi.status() == WL_NO_SHIELD) {
    Serial.println("Je tourve pas l'ESP");    
    while (true);  // On ne continue pas
  }
  
  // On essaye de se connécter au réseau wifi
  while ( status != WL_CONNECTED) {
    Serial.print("J'essaye de me connecter au réseau wifi : ");
    Serial.println(ssid);
    status = WiFi.begin(ssid, pass);
  }
  // On affiche les infos de connexions
  Serial.print("Connécté à : ");
  Serial.println(WiFi.SSID());  
  IPAddress ip = WiFi.localIP();
  Serial.print("Mon ip est : ");
  Serial.println(ip);  
  // On demarre l'udp
  Udp.begin(localPort);
  // on configure la synchronisation (toutes les 30s on interrogera le serveur NTP) 
  setSyncProvider(getNtpTime);
  setSyncInterval(30);      // 30 secondes pour les tests sinon 300 secondes ou plus
  setTime(Chg_heure.toUTC(compileTime())); // pour permettre le chg d'heure
} 



void loop() {
  if (timeStatus() != timeNotSet) {        // si le temps est configuré
    if (now() != prevDisplay) {            // ne met à jour l'affichage que si l'heure a changé
      prevDisplay = now();                 // l'ancienne heure devient celci
      digitalClockDisplay();               // on affiche l'heure dans la com serie avec le pc;
    }
  }
}


time_t getNtpTime() {  
  // on attend que l'udp reponde
  unsigned long startMs = millis();
  while (!Udp.available() && (millis() - startMs) < UDP_TIMEOUT) {}  
  Serial.println(Udp.parsePacket());   
  while (Udp.parsePacket() > 0) ;
   // Envoie de la requète NTP
    Serial.println("Envoie de la requète NTP !!!");
    sendNTPpacket(timeServer);
    // on récupère la réponse (1500 ms pour recevoir) 
    uint32_t beginWait = millis();
    while (millis() - beginWait < 1500) {
      int size = Udp.parsePacket();
      if (size >= NTP_PACKET_SIZE) {
        Serial.println("Reponse du serveur NTP reçu :");             
        Udp.read(packetBuffer, NTP_PACKET_SIZE);
        // l'horodatage commence à l'octet 40 du paquet reçu et fait quatre octets,
        // ou deux mots soit 2 octets par mot. On extraye les deux mots:
        unsigned long highWord = word(packetBuffer[40], packetBuffer[41]);
        unsigned long lowWord = word(packetBuffer[42], packetBuffer[43]);
        // on combine les quatre octets (les deux mots) en un entier long
        // c'est l'heure NTP en secondes depuis le 1er janvier 1900
        unsigned long secsSince1900 = highWord << 16 | lowWord;
        // on affiche cette heure en timstamp dans la comme serie
        Serial.print("Temps en secondes depuis le 1er janvier 1900 = ");
        Serial.println(secsSince1900);
        // on calcule le temps en ajoutant la time zone et le formate pour la librairie timelib (2208988800UL année 2000)
        return secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR;
      }
    }
    // si rien n'arrive au bout de 1500ms on sort de la boucle while et on retourne 0
    Serial.println("Pas de reponse du serveur NTP !!!");
    return 0; 
}


// fonction qui envoie une requête NTP au serveur de temps à l'adresse indiquée en argument
void sendNTPpacket(char *ntpSrv) {
  // on met tous les octets du tampon à 0
  memset(packetBuffer, 0, NTP_PACKET_SIZE);
  // On initialise les valeurs nécessaires pour former la requête NTP (j'ai pas trouver d'infos sur ces valeurs, pourquoi celle-ci, est ce modifiable ?) 
  packetBuffer[0] = 0b11100011;   // LI, Version, Mode
  packetBuffer[1] = 0;            // Stratum, or type of clock
  packetBuffer[2] = 6;            // Polling Interval
  packetBuffer[3] = 0xEC;         // Peer Clock Precision
  // 8 bytes à zero de packetBuffer[4] à packetBuffer[11] puis
  packetBuffer[12]  = 49;
  packetBuffer[13]  = 0x4E;
  packetBuffer[14]  = 49;
  packetBuffer[15]  = 52;
  // tous les champs tampon ont reçu les valeurs nécéssaire à la requète de temps sur le serveur NTP
  // on ouvre la com au serveur (adresse et port de la requete tj 123)
  Udp.beginPacket(ntpSrv, 123); 
  // on ecrit la demande (le tampon)
  Udp.write(packetBuffer, NTP_PACKET_SIZE);
  // on ferme la com
  Udp.endPacket();
}