//
//  ██╗   ██╗  ████████╗  ██╗  ██╗       ███████╗         ██████╗   ██████╗   ███    ███╗  ███    ███╗   ██████╗   ███    ██╗ 
//  ██║   ██║  ╚══██╔══╝  ██║  ██║       ██╔════╝        ██╔════╝  ██╔═══██╗  ████  ████║  ████  ████║  ██╔═══██╗  ████   ██║ 
//  ██║   ██║     ██║     ██║  ██║       ███████╗        ██║       ██║   ██║  ██║████╔██║  ██║████╔██║  ██║   ██║  ██║██  ██║ 
//  ██║   ██║     ██║     ██║  ██║       ╚════██║        ██║       ██║   ██║  ██║ ██╔╝██║  ██║ ██╔╝██║  ██║   ██║  ██║ ██ ██║ 
//   ██████╔╝     ██║     ██║  ███████╗  ███████║         ██████╗  ╚██████╔╝  ██║ ╚═╝ ██║  ██║ ╚═╝ ██║   ██████╔╝  ██║  ████║ 
//   ╚═════╝      ╚═╝     ╚═╝  ╚══════╝  ╚══════╝         ╚═════╝   ╚═════╝   ╚═╝     ╚═╝  ╚═╝     ╚═╝   ╚═════╝   ╚═╝  ╚═══╝ 
//
//########################################################################################################################################################### 
//######    Utils    ######   Sous Programme / Fonction Commune a plusieur healder  | Dont etre inclu en premier
//########################################################################################################################################################### 

// Affichage Message Boot [      ]
void OK()     {   Serial.print(F("[\033[1;32;40m  OK  \033[0;39;49m]  "));   }                                         // [FAILED] FAILED en Rouge  decalage 10 
void FAILED() {   Serial.print(F("[\033[;37;41mFAILED\033[0;39;49m]  "));    }                                         // [  OK  ] OK en vert [      ]
void VIDE()   {   Serial.print(F("[      ]  "));                             }


/*************************************************************************************************************
* Sous Programme : Affichage sur le LCD Blacklight
**************************************************************************************************************
* Entree : ligne 1 ou 2 - texte a afficher
* Sortie : LCD
**************************************************************************************************************/
// Declatation des Librairies 
// (Librairie Interne : rien a faire !! Librairie Externe il faut les placer dans le sous repertoire Mes Documents\Arduino\libraries)
//#include <Wire.h>                                                       // Libairie LOCAL de l'IDE d'Arduino
#include <rgb_lcd.h>                                                      // Librairie Externe : Grove_LCD_RGB_Backlight sur PORT I2C

/****** Initilalisation librairie                      ******/
// Initilalisation RGB LCD Backlight_master sur Port I2C
  rgb_lcd lcd;                                                            // Initialisation du LCD
  unsigned char colorR = 255;                                                       // Initialisation couleur LCD R Rouge !! G Vert !! B Bleu
  unsigned char colorG = 255;
  unsigned char colorB = 255;
  char ligne1[17];
  char ligne2[17];  

void AfficheLCD( unsigned char  colorR, unsigned char  colorG, unsigned char  colorB, unsigned char validLigne,  char text[16] )
{

    if ( validLigne == 1 && strcmp(ligne1, text) != 0 ){
          lcd.setRGB(colorR, colorG, colorB);                    // Couleur du LCD
          lcd.setCursor(0, 0);                       
          delay(1);
          lcd.print("                ");                         // Efface la ligne 1 seulement
          delay(10);
          lcd.setCursor(0, 0);                                   // Passe à la ligne : 0 pour ligne 1 et 1 pour ligne 2 
          delay(1);
          lcd.print(text);
          delay(10);
          //Serial.print("Linge 1 : "); Serial.println(text);
          strcpy(ligne1, text);
       }
    if ( validLigne == 2 && strcmp(ligne2, text) != 0 ){
          lcd.setRGB(colorR, colorG, colorB);                     // Couleur du LCD
          lcd.setCursor(0, 1);                       
          delay(1);
          lcd.print("                ");                          // Efface la ligne 2 seulement
          delay(10);
          lcd.setCursor(0, 1);                                    // Passe à la ligne : 0 pour ligne 1 et 1 pour ligne 2 
          delay(1);
          lcd.print(text);
          delay(10);
          //Serial.print("Linge 2 : "); Serial.println(text);
          strcpy(ligne2, text);
       }     
}
/***********************************************************************************************************************************************************/
// #####   ----   Sous Programme Banner de 1ere Connesxion Serial et Telnet
/***********************************************************************************************************************************************************/
//#####  ----->  Banner 1ere Connexion : Telnet 
void PremiereConnexionTelnet()
{
if (clientTelnet && clientTelnet.connected() && premiereConnexionTelnet) {
          cmdLine.println( F("*****  Telnet Client connecte !!"));
          clientTelnet.println(F("\033[1;32;40m"));
          clientTelnet.println(F(" #############################################################"));
          clientTelnet.println(F(" ###  Prise IP  . . . . . . . . . . . .  Connexion Telnet  ###"));
          clientTelnet.println(F(" ###  Arduino UNO R4 - Ethernet 2                   IoToS  ###"));
          clientTelnet.println(F(" #############################################################"));       
          clientTelnet.println(F("\033[0;39;49m")); // Serial color defaut 
          cmdLine.println();             
          clientTelnet.println(F(" Avec MobaXtern taper cli off pour eviter les doubles carateres"));
          clientTelnet.println(F(" Si besoin : Dans PUTTY le menu Telnet decocher"));
          clientTelnet.println(F(" Return key sends Telnet New Line instead of ^M"));
          clientTelnet.println(F(" - Serial Commande Line (cmdLine).. START  : OK !"));
          clientTelnet.println(F("   Taper help pour voir les commandes disponibles"));
          clientTelnet.println(F("   Taper print pour voir la configuration réseau"));
          clientTelnet.println();
          cmdLine.print( F("> "));
          //clientTelnet.flush();  // clear input buffer, else you get strange characters 
          premiereConnexionTelnet = 0; 
          timeoutDeconnexionTelnet = millis();
  }
  // --- Timeout de réinitialisation : réactive le banner seulement si pas connecté
  // (évite de remettre premiereConnexionTelnet=1 pendant une session active)
  if ( (connexionTelnet == 0 ) && (( millis() - timeoutDeconnexionTelnet) ) >= telnetTimeDuree )
      premiereConnexionTelnet = 1;                 // Prêt pour une nouvelle connexion

}

//#####  ----->  Banner 1ere Connexion : Serial
void PremiereConnexionSerial()
{
 //#####  ----->  Serial Connexion
  etatActuelConnexionSerial = Serial;
  if (etatActuelConnexionSerial && !etatPrecedentConnexionSerial) {
          Serial.println(F("\033[1;32;40m"));
          Serial.println(F(" #############################################################"));
          Serial.println(F(" ###  Prise IP  . . . . . . . . . . . .  Connexion Serial  ###"));
          Serial.println(F(" ###  Arduino UNO R4 - Ethernet 2                   IoToS  ###"));
          Serial.println(F(" #############################################################"));       
          Serial.println(F("\033[0;39;49m")); // Serial color defaut 
          Serial.println();             
          Serial.println(F(" Si besoin : Dans PUTTY le menu "));
          Serial.println(F(" Connexion --> Serial decocher : Flow control : None"));
          Serial.println(F(" - Serial Commande Line (cmdLine).. START  : OK !"));
          Serial.println(F("   Taper help pour voir les commandes disponibles"));
          Serial.println(F("   Taper print pour voir la configuration réseau"));
          Serial.println();
          Serial.print(F("> "));
          //premiereConnexionSerial = 0; 
  } 
  etatPrecedentConnexionSerial = etatActuelConnexionSerial;
//-----------------------------------------------------------------
}


/***********************************************************************************************************************************************************/
// #####   ----   Sous Programme Interruption : Watchdog
/***********************************************************************************************************************************************************/
// Pas de message envoye

/***********************************************************************************************************************************************************
* Sous Programme : PROGMEM SEND (envoi d'un texte en format PROGMEM)
************************************************************************************************************************************************************
* Entree    : const char PROGMEM myText[] = R"=====( \033[1;32;40m ... )=====";
* Sortie    : cmdLine (Serial / Telnet)
* Remarque  : - Convertit \n  en \r\n  (Telnet RFC 854)
*             - Convertit \033[ en ESC[ (séquence ANSI réelle 0x1B)
*             - Fonctionne avec raw string R"=====( )=====" 
***********************************************************************************************************************************************************/
void PrintProgmem(const char* p) {
  const size_t BUF_SIZE = 256;
  static char  buf[BUF_SIZE];
  uint16_t     idx = 0;

  auto flush = [&]() {
    if (idx > 0) {
      cmdLine.write((uint8_t*)buf, idx);
      idx = 0;
    }
  };

  auto pushByte = [&](char b) {
    if (idx >= BUF_SIZE - 1) flush();
    buf[idx++] = b;
  };

  char c;
  while ((c = (char)pgm_read_byte(p++))) {

    // --- Détection séquence  \033  (4 caractères: \ 0 3 3) -----------------
    if (c == '\\') {
      char c1 = (char)pgm_read_byte(p);
      if (c1 == '0') {
        char c2 = (char)pgm_read_byte(p + 1);
        if (c2 == '3') {
          char c3 = (char)pgm_read_byte(p + 2);
          if (c3 == '3') {
            // trouvé \033 → émet ESC (0x1B) et saute les 3 chars suivants
            p += 3;
            pushByte(0x1B);
            continue;
          }
        }
      }
      // backslash ordinaire → copie tel quel
      pushByte(c);
      continue;
    }

    // --- Conversion \n → \r\n (Telnet) --------------------------------------
    if (c == '\n') {
      if (idx >= BUF_SIZE - 2) flush();
      buf[idx++] = '\r';
      buf[idx++] = '\n';
      continue;
    }

    // --- Caractère ordinaire ------------------------------------------------
    pushByte(c);
  }

  flush();
}
/***********************************************************************************************************************************************************
* Sous Programme : Datalogger
************************************************************************************************************************************************************
* Entree : X
* Sortie : Sauvegarde fichier SD en Excel (Fichier CSV)
***********************************************************************************************************************************************************/
void Datalogger()
{
  cmdLine.print( F("*****  Enregistrement Data sur le fichier : "));    cmdLine.println( conf.fichierEnCours);

  if (!sd.exists( conf.fichierEnCours ) ) {
      cmdLine.println( F("Le fichier n'existe pas !!"));
      return;
  }
  
  FsFile file = sd.open(conf.fichierEnCours, O_WRONLY | O_APPEND);
  if (!conf.fichierEnCours)
      {
        cmdLine.println( F("Erreur ouverture du fichier"));
        return;
      }
// Creation d'une ligne du fichier Excel
// Compteur
  //file.print(conf.compteurHorodateur);
  //compteurHorodateur++;
  //file.print(";");
  //Serial.print("Compteur  : ");                     Serial.println(compteurHorodateur); 

// Affichage date
  timeClient.update();

  file.print(timeClient.getFormattedDate() );
  file.print(";"); 
  cmdLine.print( F("Date                   : "));     cmdLine.println( timeClient.getFormattedDate());
  
  file.print(timeClient.getFormattedTime());                                                                       // GET Heure : 12:22:01
  file.print(";");  
  cmdLine.print( F("Heure                  : "));     cmdLine.println( timeClient.getFormattedTime() ); 

  file.print(conf.led == 0 ? "OFF" : "ON");
  //-----------------
  
  //file.write("\r\n", 2);   // écrit 2 octets : 0x0D 0x0A
  file.println();   // écrit 2 octets : 0x0D 0x0A   
  
  //-----------------
  file.println();
  cmdLine.print( F("Taille du fichier      : "));     cmdLine.println( file.size() );
  file.close();
  cmdLine.println( F("*******************************************")); cmdLine.println(); cmdLine.println();
  cmdLine.print( F("> "));
}

///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////     Fonction commune       ////////////////////////////////////////////////////////////////////////////////////////////////////////
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// HELPER FUNCTIONS //
////////////////////////////////////////////////////////////////////////////////////////////////////
byte parseNibble(const char *arg) {
  if (*arg >= '0' && *arg <= '9') {
    return *arg - '0';
  } else if (*arg >= 'a' && *arg <= 'f') {
    return *arg - 'a' + 0x0a;
  } else if (*arg >= 'A' && *arg <= 'F') {
    return *arg - 'A' + 0x0a;
  }
  return 0;
} 
////////////////////////////////////////////////////////////////////////////////////////////////////
byte parseByte(const char *arg) {
  byte ret = parseNibble(arg++) << 4;
  return ret + parseNibble(arg);
}

void PrintMAC(uint8_t mac[6])
{
  for (int i = 0; i < 6; ++i) {
    if (i > 0) {
        cmdLine.print( ':');
    }
    if (mac[i] < 0x10) {
        cmdLine.print( '0');
    }
    Serial.print(mac[i], HEX);     clientTelnet.print(mac[i], HEX);
  }
}  

/*************************************************************************************************************
* Sous Programme : TestValidityIPadress
*------------------------------------------------------------------------------------------------------------
* Description    : Vérifie la cohérence réseau entre l'adresse IP et la passerelle (gateway)
*                  au regard du masque de sous-réseau.
*                  Pour chaque bit à '1' dans le masque, le bit correspondant de l'IP
*                  doit être identique au bit correspondant de la gateway.
*                  (Ils doivent appartenir au même sous-réseau.)
*------------------------------------------------------------------------------------------------------------
* Entrees        : IPAddress ip      : adresse IP locale         ex. { 192, 168,   0,   1 }
*                  IPAddress subnet  : masque de sous-réseau     ex. { 255, 255, 255,   0 }
*                  IPAddress gateway : adresse de la passerelle  ex. { 192, 168,   0, 254 }
*------------------------------------------------------------------------------------------------------------
* Sortie         : boolean testIP
*                    1 (true)  : IP et gateway cohérentes avec le masque → configuration valide
*                    0 (false) : incohérence détectée              → erreur de paramétrage réseau
*------------------------------------------------------------------------------------------------------------
* Machine d'état : Sans machine d'état formelle.
*                  Logique de vote binaire bit à bit :
*                    Phase 1 — initialisation : testIP = 1 (présumé valide)
*                    Phase 2 — analyse        : pour chaque bit masqué à '1',
*                                               comparaison ip[bit] == gateway[bit]
*                    Phase 3 — verdict        : premier bit discordant → testIP = 0 (définitif,
*                                               ne peut plus repasser à 1 par construction)
*------------------------------------------------------------------------------------------------------------
* Variables locales :
*    j          [int, 0..3]      : index d'octet (4 octets de l'adresse IPv4)
*    i          [int, 0..7]      : index de bit dans l'octet (bit 0 = LSB)
*    t_ip       [unsigned char]  : bit isolé de ip[j]      après décalage et masquage
*    t_subnet   [unsigned char]  : bit isolé de subnet[j]  après décalage et masquage
*    t_gateway  [unsigned char]  : bit isolé de gateway[j] après décalage et masquage
*    testIP     [boolean]        : accumulateur de validité (1=OK, 0=erreur), valeur de retour
*------------------------------------------------------------------------------------------------------------
* Remarques      : - Paramètres de type IPAddress, accessibles par l'opérateur [].
*                  - Une fois testIP mis à 0, il ne peut plus repasser à 1 (AND cumulatif).
*                  - La fonction ne modifie aucune variable globale.
*                  - Complexité : O(4×8) = 32 itérations fixes, indépendamment des valeurs.
*************************************************************************************************************/
boolean TestValidityIPadress(IPAddress ip, IPAddress subnet, IPAddress gateway)
{
  boolean testIP = 1;                               // On initialise la sortie testIP a 1 pour valid
  for (int j = 0 ; j <= 3 ; j++)                   // quatre boucle pour les quatres chiffre de l'IP ip(192, 169, 0, 1);
    {
      for (int i = 0 ; i <= 7 ; i++)               // 8 boucle pour les 8 valeur binaire
        {
          unsigned char t_ip = 0;                  // initialisation des valeur temporaire
          unsigned char t_subnet = 0;
          unsigned char t_gateway = 0;
          t_subnet = subnet[j] >> i;               // On decale le masque de sous reseau de i vers la droite
          t_subnet = t_subnet & 0b00000001;        // On masque le dernier bit

          if ( t_subnet == 0b00000001)             // si bit du masque est a 1 on test ip[i] == gateway[i]
            {
              t_ip = ip[j] >> i;
              t_ip = t_ip & 0b00000001;
              t_gateway = gateway[j] >> i;
              t_gateway = t_gateway & 0b00000001;
              if ( t_ip == t_gateway) testIP = testIP & 1;  // bit IP == bit gateway : on laisse testIP a 1
              else testIP = 0;                              // bit discordant : testIP = 0 definitif
            }
        }
    }
  return testIP;
}


/* Solution avec IA
boolean TestValidityIPadress(IPAddress ip, IPAddress subnet, IPAddress gateway)
{
  return ( (ip & subnet) == (gateway & subnet) );
}

ou

  if ((conf.ip[0] & conf.subnet[0]) == (conf.gateway[0] & conf.subnet[0]) &&
      (conf.ip[1] & conf.subnet[1]) == (conf.gateway[1] & conf.subnet[1]) &&
      (conf.ip[2] & conf.subnet[2]) == (conf.gateway[2] & conf.subnet[2]) &&
      (conf.ip[3] & conf.subnet[3]) == (conf.gateway[3] & conf.subnet[3])) {
    Serial.println("L'adresse IP est sur le même réseau que la passerelle.");
  } else {
    Serial.println("L'adresse IP n'est pas sur le même réseau que la passerelle.");
  }




*/