/*
   Bibliothèque de fonctions qui manipulent un fichier html pour l'envoyer
   sur la carte éthernet après des transformations éventuelles
*/


#include <string.h>
#include <SPI.h>
#include <Ethernet.h>
#include <SD.h>
#include "trthtml.h"

#define TAILLEMAX_LIGNE  100

/*
  Valeurs possibles pour DEBUG
  // pas de #define DEBUG : même fonctionnement qu'avec DEBUG à 0
  #define DEBUG   0 // pas de messages sur le moniteur série
  #define DEBUG   1 // affichage seulement des message indiquant un envoi ou une réception
  #define DEBUG   2 // on affiche en plus les caractères reçus par la carte arduino
  #define DEBUG   3 // on affiche également les caractères envoyés par la carte arduino
*/

//#define DEBUG   3

char ligne [TAILLEMAX_LIGNE + 2];

#ifdef DEBUG
    #if DEBUG > 0
        // variables pour afficher < ou > au début des lignes de debug
        char carprec_env = '\n';
        char carprec_rec = '\n';
    #endif
#endif



/*
   Partie 1 : fonctions permettant a réception de données depuis la carte éthernet
*/



// lecture d'un caractère envoyé par le client et affichage éventuel sur le moniteur série

char lire_car_client (EthernetClient client)
{
    char c;  // caractère lu


    // lire un caractère sur le port éthernet
    c = client.read ();

#ifdef DEBUG
    // si affichage des messages reçus
    #if DEBUG > 1
        // si nouvelle ligne
        if (carprec_rec == '\n')
            // l'indiquer sur le moniteur série
            Serial.print ("< ");

        // afficher le caractère reçu sur le moniteur série
        if (c != EOF)
            Serial.print (c);

        // le mémoriser pour le cas où se serait un changement de ligne
        carprec_rec = c;
    #endif
#endif

    // retourner le caractère lu
    return (c);
}


// lecture et mémorisation de l'url de la page web demandée par le client

void lire_url (EthernetClient client, char *url, int taille)
{
    int  i;  // compteur, pour récupérer l'url
    char c;  // caractère lu


    // lire la première ligne jusqu'au /
    do
        c = lire_car_client (client);
    while (c != '/' && c != EOF);

    // position maximale du dernier caractère du nom de fichier
    taille = taille - 2;

    // se positionner au début de l'url
    i = 0;

    // lire le caractère qui suit le /
    c = lire_car_client (client);

    // tant que le nom du fichier n'est pas fini
    while (c != ' ' && c != '?' && c != EOF && i < taille)
    {
        // mémoriser le dernier caractère lu
        url [i++] = c;

        // lire le caractère suivant
        c = lire_car_client (client);
    }

    // fin du nom de fichier
    url [i++] = '\0';

    // si paramètres get
    if (c == '?')
    {
        // lire et mémoriser la fin de ligne
        c = lire_car_client (client);

        while (c != ' ' && c != '\n' && c != EOF && i <= taille)
        {
            // mémoriser le dernier caractère lu
            url [i++] = c;

            // lire le caractère suivant
            c = lire_car_client (client);
        }
    }

    // terminer l'url
    url [i] = '\0';
}


// se positionner sur la zone des cookies de la trame envoyée par le client

int trouve_cookies (EthernetClient client)
{
    char mot_cle [] = "cookie:";
    int  i; // compteur;
    char c; // caractère lu


    // lire un caractère
    c = lire_car_client (client);

    // tant qu'on n'a pas trouvé de ligne vide
    while (c != '\r' && c != EOF)
    {
        // se positionner au début du mot clé cherché
        i = 0;

        // tant qu'on trouve des caractères du mot clé
        while (tolower (c) == mot_cle [i])
        {
            // passer au caractère suivant
            c = lire_car_client (client);
            i++;
        }

        // si mot clé trouvé en entier
        if (mot_cle [i] == '\0')
            // succès dans la recherche des cookies
            return (1);
        // sinon
        else
        {
            // aller en fin de ligne
            while (c != '\n' && c != EOF)
                // lire un caractère
                c = lire_car_client (client);
        }

        // lire le premier caractère de la ligne suivante
        c = lire_car_client (client);
    }

    // échec : cookies cherchés mais non trouvés
    return (-1);
}


// lecture et mémorisation des cookies envoyés par le client

void lire_cookies (EthernetClient client, char *cookie, int taille)
{

    int  i; // compteur, pour récupérer les cookies
    char c; // caractère lu
    char carprec; // caractère précédent


    // lire un caractère
    c = lire_car_client (client);

    // se positionner au début de la liste des cookies
    i = 0;

    // position maximale du dernier caractère de la liste
    taille --;

    // tant que non fin de trame et non fin de ligne
    while (c != EOF && c != '\r' && c != '\n' && i < taille)
    {
        // mémoriser le caractère lu
        cookie [i++] = c;
        carprec = c;

        // lire le caractère suivant
        c = lire_car_client (client);

        // sauter l'espace entre un ; et le variable suivante
        if (c == ' ' && carprec == ';')
            c = lire_car_client (client);
    }

    // terminer la chaine de caractères des cookies
    cookie [i] = '\0';
}


// se positionner sur la zone POST de la trame envoyée par le client

void posit_zone_post (EthernetClient client)
{
    char c;       // dernier caractère lu
    char carprec; // caractère précédent


    // lire un caractère
    c = lire_car_client (client);

    // rechercher un \n suivi d'un \r , ce qui indique une ligne vide
    while ((c != '\r' || carprec != '\n') && c != EOF)
    {
        // mémoriser le dernier caractère lu
        carprec = c;

        // lire le suivant
        c = lire_car_client (client);
    }

    // si on a bien trouvé cette séquence de caractères
    if (c == '\r')
        // passer à la ligne suivante
        c = lire_car_client (client);
}


// lecture et mémorisation des variables POST dans la trame envoyée par le client

void lire_post (EthernetClient client, char *post, int taille)
{
    int  i; // compteur, pour récupérer les variables post
    char c; // caractère lu


    // lire un caractère
    c = lire_car_client (client);

    // se positionner au début de la chaine post
    i = 0;

    // position maximale du dernier caractère de la liste des variables post
    taille --;

    // tant que non fin de trame et non fin de ligne
    while (c != EOF && c != '\r' && c != '\n' && i < taille)
    {
        // mémoriser le caractère lu
        post [i++] = c;

        // lire le caractère suivant
        c = lire_car_client (client);
    }

    // terminer la chaine de caractères post
    post [i] = '\0';
}


// lecture d'une demande de page web par le client

void lire_trame_client (EthernetClient client, char *url=0, int taille=14,
                        char *post=0, int tailpost=0, char *cookie=0, int tailcook=0)
{
   char c;      // caractère lu
   char cardeb; // tout premier caractère du message (pour savoir si get ou post)
   int  cooktrouve = 0;   // indique si on a cherché et trouvé des cookies


#ifdef DEBUG
  #if DEBUG > 0
    Serial.println (F("Réception demande du client"));

    // initialisation
    carprec_rec = '\n';
  #endif
#endif

    // lire le premier caractère reçu
    c = lire_car_client (client);

    // si récupération de l'url
    if (url)
    {
        // mémoriser si get ou post
        cardeb = c;

        // le faire
        lire_url (client, url, taille);

        // si cookies à récupérer
        if (cookie)
        {
            // rechercher si des cookies ont été envoyés par le client
            cooktrouve = trouve_cookies (client);

            // si cookies trouvés
            if (cooktrouve > 0)
                // en mémoriser la liste
                lire_cookies (client, cookie, tailcook);
            // sinon
            else
                // liste des cookies vide
                *cookie = '\0';
        }

        // si variables post à récupérer
        if (post)
        {
            // si trame envoyée avec la méthode post
            if (toupper (cardeb) == 'P')
            {
                // si on n'est pas déjà à l'endroit de la zone post
                // (on y est si on a cherché des cookies alors qu'il n'y en a pas)
                if (cooktrouve >= 0)
                    // se positionner sur la zone post
                    posit_zone_post (client);

                // mémoriser la liste
                lire_post (client, post, tailpost);
            }
            // sinon
            else
                // liste des variables post vide
                *post = '\0';
        }
    }

    // tant qu'on trouve des caractères à lire
    while (c != EOF)
    {
        // lire le caractère suivant
        c = lire_car_client (client);
    }
}



/*
   Partie 2 : fonctions permettant l'envoi de données sur la carte éthernet
*/



// envoi d'un caractère sur le port ethernet avec copie éventuelle sur le moniteur série

void envoie_car (char car, EthernetClient client)
{
    client.print (car);

#ifdef DEBUG
    // si affichage des messages envoyés
    #if DEBUG > 2
        // si nouvelle ligne
        if (carprec_env == '\n')
            // l'indiquer sur le moniteur série
            Serial.print ("> ");

        // afficher le caractère recu sur le moniteur série
        Serial.print (car);

        // le mémoriser pour le cas où se serait un changement de ligne
        carprec_env = car;
    #endif
#endif
}


// envoi d'un message sur le port ethernet

void envoie_trame (String trame, EthernetClient client)
{
    int i;  // numéro du caractère dans la trame

    // initialisation
    i = 0;

    // tant que non fin de trame
    while (trame [i])
    {
        // envoyer un caractère
        envoie_car (trame [i], client);

        // passer au caractère suivant
        i++;
    }
}


// envoi d'un message sur le port ethernet suivi d'un passage à la ligne

void envoieln_trame (String trame, EthernetClient client)
{
    envoie_trame (trame, client);
    envoie_car ('\n', client);
}


// envoi de l'entête d'une page web acceptée par le serveur

void envoie_entete_reponse (EthernetClient client, char *url=0)
{
    int pospoint;  // pour localiser le suffixe du nom de fichier

#ifdef DEBUG
  #if DEBUG > 0
    Serial.println (F("On envoie au client"));
  #endif
#endif

    // Tout d'abord le code de réponse 200 = réussite
    envoieln_trame (F("HTTP/1.1 200 OK"), client);

    // Puis le type mime du contenu renvoyé, du html
    envoie_trame (F("Content-Type: text/"), client);

    // si on a passé l'url en paramètre
    if (url)
    {
        // localiser le suffixe du fichier
        pospoint = strpos (url, ".");

        // si c'est un fichier css, le préciser
        if (strcmp (tolower (url + pospoint + 1), "css") == 0)
            envoieln_trame (F("css"), client);
        // sinon
        else
            // par défaut, c'est un fichier HTML
            envoieln_trame (F("html"), client);
    }
    // idem si l'url n'est pas précisée
    else
        envoieln_trame (F("html"), client);

    // fin d'envoi de l'entête
    envoieln_trame (F("Connection: close"), client);
    envoie_car ('\n', client);
}



/*
   Partie 3 : fonction permettant :de manipuler les chaines de caractères
*/



// recherche la position de la sous chaine rech dans la chaine de caractères chaine

int strpos (char *chaine, char *rech)
{
    int i, j;


    // cas particulier : recherche d'une sous chaine vide
    if (*rech == '\0')
        return (0);

    // commencer au début de la chaine à analyser
    i = 0;

    // bouche jusqu'à sous chaine trouvée ou chaine analysée en entier
    while (1)
    {
        // commencer au début de la sous chaine
        j = 0;

        // tant qu'on trouve des caractères identiques dans les 2 chaines
        while (chaine [i + j] == rech [j])
        {
            // avancer d'un caractère
            j++;

            // si on est arrivé en fin de sous chaine
            if (rech [j] == '\0')
                // retourner sa position dans la chaine à analyser
                return i;
        }

        // si on est arrivé en fin de chaine à analyser
        if (chaine [i + j] == '\0')
            // retourner "sous chaine non trouvée"
            return -1;
        // sinon
        else
            // recommencer à partir du caractère suivant de la chaine à analyser
            i++;
    }
}


/* extraction d'une sous-chaine de caractères */

void substr (char *orig, char *dest, int debut, int fin)
{
    int i, j;


    // initialisation
    j = 0;

    // copie de la sous-chaine
    for (i = debut; i <= fin && orig [i]; i++)
        dest [j++] = orig [i];

    // terminer la chaine
    dest [j] = '\0';
}



/*
   Partie 4 : fonctions permettant de récupérer le valeurs des variables
   passées par les méthodes GET et POST
*/



// convertir un caractère hexadécimal en valeur numérique

int valhexa (char car)
{
    // transformer le caractère en chiffre
    car = car - '0';

    // si c'était une lettre (on a dépassé 9)
    if (car > 9)
        // corriger la valeur
        car = car - 7;

    // retourner le résultat
    return car;
}


// retourne la valeur d'une variable mémorisée dans une liste
// indique au niveau du code de retour si la variable a été trouvée

int lecvar (char *nom, char *liste, char *result, char separateur)
{
    int i, j;  // compteurs
    char car;  // caractère de la variable liste


    // se positionner au début de la liste des variables
    j = 0;

    // tant que non fin de liste
    while (liste [j])
    {
        // se positionner au début du nom de la variable
        i = 0;

        // tant que le nom carrespond à celui de la liste
        while (nom [i] == liste [j])
        {
            // avancer d'un caractère
            i++;
            j++;
        }

        // si fin du nom et on est arrivé sur un = dans la liste
        if (! nom [i] && liste [j] == '=')
        {
            // on va mémoriser la valeur de la variable
            i = 0;

            // avancer d'un caractère dans la liste
            j++;

            // tant qu'on n'est ni en fin de liste ni sur l'annonce d'une autre variable
            while (liste [j] && liste [j] != separateur)
            {
                car = liste [j++];

                if (car == '+' && separateur == '&')
                    car = ' ';
                else if (car == '%' && separateur == '&')
                    car = (valhexa (liste [j++]) * 16) + valhexa (liste [j++]);

                // mémoriser un caractère de la valeur de la variable et passer au suivant
                result [i++] = car;
            }

            // terminer la chaine de caractère contenant la valeur cherchée
            result [i] = '\0';

            // variable trouvée
            return 1;
        }
        // sinon
        else
        {
            // se positionner sur la variable suivante
            while (liste [j] && liste [j] != separateur)
                j++;
        }

        // s'il y a encore des variables à tester
        if (liste [j] == separateur)
            // aller sur le nom de la suivante
            j++;
    }

    // variable non trouvée, retourner une chaine vide
    *result = '\0';

    return 0;
}


// retourne la valeur d'une variable reçue par ma méthode GET

int lecvar_get (char *nom, char *liste, char *result)
{
    // la liste des paramètres est après le nom de la page web
    return (lecvar (nom, liste + strlen (liste) + 1, result, '&'));
}


// retourne la valeur d'une variable reçue par ma méthode POST

int lecvar_post (char *nom, char *liste, char *result)
{
    return (lecvar (nom, liste, result, '&'));
}


// retourne la valeur d'un cookie

int lecval_cookie (char *nom, char *liste, char *result)
{
    return (lecvar (nom, liste, result, ';'));
}



/*
   Partie 5 : fonctions permettant d'envoyer la page html
   dont la structure est mémorisée sur la carte SD en
   modifiant certaines parties de cette page.
*/



// lit une ligne dans le fichier HTML, la mémorise dans ligne

void lire_ligne (File descfic)
{
    char car;     // dernier caractère lu
    int  poslig;  // position dans la ligne


    // initialisation
    poslig = 0;

    // lire un caractère
    car = descfic.read();

    // tant que non fin de ligne et non fin de fichier et place disponible dans ligne
    while (car != '\n' && car != EOF && poslig < TAILLEMAX_LIGNE)
    {
        // mémoriser le caractère lu
        ligne [poslig ++] = car;

        // lire le caractère suivant
        car = descfic.read();
    }

    // si on n'est pas arrivé en fin de fichier
    if (car != EOF)
        // rajouter ce caractère
        ligne [poslig ++] = car;

    // terminer proprement la chaine de caractères
    ligne [poslig] = '\0';
}


// envoie sur le port éthernet le fichier HTML jusqu'à la ligne contenant chaine
// cette dernière ligne est copiée si le paramètre cop_derlig est à 1

void copie_jusque_chaine (File descfic, EthernetClient client, char *chaine, int cop_derlig = 0)
{
    // lire une ligne du fichier HTML
    lire_ligne (descfic);

    // tant qu'on a lu une ligne qui ne contient pas la chaine cherchée
    while (*ligne && strpos (ligne, chaine) < 0)
    {
        // envoyer cette ligne sur le port éthernet
        envoie_trame (ligne, client);

        // lire la ligne suivante
        lire_ligne (descfic);
    }

    // si demande d"envoi de la ligne contenant la chaine cherchée
    if (cop_derlig)
        // le faire
        envoie_trame (ligne, client);
}


// saute dans le fichier HTML les lignes jusqu'à celle contenant chaine

void sauter_jusque_chaine (File descfic, char *chaine)
{
    // lire une ligne du fichier HTML
    lire_ligne (descfic);

    // tant qu'on a lu une ligne qui ne contient pas la chaine cherchée
    while (*ligne && strpos (ligne, chaine) < 0)
        // lire la ligne suivante
        lire_ligne (descfic);
}


// envoie sur le port éthernet le fichier HTML jusqu'à la ligne contenant chaineorig
// et envoie également cette ligne en remplaçant chaineorig par chainedest

void coprep_chaine (File descfic, EthernetClient client, char *chaineorig, char *chainedest)
{
    int posit;  // position de la chaine cherchée dans la ligne
    int i;


    // lire une ligne du fichier HTML
    lire_ligne (descfic);

    // tant qu'on a lu une ligne qui ne contient pas la chaine cherchée
    while (*ligne && (posit = strpos (ligne, chaineorig)) < 0)
    {
        // envoyer cette ligne sur le port éthernet
        envoie_trame (ligne, client);

        // lire la ligne suivante
        lire_ligne (descfic);
    }

    // si non fin de fichier
    if (*ligne)
    {
        // envoyer la partie de la ligne avant le chaine trouvée
        for (i = 0; i < posit; i++)
            envoie_car (ligne [i], client);

        // envoyer la chaine de remplacement
        envoie_trame (chainedest, client);

        // envoyer la fin de la ligne
        i = i + strlen (chaineorig);
        envoie_trame (& ligne [i], client);
    }
}


// envoie sur le port éthernet la fin du fichier HTML

void copie_jusque_fin (File descfic, EthernetClient client)
{
    char car;  // dernier caractère lu


    // on simplifie le traitement en lisant caractère par caractère au lieu de ligne par ligne
    car = descfic.read ();

    // tant que non fin de fichier
    while (car != EOF)
    {
        // envoyer le dernier caractère lu
        envoie_car (car, client);

        // lire le suivant
        car = descfic.read ();
    }
}



/*
   Partie 6 : fonctions permettant de générer un menu commun aux pages du site à
   partir d'un fichier texte qui contient les noms des liens suivi de leur libellé
*/



// génère une indentation du nombre de caractères demandé

void indente (EthernetClient client, int indent=4)
{
    while (indent-- > 0)
        envoie_car (' ', client);
}


// génère le menu en HTML à partir du fichier texte qui le contient

void ajoute_menu (char *ficmenu, char *url, EthernetClient client, int indent=4, char *couleur="#FF8000")
{
    File descmenu;  // descripteur du fichier texte contenant la menu
    int  posblanc;  // position de l'espace entre le nom du lie et son libellé
    char *libelle;  // libellé du lien


    // ouvrir le fichier texte contenant le menu
    descmenu = SD.open (ficmenu, FILE_READ);

    // si l'ouverture s'est bien passée
    if (descmenu)
    {
        // lire la première ligne
        lire_ligne (descmenu);

        // tant que non fin de fichier
        while (*ligne)
        {
            // générer un saut de ligne (pour aérer le menu généré)
            indente (client, indent);
            envoieln_trame ("<br />", client);

            // si la ligne lue n'est pas vide
            if (*ligne != '\n')
            {
                // supprimer le passage à la ligne après le libellé du lien
                ligne [strlen (ligne) - 1] = '\0';

                // chercher la fin du nom du lien
                posblanc = strpos (ligne, " ");

                // et la marquer comme fin de chaine de caractères
                ligne [posblanc] = '\0';

                // cherché le début du libellé du lien
                libelle = ligne + posblanc + 1;

                while (*libelle == ' ')
                    libelle ++;

                // générer l'indentation en débur de ligne HTML
                indente (client, indent);

                // si on est sur la page correspondant au lien dans le menu
                if (strcmp (tolower (ligne), tolower (url)) == 0)
                {
                    // générer lien vers cette page comme page active
                    envoie_trame (F("<font color=\""), client);
                    envoie_trame (couleur, client);
                    envoie_trame ("\">", client);
                    envoie_trame (libelle, client);
                    envoie_trame (F("</font>"), client);
                }
                // sinon
                else
                {
                    // générer lien vers la page
                    envoie_trame (F("<a href=\""), client);
                    envoie_trame (ligne, client);
                    envoie_trame ("\">", client);
                    envoie_trame (libelle, client);
                    envoie_trame ("</a>", client);
                }

                // générer la fin de ligne
                envoieln_trame ("<br />", client);
            }

            // lire la ligne suivante dans le fichier menu
            lire_ligne (descmenu);
        }

        // terminé avec le fichier menu
        descmenu.close ();
    }
    // sinon message d'erreur
    else
    {
        envoieln_trame (F("Fichier menu "), client);
        envoieln_trame (ficmenu, client);
        envoieln_trame (F(" manquant"), client);
    }
}