#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 <Ephemeris.h>    // https://github.com/MarScaper/ephemeris

// 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 = 1;                        // la timezone pour UTC vers GMT (+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;

// flag pour lancer le calcule du lever et coucher du soleil après chaque synchronisation au serveur NTP
bool flag_ephemeride = false;  // en position desactivé               
// variable pour stocker les heures et minutes du lever et coucher de soleil
int heure_lever, minute_lever, heure_coucher, minute_coucher;

// fonction pour afficher l'heure et la date 
void digitalClockDisplay() {
  // digital clock display of the time
  printDigits(hour());
  Serial.print(":");
  printDigits(minute());
  Serial.print(":");
  printDigits(second());
  Serial.print(" ");
  printDigits(day());
  Serial.print("/");
  printDigits(month());
  Serial.print("/");
  Serial.print(year());
  Serial.println();
}

// fonction qui met en forme les chiffres de 0 à 9 sur 2 digits
void printDigits(int digits) {    
  if (digits < 10)
    Serial.print('0');
  Serial.print(digits);
}


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
}

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      
      if (flag_ephemeride) {               // si le flag est activé on calcul l'ephemeride avec la date et heure actuelle
         printRiseAndSet("Varages", 43.6, 5.9667, timeZone, day(),month(),year());
      }
    }
  }
}


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)
        unsigned long Date_heure_courante = secsSince1900 - 2208988800UL + timeZone * SECS_PER_HOUR; 
        // on active le flag pour calculer l'ephemeride        
        flag_ephemeride = true; 
        // on retourne la date et heure pour la synchro
        return Date_heure_courante;
      }
    }
    // 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();
}

void printRiseAndSet(char *city, FLOAT latitude, FLOAT longitude, int UTCOffset, int day, int month, int year)
{
  Ephemeris::setLocationOnEarth(latitude,longitude);    
  Serial.print(city);
  Serial.print(" (UTC");
  if( UTCOffset >= 0 ) { Serial.print("+"); }
  Serial.print(UTCOffset);
  Serial.print(")");
  Serial.println(":");
           
  SolarSystemObject sun = Ephemeris::solarSystemObjectAtDateAndTime(Sun,day,month,year,0,0,0);

  // si le calcul est bon on stock les heures et minutes du lever et coucher dans les variables
  if( sun.riseAndSetState == RiseAndSetOk )  {
    float seconds;
    // on converit et heure minute et seconde
    Ephemeris::floatingHoursToHoursMinutesSeconds(Ephemeris::floatingHoursWithUTCOffset(sun.rise,UTCOffset), &heure_lever, &minute_lever, &seconds);
    // on converit et heure minute et seconde
    Ephemeris::floatingHoursToHoursMinutesSeconds(Ephemeris::floatingHoursWithUTCOffset(sun.set,UTCOffset), &heure_coucher, &minute_coucher, &seconds);
  }
  // si le calcul sont mauvais on affiche l'erreur
  else if( sun.riseAndSetState == LocationOnEarthUnitialized )
  {
    Serial.println("Vous devez d'abord définir votre Longitude et Latitude");
  }
  else if( sun.riseAndSetState == ObjectAlwaysInSky )
  {
    Serial.println("Soleil toujours dans le ciel pour votre emplacement");
  }
  else if( sun.riseAndSetState == ObjectNeverInSky )
  {
    Serial.println("Soleil jamais dans le ciel pour votre emplacement");
  }
  // on ecrit dans la com serie avec le pc les infos
  Serial.println();
  Serial.print("A Varages le soleil se levera à ");
  printDigits(heure_lever);
  Serial.print("H");
  printDigits(minute_lever);
  Serial.print(" et se couchera à ");
  printDigits(heure_coucher);
  Serial.print("H");
  printDigits(minute_coucher);
  Serial.println();  
  // on desactive le flag
  flag_ephemeride = false;
}