//
//    █████    ██████    ██████╗   ██╗       ██╗   ██████╗   █████    ████████╗   ██╗   ██████    ███   ██╗
//   ██   ██╗  ██   ██╗  ██   ██╗  ██║       ██║  ██╔════╝  ██   ██╗     ██╔══╝   ██║  ██╔═══██╗  ████  ██║
//   ███████║  ██████╔╝  ██████╔╝  ██║       ██║  ██║       ███████║     ██║      ██║  ██║   ██║  ██╔██ ██║
//   ██╔══██║  ██╔═══╝   ██╔═══╝   ██║       ██║  ██║       ██╔══██║     ██║      ██║  ██║   ██║  ██║ ████║
//   ██║  ██║  ██║       ██║       ███████╗  ██║   ██████╗  ██║  ██║     ██║      ██║  ╚██████╔╝  ██║  ███║
//   ╚═╝  ╚═╝  ╚═╝       ╚═╝       ╚══════╝  ╚═╝   ╚═════╝  ╚═╝  ╚═╝     ╚═╝      ╚═╝   ╚═════╝   ╚═╝  ╚══╝
//
/*************************************************************************************************************************************************************
*  Sous Programme : ServeurWEB -- Moteur HTTP avec routeur par table
*  Fonction : Serveur WEB AJAX avec architecture de routage Flask-inspired
*  Version 4  (refonte routeur)
************************************************************************************************************************************************************
*  Entree :
*  boolean loginWEB;                                                  // ON / OFF : Login et Password du site WEB
*  char loginPasswordWEB[40];                                         // dXNlcjpwYXNz = 'user:pass' (user:pass) base64  /////  (admin:01_Admin YWRtaW46MDFfQWRtaW4=)   https://www.base64encode.org/
*
*  Sortie : client.Print ---> Ethernet HTML
*  Variables (configIP)
*  Variables modifiees (via AJAX ajax_outputs) :
*   conf.dhcp         (boolean)   -- Arduino configure en IP STATC = 0 // Arduino configure en IP DHCP = 1
*   conf.ip[4]        (uint8_t)   -- nouvelle adresse IP
*   conf.subnet[4]    (uint8_t)   -- nouveau masque de sous-reseau
*   conf.gateway[4]   (uint8_t)   -- nouvelle passerelle
*   conf.loginWEB     (boolean)   -- activation authentification WEB
*   conf.led          (boolean)   -- 0=OFF / 1=ON (via BoutonON/BoutonOFF)
************************************************************************************************************************************************************/

// Variable Moteur Serveur WEB
#define REQ_BUF_SZ   512                                              // taille de la memoire tampon utilisee pour capturer les requetes HTTP
char HTTP_req[REQ_BUF_SZ] = {0};                                      // buffered HTTP request stored as null terminated string
int req_index = 0;                                                    // index into HTTP_req buffer
unsigned long previousMillisWEB = 0;                                  // TimeOut Serveur WEB : variable pour un break dans la boucle du serveur WEB
char c = 0;                                                           // caractere recu par le client
//EthernetServer server(80);                                          // PORT du serveur WEB Definit dans prog principale
//boolean etatServeurWEB = 0;                                         // Variable connexion au serveur WEB --> definit dans LCD -- Permet de detecter serveur WEB actif

// Declaration Fonctions
void StrClear(char *str, int taille);                                 // Efface string
char StrContains(char *str, const char *sfind);                       // Detect chaine de caractere -- declaree, non utilisee dans le routeur (conservee pour compatibilite)
void PageWEB(EthernetClient &client);                                 // Gestion Pages WEB / AJAX
void SendFile(EthernetClient &client, const char *fichierSD);         // Envoi fichier SD vers client HTTP

// ============================================================================================================================
// TYPES ET CONSTANTES DU ROUTEUR HTTP
// ============================================================================================================================
// Methodes HTTP supportees
#define HTTP_GET   0
#define HTTP_POST  1
#define HTTP_ANY   2                                                   // accepte GET et POST (utilise pour le fallback SD)

// Type d'un handler de route : recoit le client HTTP actif
typedef void (*route_handler_t)(EthernetClient &);

// Structure d'une entree dans la table de routes
// url    : chaine de detection dans HTTP_req (en RAM -- PROGMEM non utilise ici car pointers de fonctions)
// method : HTTP_GET / HTTP_POST / HTTP_ANY
// handler: fonction appelee si la route correspond
typedef struct {
    const char       *url;
    uint8_t           method;
    route_handler_t   handler;
} route_t;

// ============================================================================================================================
// FORWARD DECLARATIONS DES HANDLERS SYSTEME IoToS
// ============================================================================================================================
void Handler_Ajax_Outputs  (EthernetClient &client);   // Reception config IP depuis ConfIP.html → EEPROM → Reset
void Handler_Ajax_Led      (EthernetClient &client);   // Polling etat LED depuis index.html (ajax_inputs?led)
void Handler_Ajax_Inputs   (EthernetClient &client);   // Envoi config IP complete vers ConfIP.html (ajax_inputs)
void Handler_Bouton_ON     (EthernetClient &client);   // Commande LED ON  depuis index.html
void Handler_Bouton_OFF    (EthernetClient &client);   // Commande LED OFF depuis index.html
void Handler_Favicon       (EthernetClient &client);   // Reponse 404 pour favicon.ico
void Handler_SD_File       (EthernetClient &client);   // Fallback generique : sert tout fichier present sur la SD

// ============================================================================================================================
// EXTERN ROUTES UTILISATEUR
// user_routes[] et user_routes_count sont definis dans IoToS_User.h (inclus apres ce fichier)
// Declares extern ICI car PageWEB() est dans ce fichier et doit les voir a la compilation
// ============================================================================================================================
//             PageWEB() utilise user_routes[] -- l'extern doit etre visible dans ce fichier
extern const route_t user_routes[];
extern size_t        user_routes_count;

// ============================================================================================================================
// TABLE DE ROUTES SYSTEME IoToS
// Ordre important : les routes les plus specifiques sont en PREMIER
//   ajax_inputs?led  AVANT  ajax_inputs  (sinon ajax_inputs attrape les deux)
//   BoutonON / BoutonOFF  AVANT  GET / ? (le fallback index attraperait /?Bouton)
//   favicon.ico  AVANT  SD fallback
//   SD fallback en DERNIER (HTTP_ANY, url vide = toujours vrai)
// ============================================================================================================================
const route_t iotos_routes[] = {
    { "ajax_outputs",   HTTP_GET,  Handler_Ajax_Outputs },   // config IP entrant  → EEPROM + Reset
    { "ajax_inputs?led",HTTP_GET,  Handler_Ajax_Led     },   // polling LED        → XML <LED>
    { "ajax_inputs",    HTTP_GET,  Handler_Ajax_Inputs  },   // config IP sortant  → XML complet
    { "favicon.ico",    HTTP_ANY,  Handler_Favicon      },   // favicon            → 404
    //             ces routes sont definies dans user_routes[] (IoToS_User.h) avec priorite
    { "",               HTTP_ANY,  Handler_SD_File      },   // fallback SD        → tout fichier sur SD
};
const uint8_t iotos_routes_count = sizeof(iotos_routes) / sizeof(iotos_routes[0]);

/*************************************************************************************************************************************************************
* Sous Programme : ServeurWEB
*=============================================================================================================
* Fonction   : Point d'entree du serveur WEB -- accepte un client HTTP entrant,
*              gere l'authentification Basic (Base64) et route vers PageWEB()
*-----------------------------------------------------------------------------
* Entree     : EthernetClient &client  -- client HTTP fourni par server.available()
*-----------------------------------------------------------------------------
* Sortie     : Appel de PageWEB(client) si authentification OK
*              HTTP 401 Unauthorized si authentification refusee
*              cmdLine.println -- messages de debug dans le terminal
*-----------------------------------------------------------------------------
* Variables modifiees :
*   req_index         (int)           -- index courant dans le buffer HTTP_req
*   HTTP_req[]        (char[512])     -- buffer de la requete HTTP recue
*   previousMillisWEB (unsigned long) -- horodatage pour le timeout (5 secondes)
*   currentLineIsBlank(boolean)       -- detecte la fin des en-tetes HTTP
*-----------------------------------------------------------------------------
* Machine d'etat :
*   client connecte ?
*     OUI -> lire octets HTTP dans HTTP_req jusqu'a ligne vide
*       conf.loginWEB == 1 ?
*         OUI -> loginPasswordWEB present dans HTTP_req ?
*                  OUI -> PageWEB(client)
*                  NON -> HTTP 401 Unauthorized
*         NON -> PageWEB(client) directement
*       Timeout > 5s -> flush + stop + break
*     NON -> rien
*-----------------------------------------------------------------------------
* Commande : appelee depuis loop() si conf.SrvWEBswitch == 1
*************************************************************************************************************************************************************/
void ServeurWEB(EthernetClient &client)
{
  if (client) {                                                                                                    // client obtenu?
    Serial.println();  Serial.println( F("ooooo Serveur WEB : Nouveau Client connecte ...."));                     // Si un nouveau client se connecte,imprimer un message sur le port serie
    previousMillisWEB = millis();
    boolean currentLineIsBlank = true;                                                                             // SRV WEB actif --> pour Affichage LCD
    //etatServeurWEB = 1;                                                                                            // Variable pour le LCD
    while (client.connected()) {
      // Sortie de la boucle while en cas de bloquage pendant une minute
      if ( millis() - previousMillisWEB >= 5 * 1000) {                                                             // Test le fonctionnement pendant 5sec (en ms : Exemple 2 min = 2*60*1000)
        Serial.println( F("oo Serveur WEB : TimeOut connexion !!!!"));
        client.flush();
        client.stop();
        break;
      }

      if (client.available()) {                                                                                    // donnees client disponibles pour lire lire 1 octet (caractere) du clientla derniere ligne de la demande du client est vide et se termine par \ n
        c = client.read();                                                                                         // repondre au client seulement apres la derniere ligne recue  Tamponner la premiere partie de la requete HTTP dans le tableau HTTP_req (chaine)
                                                                                                                   // laisse le dernier element dans le tableau de 0 a null termine la chaine (REQ_BUF_SZ - 1)
        if (req_index < (REQ_BUF_SZ - 1)) {
          HTTP_req[req_index] = c;                                                                                 // enregistrer le caractere de requete HTTP
          req_index++;
        }
        //Serial.print(c);                                                                                         // Impression du caractere de requete HTTP sur le moniteur serie
        if (c == '\n' && currentLineIsBlank) {                                                                     // la derniere ligne de la requete client est vide et se termine par \ n
                                                                                                                   // repond au client seulement apres la derniere ligne recue// open requested web page file
          for (int i = 0 ; i < req_index ; i++) {
              Serial.print( HTTP_req[i]);                                                                          // Impression du caractere de requete HTTP sur le moniteur serie
          }

          // ============================================================================================================================
          // BLOC AUTH : Verification des identifiants Basic Auth (Base64)
          // ----------------------------------------------------------------------------------------------------------------------------
          // Donnee traitee  : HTTP_req[]                -- en-tetes HTTP complets recus du navigateur
          //                                                contient "Authorization: Basic dXNlcjpwYXNz" si login present
          // Resultat        : si credential trouve      -> PageWEB(client)
          //                   sinon                     -> HTTP 401 Unauthorized (le navigateur affiche la boite login)
          // Variables entree: HTTP_req[]                   (char[512])  -- buffer de la requete HTTP
          //                   conf.loginPasswordWEB        (char[40])   -- credential Base64 attendu ex: "dXNlcjpwYXNz"
          // Variables sortie: PageWEB(client)           -- routage vers les pages si authentifie
          //                   client.println()          -- reponse HTTP 401 si echec
          // ============================================================================================================================
          if (conf.loginWEB == 1)  {                                                                               // Selecteur ON /OFF Login password
            if (strstr(HTTP_req, conf.loginPasswordWEB) != NULL)                                                   // checking if header is valid : dXNlcjpwYXNz = 'user:pass' (user:pass) base64 encode Finding the right credential string, then loads web page
            {                                                                                                      // https://www.base64encode.org/

              PageWEB(client);

            }
            // Utilisateur ou mot de passe incorrect, la requete HTTP echoue donc ...
            else  {
              client.println(F("HTTP/1.1 401 Unauthorized"));
              client.println(F("WWW-Authenticate: Basic realm=\"Secure\""));
              client.println(F("Content-Type: text/html"));
              client.println();
              client.println(F("<html>Authentication failed</html>"));
            } // Fin else IF Login / user

          } // Fin selecteur ON Login password
          else  {                                                                                                  // Selecteur OFF login password
            PageWEB(client);
          } // Fin selecteur Login Password

          //delay(10);

          // reinitialiser l'index du tampon et tous les elements du tampon a 0
          req_index = 0;
          StrClear(HTTP_req, REQ_BUF_SZ);
          //client.flush();
          client.stop();
          break;
        } // end if (c == '\n' && currentLineIsBlank)


        if (c == '\n') {                                                                                         // chaque ligne de texte recue du client se termine par \r \n
          // dernier caractere en ligne du texte recu Commencer une nouvelle ligne avec le prochain caractere lu
          currentLineIsBlank = true;
        }
        else if (c != '\r') {                                                                                    // un caractere de texte a ete recu du client
          currentLineIsBlank = false;
        }
      } // end if (client.available())
    } // end while (client.connected())
    //delay(10);                                                                                                 // donnez au navigateur le temps de recevoir les donnees
    //while (client.available()) client.read();
    //client.flush();
    client.stop();                                                                                               // close the connection
    Serial.println( F("ooooo Client Deconnecte (Fin boucle)"));
    Serial.println( F("-------------------------------------------------------"));
    Serial.println();
    Serial.print( F("> "));
  } // end if (client)
}

/*************************************************************************************************************************************************************
* Sous Programme : StrClear
*=============================================================================================================
* Fonction   : Efface un tableau de caracteres en mettant tous les elements a 0
*-----------------------------------------------------------------------------
* Entree     : char *str    -- pointeur vers le tableau a effacer
*              int taille   -- nombre d'octets a mettre a 0
*-----------------------------------------------------------------------------
* Sortie     : str[]        -- tous les elements mis a 0 (null)
*-----------------------------------------------------------------------------
* Variables modifiees :
*   str[0..taille-1]  -- mis a 0 octet par octet
*-----------------------------------------------------------------------------
* Utilisation : StrClear(HTTP_req, REQ_BUF_SZ) apres traitement de la requete
*************************************************************************************************************************************************************/
void StrClear(char *str, int taille)
{
  for (int i = 0; i < taille; i++) {
    str[i] = 0;
  }
}

/*************************************************************************************************************************************************************
* Sous Programme : SendFile
*=============================================================================================================
* Fonction   : Lit un fichier sur la carte SD et l'envoie par blocs au client Ethernet
*              Envoi optimise par blocs de 512 octets (TAILLE_BUF)
*-----------------------------------------------------------------------------
* Entrees    : EthernetClient &client      -- client HTTP destination (navigateur)
*              const char *fichierSD       -- nom du fichier a lire sur la SD
*                                            ex: "index.html" ou "ConfIP.html"
*-----------------------------------------------------------------------------
* Sortie     : client.write(buffer, n)    -- envoi du contenu au navigateur
*              cmdLine.println            -- messages de progression dans le terminal
*-----------------------------------------------------------------------------
* Variables locales :
*   pageWeb     (FsFile)      -- objet fichier SdFat pour la lecture (O_RDONLY)
*   clientBuf[] (byte[512])   -- buffer de lecture par blocs (TAILLE_BUF=512)
*   n           (int)         -- nombre d'octets lus au dernier read()
*-----------------------------------------------------------------------------
* Algorithme :
*   1. Verifier l'existence du fichier (sd.exists)
*   2. Ouvrir en lecture seule (O_RDONLY)
*   3. Boucle : lire 512 octets -> envoyer au client -> repeter jusqu'a EOF (n<=0)
*   4. Fermer le fichier
*************************************************************************************************************************************************************/
void SendFile(EthernetClient &client, const char *fichierSD)
{
  // Verifie si le fichier existe sur la carte SD
  if (!sd.exists(fichierSD))
      {
        Serial.println( F("oo Fichier introuvable sur SD"));
        return;
      }

  #define TAILLE_BUF 512      // taille du buffer (multiple de 512 conseille pour SD)

  // Ouverture du fichier en lecture seule
  FsFile pageWeb = sd.open(fichierSD, O_RDONLY);

  if (!pageWeb)
      {
        Serial.println( F("oo Erreur ouverture fichier SD"));
        return;
      }

  byte clientBuf[TAILLE_BUF];

  Serial.println();
  Serial.print( F("Envoie Page WEB : "));
  Serial.println( fichierSD);

  // Lecture du fichier par blocs
  while (true)
      {
        int n = pageWeb.read(clientBuf, TAILLE_BUF);  // lit jusqu'a 512 octets

        if (n <= 0)                                   // fin de fichier
          break;

        client.write(clientBuf, n);                   // envoi au client Ethernet

        Serial.print( F("..."));                     // progression
      }

  // fermeture du fichier
  pageWeb.close();

  Serial.println();
  Serial.println( F("Fin envoi fichier"));
}

/*************************************************************************************************************************************************************
* Sous Programme : StrContains
*=============================================================================================================
* Fonction   : Recherche la sous-chaine sfind dans la chaine str
*              Equivalent simplifie de strstr() avec retour char (0 ou 1)
*              Conservee pour compatibilite -- le routeur utilise strstr() directement
*-----------------------------------------------------------------------------
* Entrees    : char *str         -- chaine source (buffer HTTP_req)
*              const char *sfind -- chaine a rechercher
*                                   ex: "ajax_inputs", "BoutonON", loginPasswordWEB
*-----------------------------------------------------------------------------
* Sortie     : char -- 1 = sous-chaine trouvee / 0 = non trouvee
*************************************************************************************************************************************************************/
char StrContains(char *str, const char *sfind)
{
  unsigned int found = 0;
  unsigned int index = 0;
  unsigned int len;

  len = strlen(str);

  if (strlen(sfind) > len) {
    return 0;
  }
  while (index < len) {
    if (str[index] == sfind[found]) {
      found++;
      if (strlen(sfind) == found) {
        return 1;
      }
    }
    else {
      found = 0;
    }
    index++;
  }
  return 0;
}

/*************************************************************************************************************************************************************
*  Description : Routeur HTTP principal -- point d'entree apres authentification
*  Fonction    : Parcourt d'abord user_routes[] (IoToS_User.h) puis iotos_routes[] (systeme)
*               Pour chaque route : compare l'url avec HTTP_req via strstr()
*               Appelle le handler de la premiere route qui correspond
*               Si aucune route ne correspond : Handler_SD_File() en fallback
*============================================================================================================*
*  Entree  : EthernetClient &client    -- client HTTP actif
*            HTTP_req[]  (global)      -- buffer de la requete HTTP deja rempli (512 octets)
*            user_routes[]             -- tableau de routes defini dans IoToS_User.h
*            user_routes_count         -- nombre de routes utilisateur
*            iotos_routes[]            -- tableau de routes systeme IoToS
*            iotos_routes_count        -- nombre de routes systeme
*  Sortie  : appel du handler correspondant (client.print/write → navigateur)
*            cmdLine.println           -- messages de debug dans le terminal
*-------------------------------------------------------------------------------------------------------------
* Machine d'etat :
*
*   ┌─────────────────────────────────────────────────────────┐
*   │  HTTP_req[] disponible (rempli par ServeurWEB)          │
*   └──────────────────────┬──────────────────────────────────┘
*                          │
*              ┌───────────▼────────────┐
*              │  Parcours user_routes  │  (IoToS_User.h -- routes applicatives)
*              │  i = 0 .. count-1      │
*              └───────────┬────────────┘
*                          │
*               url vide ──┼── strstr(HTTP_req, url) trouve ?
*               (always)   │              │
*                         OUI           NON ──► i++
*                          │
*              ┌───────────▼────────────┐
*              │  handler(client)       │ ──► return
*              └────────────────────────┘
*                          │
*              (si aucune user_route ne correspond)
*                          │
*              ┌───────────▼────────────┐
*              │  Parcours iotos_routes │  (routes systeme IoToS)
*              │  i = 0 .. count-1      │
*              └───────────┬────────────┘
*                          │
*               url vide ──┼── strstr(HTTP_req, url) trouve ?
*               (always)   │              │
*                         OUI           NON ──► i++
*                          │
*              ┌───────────▼────────────┐
*              │  handler(client)       │ ──► return
*              └────────────────────────┘
*
*               user_routes[] parcouru EN PREMIER (priorite utilisateur)
*               iotos_routes[] ensuite (routes systeme)
*               Handler_SD_File() est le dernier element de iotos_routes[] (url="", HTTP_ANY)
*************************************************************************************************************************************************************/
void PageWEB(EthernetClient &client)
{
  Serial.println(); Serial.println();
  Serial.println( F("oo PageWEB : recherche route..."));

  // ----------------------------------------------------------
  // Etape 1 : Routes utilisateur (definies dans IoToS_User.h)
  //           Priorite maximale -- parcourues en premier
  // ----------------------------------------------------------
  for (uint8_t i = 0; i < user_routes_count; i++) {
    const char *url = user_routes[i].url;
    bool url_match  = (url[0] == '\0') || (strstr(HTTP_req, url) != NULL);
    bool met_match  = (user_routes[i].method == HTTP_ANY) ||
                      ( (user_routes[i].method == HTTP_GET)  && (strstr(HTTP_req, "GET ")  != NULL) ) ||
                      ( (user_routes[i].method == HTTP_POST) && (strstr(HTTP_req, "POST ") != NULL) );
    if (url_match && met_match) {
      Serial.print( F("oo Route USER : ")); Serial.println(url);
      user_routes[i].handler(client);
      return;
    }
  }

  // ----------------------------------------------------------
  // Etape 2 : Routes systeme IoToS (iotos_routes[])
  //           Le dernier element (url="") est le fallback SD
  // ----------------------------------------------------------
  for (uint8_t i = 0; i < iotos_routes_count; i++) {
    const char *url = iotos_routes[i].url;
    bool url_match  = (url[0] == '\0') || (strstr(HTTP_req, url) != NULL);
    bool met_match  = (iotos_routes[i].method == HTTP_ANY) ||
                      ( (iotos_routes[i].method == HTTP_GET)  && (strstr(HTTP_req, "GET ")  != NULL) ) ||
                      ( (iotos_routes[i].method == HTTP_POST) && (strstr(HTTP_req, "POST ") != NULL) );
    if (url_match && met_match) {
      Serial.print( F("oo Route SYS  : ")); Serial.println(url[0] ? url : "(fallback SD)");
      iotos_routes[i].handler(client);
      return;
    }
  }

  // ----------------------------------------------------------
  // Aucune route ne correspond (ne devrait pas arriver car
  // le fallback SD est toujours en dernier avec url="")
  // ----------------------------------------------------------
  Serial.println( F("oo PageWEB : aucune route -- 404"));
  client.println(F("HTTP/1.1 404 Not Found"));
  client.println(F("Content-Type: text/plain"));
  client.println(F("Connection: close"));
  client.println();
} // Fin PageWEB


// ============================================================================================================================
// HANDLERS SYSTEME IoToS
// ============================================================================================================================

/*************************************************************************************************************************************************************
*  Description : Handler AJAX -- Reception configuration IP depuis ConfIP.html
*  Fonction    : Decode les parametres IP/MSR/GAT/DHCP/LOG depuis HTTP_req,
*               sauvegarde en EEPROM, affiche confirmation, lance ResetNow()
*============================================================================================================*
*  Entree  : EthernetClient &client  -- client HTTP actif
*            HTTP_req[] (global)     -- "GET /ajax_outputs&DHCP=0&IPA=192&...&LOG=0&nocache=xxx"
*  Sortie  : conf.dhcp / conf.ip[] / conf.subnet[] / conf.gateway[] / conf.loginWEB modifies
*            SaveConfEEPROM() + ResetNow() appeles
*            client.print()          -- confirmation HTML vers navigateur
*************************************************************************************************************************************************************/
void Handler_Ajax_Outputs(EthernetClient &client)
{
  // Analyse de la chaine de caracter Fonction strtok :  http://koor.fr/C/cstring/strtok.wp
  boolean error = 0;
  int test = 0;
  const char * separators = "GET /ajax_outputs&DHCP=IPABCDMSRGATLOG";           // on enleve de la chaine les caracter suivant Attention IP MSR GAT sans les chiffre !!!!
  char * strToken = strtok ( HTTP_req, separators );
  conf.dhcp  = atoi(strToken);

  for (int i = 0 ; i < 4 ; i++)
  {
    strToken = strtok ( NULL, separators );
    test = atoi(strToken);
    if ( test < 0 || test > 255 ) { error = 1; break; }
    conf.ip[i] = test;
  }
  for (int i = 0 ; i < 4 ; i++)
  {
    strToken = strtok ( NULL, separators );
    test = atoi(strToken);
    if ( test < 0 || test > 255 ) { error = 1; break; }
    conf.subnet[i] = test;
  }
  for (int i = 0 ; i < 4 ; i++)
  {
    strToken = strtok ( NULL, separators );
    test = atoi(strToken);
    if ( test < 0 || test > 255 ) { error = 1; break; }
    conf.gateway[i] = test;
  }
  strToken = strtok ( NULL, separators );
  conf.loginWEB = atoi(strToken);

  if (error == 1)
  {
    SetDefaultConf(nullptr);
    Serial.println( F("ERROR !! Parametre entre Invalide !")); Serial.println();
    client.print(F("<p> ERROR !! Parametre entre Invalide !!!!!!!! <p> "));
  }

  PrintConf("1");
  SaveConfEEPROM(nullptr);
  delay(200);
  Serial.println( F("Enregistrement de la configuration IP en EEPROM : Ok!")); Serial.println();

  client.print(F("<p style=\"text-align:left\"> Configuration Enregistre"));
  client.print(F("<br> L'Arduino UNO R4 est accessible dans quelques instant a l'adresse IP : "));
  if ( conf.dhcp == 0)
  {
    client.print(conf.ip[0], DEC);  client.print(".");
    client.print(conf.ip[1], DEC);  client.print(".");
    client.print(conf.ip[2], DEC);  client.print(".");
    client.print(conf.ip[3], DEC);
  }
  else client.print(F("DHCP OK !"));
  delay(500);
  client.print(F("<br><br> Reboot : Now ..............."));
  client.print(F("<br><br>Attendre 5 secondes<br><br></p>"));
  delay(200);
  client.flush();
  client.stop();
  Serial.println( F("------- RESET Arduino UNO R4  !!!! -------")); Serial.println();
  ResetNow(nullptr);
}

/*************************************************************************************************************************************************************
*  Description : Handler AJAX -- Polling periodique etat LED depuis index.html
*  Fonction    : Repond une reponse XML contenant l'etat courant de la LED (0 ou 1)
*               Declenche par "GET /ajax_inputs?led&nocache=xxx"
*               Intervalle cote navigateur : LED_POLL_MS (variable dans index.html)
*============================================================================================================*
*  Entree  : EthernetClient &client  -- client HTTP actif
*            led (global, boolean)   -- etat courant de la LED
*  Sortie  : client.print()          -- HTTP 200 + XML : <inputs><LED>0</LED></inputs>
*************************************************************************************************************************************************************/
void Handler_Ajax_Led(EthernetClient &client)
{
  Serial.println(F("oo Envoie Page WEB : AJAX : LED Status"));

  client.println(F("HTTP/1.1 200 OK"));
  client.println(F("Content-Type: text/xml"));
  client.println(F("Connection: close"));
  client.println();

  client.print(F("<?xml version = \"1.0\" ?>"));
  client.print(F("<inputs>"));
  client.print(F("<LED>"));
  client.print(led ? "1" : "0");
  client.print(F("</LED>"));
  client.print(F("</inputs>"));
}

/*************************************************************************************************************************************************************
*  Description : Handler AJAX -- Envoi configuration IP complete vers ConfIP.html
*  Fonction    : Repond une reponse XML contenant DHCP, IP, MSR, GAT, LOG
*               Declenche par "GET /ajax_inputs&nocache=xxx" (sans ?led)
*============================================================================================================*
*  Entree  : EthernetClient &client  -- client HTTP actif
*            conf (global, conf_t)   -- configuration reseau courante
*  Sortie  : client.print()          -- HTTP 200 + XML complet de la config IP
*************************************************************************************************************************************************************/
void Handler_Ajax_Inputs(EthernetClient &client)
{
  Serial.println( F("oo Envoie Page WEB : AJAX : Emission"));

  client.println(F("HTTP/1.1 200 OK"));
  client.println(F("Content-Type: text/xml"));
  client.println(F("Connection: close"));
  client.println();

  client.print(F("<?xml version = \"1.0\" ?>"));
  client.print(F("<inputs>"));

  client.print(F("<DHCP>")); client.print(conf.dhcp);       client.print(F("</DHCP>"));

  client.print(F("<IPA>")); client.print(conf.ip[0]);       client.print(F("</IPA>"));
  client.print(F("<IPB>")); client.print(conf.ip[1]);       client.print(F("</IPB>"));
  client.print(F("<IPC>")); client.print(conf.ip[2]);       client.print(F("</IPC>"));
  client.print(F("<IPD>")); client.print(conf.ip[3]);       client.print(F("</IPD>"));

  client.print(F("<MSRA>")); client.print(conf.subnet[0]);  client.print(F("</MSRA>"));
  client.print(F("<MSRB>")); client.print(conf.subnet[1]);  client.print(F("</MSRB>"));
  client.print(F("<MSRC>")); client.print(conf.subnet[2]);  client.print(F("</MSRC>"));
  client.print(F("<MSRD>")); client.print(conf.subnet[3]);  client.print(F("</MSRD>"));

  client.print(F("<GATA>")); client.print(conf.gateway[0]); client.print(F("</GATA>"));
  client.print(F("<GATB>")); client.print(conf.gateway[1]); client.print(F("</GATB>"));
  client.print(F("<GATC>")); client.print(conf.gateway[2]); client.print(F("</GATC>"));
  client.print(F("<GATD>")); client.print(conf.gateway[3]); client.print(F("</GATD>"));

  client.print(F("<LOG>")); client.print(conf.loginWEB);    client.print(F("</LOG>"));

  client.print(F("</inputs>"));
}

/*************************************************************************************************************************************************************
*  Description : Handler Bouton -- Commande LED ON depuis index.html
*  Fonction    : Met LED = 1, commande la sortie physique, enregistre dans le datalogger
*               si actif, puis envoie une redirection HTTP 302 vers /index.html
*============================================================================================================*
*  Entree  : EthernetClient &client  -- client HTTP actif
*            HTTP_req[]              -- "GET /?BoutonON HTTP/1.1"
*  Sortie  : led=1 / digitalWrite(pinled, HIGH) / Datalogger() si actif
*            HTTP 302 Found --> Location: /index.html
*            Le navigateur emet une nouvelle requete GET /index.html propre
*************************************************************************************************************************************************************/
void Handler_Bouton_ON(EthernetClient &client)
{
  led = 1;
  digitalWrite(pinled, HIGH);
  Serial.println( F("LED est ON"));
  if (conf.dataloggerSwitch == 1 ) Datalogger();
  // Handler_SD_File recevait HTTP_req[] pollue (BoutonON + ajax_inputs)
  // et extrayait "ajax_inputs" au lieu de "index.html" --> 404
  client.println(F("HTTP/1.1 302 Found"));
  client.println(F("Location: /index.html"));
  client.println(F("Connection: close"));
  client.println();
}

/*************************************************************************************************************************************************************
*  Description : Handler Bouton -- Commande LED OFF depuis index.html
*  Fonction    : Met LED = 0, commande la sortie physique, enregistre dans le datalogger
*               si actif, puis envoie une redirection HTTP 302 vers /index.html
*============================================================================================================*
*  Entree  : EthernetClient &client  -- client HTTP actif
*            HTTP_req[]              -- "GET /?BoutonOFF HTTP/1.1"
*  Sortie  : led=0 / digitalWrite(pinled, LOW) / Datalogger() si actif
*            HTTP 302 Found --> Location: /index.html
*            Le navigateur emet une nouvelle requete GET /index.html propre
*************************************************************************************************************************************************************/
void Handler_Bouton_OFF(EthernetClient &client)
{
  led = 0;
  digitalWrite(pinled, LOW);
  Serial.println( F("LED est OFF"));
  if (conf.dataloggerSwitch == 1 ) Datalogger();
  client.println(F("HTTP/1.1 302 Found"));
  client.println(F("Location: /index.html"));
  client.println(F("Connection: close"));
  client.println();
}

/*************************************************************************************************************************************************************
*  Description : Handler Favicon -- Reponse 404 pour /favicon.ico
*  Fonction    : Le navigateur demande automatiquement /favicon.ico apres chaque chargement.
*               Sans ce handler, "GET /favicon.ico" contient "GET /" et declencherait
*               l'envoi de index.html. On repond 404 Not Found.
*============================================================================================================*
*  Entree  : EthernetClient &client  -- client HTTP actif
*  Sortie  : client.println()        -- HTTP 404 Not Found
*************************************************************************************************************************************************************/
void Handler_Favicon(EthernetClient &client)
{
  Serial.println( F("oo Requete favicon.ico -> 404 (ignore)"));
  client.println(F("HTTP/1.1 404 Not Found"));
  client.println(F("Content-Type: text/plain"));
  client.println(F("Connection: close"));
  client.println();
}

/*************************************************************************************************************************************************************
*  Description : Handler SD generique -- Fallback file server pour tout fichier sur la SD
*  Fonction    : Extrait le chemin du fichier depuis HTTP_req (entre "GET /" et " HTTP/")
*               Cherche le fichier sur la carte SD et l'envoie via SendFile()
*               Si racine "/" ou "index.html" : renvoie "index.html"
*               Si fichier absent : repond 404
*               Remplace les routes hardcodees index.html et ConfIP.html :
*               tout fichier present sur SD est automatiquement accessible
*============================================================================================================*
*  Entree  : EthernetClient &client  -- client HTTP actif
*            HTTP_req[] (global)     -- requete HTTP brute ex: "GET /ConfIP.html HTTP/1.1"
*  Sortie  : client.println()        -- HTTP 200 + SendFile() si fichier trouve
*            client.println()        -- HTTP 404 si fichier absent
*-------------------------------------------------------------------------------------------------------------
* Machine d'etat :
*   Extraire chemin depuis HTTP_req
*     chemin == "/" ou vide  →  chemin = "index.html"
*     chemin commence par '/'  →  avancer d'un caractere (enlever le /)
*     sd.exists(chemin) ?
*       OUI  →  HTTP 200 + SendFile()
*       NON  →  HTTP 404
*************************************************************************************************************************************************************/
void Handler_SD_File(EthernetClient &client)
{
  // ----- Extraction du chemin depuis HTTP_req -----
  // HTTP_req contient : "GET /chemin.html HTTP/1.1\r\n..."
  // On cherche "GET /" puis on copie jusqu'a l'espace suivant

  char fichier[64] = {0};                                    // buffer chemin fichier (64 octets suffisent)
  const char *debut = strstr(HTTP_req, "GET /");             // localise "GET /"
  if (debut != NULL) {
    debut += 5;                                              // saute "GET /"
    // copier jusqu'a l'espace, "?" ou fin de chaine
    uint8_t idx = 0;
    while (*debut && *debut != ' ' && *debut != '?' && idx < 63) {
      fichier[idx++] = *debut++;
    }
    fichier[idx] = '\0';
  }

  // ----- Chemin racine ou vide → index.html -----
  if (fichier[0] == '\0') {
    strncpy(fichier, "index.html", 63);
  }

  Serial.print( F("oo Handler_SD_File : "));
  Serial.println(fichier);

  // ----- Fichier present sur SD ? -----
  if (!sd.exists(fichier)) {
    Serial.println( F("oo Fichier absent sur SD -> 404"));
    client.println(F("HTTP/1.1 404 Not Found"));
    client.println(F("Content-Type: text/plain"));
    client.println(F("Connection: close"));
    client.println();
    client.println(F("404 - Fichier non trouve"));
    return;
  }

  // ----- Determination du Content-Type selon extension -----
  const char *ext = strrchr(fichier, '.');
  if      (ext && strcmp(ext, ".html") == 0) client.println(F("HTTP/1.1 200 OK\r\nContent-Type: text/html\r\nConnection: close"));
  else if (ext && strcmp(ext, ".css")  == 0) client.println(F("HTTP/1.1 200 OK\r\nContent-Type: text/css\r\nConnection: close"));
  else if (ext && strcmp(ext, ".js")   == 0) client.println(F("HTTP/1.1 200 OK\r\nContent-Type: application/javascript\r\nConnection: close"));
  else if (ext && strcmp(ext, ".xml")  == 0) client.println(F("HTTP/1.1 200 OK\r\nContent-Type: text/xml\r\nConnection: close"));
  else                                        client.println(F("HTTP/1.1 200 OK\r\nContent-Type: application/octet-stream\r\nConnection: close"));
  client.println();                                          // ligne vide obligatoire separant entete et corps

  // ----- Envoi du fichier -----
  Serial.println( F("oo Envoie Entete de la page WEB"));
  SendFile(client, fichier);
}
