#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <strings.h>

#include "utils.h"
#include "cell.h"

#include "laby_lecture.h"

int laby_verif_fichier(char *nom_fichier,
                       unsigned long *total_cells,
                       unsigned long *total_links,
                       int (*fc_analyse_params) (char *param_lu,
                                                 char *buffer_erreur,
                                                 int maxlen_erreur),
                       int (*fc_params_ok) (char *buffer_erreur,
                                            int maxlen_erreur),
                       char *buffer_erreur, int maxlen_erreur)
{
    FILE *hinput;
    char s[250];
    char *t;
    unsigned long i, j, k;
    int ok, len;

    unsigned long no_courant = 0;
    unsigned long tag_courant = 0;

    hinput = fopen(nom_fichier, "rb");
    if (hinput == NULL) {
        snprintf(buffer_erreur, maxlen_erreur,
                 "Erreur de lecture du fichier <%s>.\n", nom_fichier);
        return -1;
    }

    *total_cells = 0;
    *total_links = 0;

    /* boucle principale de lecture */
    while (fgets(s, sizeof(s), hinput) != NULL) {
        len = strlen(s);
        if (len == 0)
            continue;

        /* supprimer dernier char = retour chariot : */
        if (s[len - 1] < 20)
            s[len - 1] = 0;

        /* ignorer les espaces et tabulations du début */
        t = s;
        while (t[0]) {
            if ((t[0] != ' ') && (t[0] != 9))
                break;
            t++;
        }
        if (strlen(t) == 0)
            continue;
        if (t[0] == ';')
            continue;

        /* arrivé ici, chaine valide => analyse */

        /* RAF : déclaration de paramètres :
           appel de la fonction d'analyse (passée en paramètre) */
        if (strstr(t, "params:") == t) {
            if (fc_analyse_params(t, buffer_erreur, maxlen_erreur)) {
                /* erreur lors de l'analyse des paramètres
                   => terminer */
                return -1;
            }
        }

        /* déclaration du nombre de cellules composant le labyrinthe */
        ok = sscanf(t, "total_cells:%lu", &i);
        if (ok == 1) {
            if (*total_cells) {
                snprintf
                    (buffer_erreur, maxlen_erreur,
                     "Erreur : deux fois déclaration "
                     "du nombre de cellules.\n");
                fclose(hinput);
                return -1;
            }
            if (i < 9) {
                snprintf
                    (buffer_erreur, maxlen_erreur,
                     "Erreur : le nombre de cellules "
                     "minimum acceptable est 9, ici il est déclaré : %lu.\n",
                     i);
                fclose(hinput);
                return -1;
            }
            *total_cells = i;
        }

        /* déclaration du nombre total de liens entre les cellules */
        ok = sscanf(t, "total_links:%lu", &i);
        if (ok == 1) {
            if (*total_links) {
                snprintf
                    (buffer_erreur, maxlen_erreur,
                     "Erreur : deux fois déclaration "
                     "du nombre total de liens entre les cellules.\n");
                fclose(hinput);
                return -1;
            }
            if (i < 9) {
                snprintf
                    (buffer_erreur, maxlen_erreur,
                     "Erreur : le nombre total de liens entre les cellules "
                     "minimum acceptable est 9, ici il est déclaré : %lu.\n",
                     i);
                fclose(hinput);
                return -1;
            }
            *total_links = i;
        }

        /* pour chaque cellule deux possibilités :
           avec ou sans tag */
        /* forcer k=0 car sinon k garde dernière valeur */
        k = 0;
        /* précaution car sscanf prend aussi ":w=" */
        if (strstr(t, ":w=") != NULL) {
            ok = sscanf(t, "%lu:w=%lu/%lu", &i, &j, &k);
            if (ok == 0) {
                ok = sscanf(t, "%lu:w=%lu", &i, &j);
            }
        } else if (strstr(t, ":p=") != NULL) {
            ok = sscanf(t, "%lu:p=%lu/%lu", &i, &j, &k);
            if (ok == 0) {
                ok = sscanf(t, "%lu:p=%lu", &i, &j);
            }
        } else {
            ok = 0;
        }
        if (ok) {
            /* vérif que les paramètres sont correctement initialisés
               avant d'aller plus loin */
            if (fc_params_ok(buffer_erreur, maxlen_erreur) == -1) {
                /* tous les params définis avant de déclarer
                   une cellule manquent ou ne sont pas bon
                   => buffer_erreur initialisé => retour immédiat : */
                return -1;
            }
            if (*total_cells == 0) {
                snprintf(buffer_erreur, maxlen_erreur,
                         "Erreur : déclaration d'un lien trop tôt.\n"
                         "Le nombre total de cellules "
                         "n'est pas encore déclaré.\n");
                fclose(hinput);
                return -1;
            }
            if (i != no_courant) {
                /* on vient de lire une nouvelle cellule
                   => valider la précédente */
                if (i < 1) {
                    snprintf(buffer_erreur, maxlen_erreur,
                             "Erreur : déclaration d'un id de cellule "
                             "incohérent : %lu\n", i);
                    fclose(hinput);
                    return -1;
                }
                if (i > (*total_cells)) {
                    snprintf(buffer_erreur, maxlen_erreur,
                             "Erreur : déclaration d'un id de cellule "
                             "incohérent : %lu.\n"
                             "Vous avez déclaré qu'il y a %lu cellules, et l'id=%lu.\n"
                             "L'id max = max cellules = %lu (ici).\n"
                             "Ce n'est pas le cas.\n", i, *total_cells, i,
                             *total_cells);
                    fclose(hinput);
                    return -1;
                }

                if (no_courant > 0) {
                    /* => remise à zéro de la cellule en cours */
                    no_courant = tag_courant = 0;
                }
            }
            /* on vient de lire une information => augmenter le nb de links à faire */
            no_courant = i;
            if (k) {
                tag_courant = k;
            }
        }
    }
    fclose(hinput);
    return 0;
}

int laby_lecture(char *nom_fichier,
                 struct cell *tabcell, struct cell_link *tablinks,
                 char *buffer_erreur, int maxlen_erreur)
{
    /* (!) avant d'appeler cette fonction il faut
       avoir appelé laby_verif_fichier() et
       avoir alloué les cellules et les liens
       (!) tabcell et tablinks doivent être déjà alloués !
     */
    FILE *hinput;
    char s[250];
    char *t;
    unsigned long i, j, k, m;
    int ok, len, est_path;

    unsigned long no_courant = 0;
    unsigned long tag_courant = 0;
    unsigned long nb_idx_paths_tmp = 0;
    unsigned long nb_idx_walls_tmp = 0;
    unsigned long tab_idx_paths_tmp[50];
    unsigned long tab_idx_walls_tmp[50];

    struct cell_link *p_links = tablinks;
    struct cell *c;

    hinput = fopen(nom_fichier, "rb");
    if (hinput == NULL) {
        snprintf(buffer_erreur, maxlen_erreur,
                 "Erreur de lecture du fichier <%s>.\n", nom_fichier);
        return -1;
    }

    /* boucle principale de lecture */
    while (fgets(s, sizeof(s), hinput) != NULL) {
        len = strlen(s);
        if (len == 0)
            continue;

        /* supprimer dernier char = retour chariot : */
        if (s[len - 1] < 20)
            s[len - 1] = 0;

        /* ignorer les espaces et tabulations du début */
        t = s;
        while (t[0]) {
            if ((t[0] != ' ') && (t[0] != 9))
                break;
            t++;
        }
        if (strlen(t) == 0)
            continue;
        if (t[0] == ';')
            continue;

        /* arrivé ici, chaine valide => analyse */
        /* pour chaque cellule deux possibilités :
           avec ou sans tag */
        /* forcer k=0 car sinon k garde dernière valeur */
        k = 0;
        /* précaution car sscanf prend aussi ":w=" */
        if (strstr(t, ":p=") != NULL) {
            ok = sscanf(t, "%lu:p=%lu/%lu", &i, &j, &k);
            if (ok == 0) {
                ok = sscanf(t, "%lu:p=%lu", &i, &j);
            }
            est_path = 1;
        } else if (strstr(t, ":w=") != NULL) {
            ok = sscanf(t, "%lu:w=%lu/%lu", &i, &j, &k);
            if (ok == 0) {
                ok = sscanf(t, "%lu:w=%lu", &i, &j);
            }
            est_path = 0;
        } else {
            ok = 0;
        }
        if (ok) {
            if (i != no_courant) {
                if (no_courant > 0) {

                    /* on vient de lire une nouvelle cellule
                       et on a déjà des infos sur une précédente
                       => valider la précédente */
                    c = &tabcell[no_courant - 1];
                    if (tag_courant) {
                        c->tag = tag_courant;
                    }
                    if (c->nb_paths && nb_idx_paths_tmp) {
                        snprintf(buffer_erreur, maxlen_erreur,
                                 "Erreur de déclaration de paths.\n"
                                 "Il faut que pour une cellule donnée, "
                                 "tous les paths\nsoient déclarés d'un coup.\n"
                                 "Ce n'est pas le cas pour la cellule no %lu.\n"
                                 "Cette derniere a déjà des paths (%d exactement).\n",
                                 no_courant, c->nb_paths);
                        fclose(hinput);
                        return -1;
                    }
                    if (c->nb_walls && nb_idx_walls_tmp) {
                        snprintf(buffer_erreur, maxlen_erreur,
                                 "Erreur de déclaration de walls.\n"
                                 "Il faut que pour une cellule donnée, "
                                 "tous les walls\nsoient déclarés d'un coup.\n"
                                 "Ce n'est pas le cas pour la cellule no %lu.\n"
                                 "Cette derniere a déjà des walls (%d exactement).\n",
                                 no_courant, c->nb_walls);
                        fclose(hinput);
                        return -1;
                    }

                    /* on déplace tous les paths */
                    if (nb_idx_paths_tmp) {
                        c->nb_paths = nb_idx_paths_tmp;
                        c->paths = p_links;
                        for (m = 0; m < nb_idx_paths_tmp; m++) {
                            p_links->link = &tabcell[tab_idx_paths_tmp[m]];
                            p_links->tag = 0;
                            p_links++;
                        }
                    }

                    /* on déplace tous les walls */
                    if (nb_idx_walls_tmp) {
                        c->nb_walls = nb_idx_walls_tmp;
                        c->walls = p_links;
                        for (m = 0; m < nb_idx_walls_tmp; m++) {
                            p_links->link = &tabcell[tab_idx_walls_tmp[m]];
                            p_links->tag = 0;
                            p_links++;
                        }
                    }
                    /* => remise à zéro de la cellule en cours */
                    nb_idx_paths_tmp = nb_idx_walls_tmp = 0;
                    no_courant = tag_courant = 0;
                }
            }
            /* ajout des informations qu'un vient de lire */
            if (est_path) {
                tab_idx_paths_tmp[nb_idx_paths_tmp++] = j - 1;
            } else {
                tab_idx_walls_tmp[nb_idx_walls_tmp++] = j - 1;
            }
            no_courant = i;
            if (k) {
                tag_courant = k;
            }

        }
    }

    if ((no_courant > 0) && ((nb_idx_paths_tmp > 0) || (nb_idx_walls_tmp))) {

        /* une cellule restant en mémoire, ne pas l'oublier
           => l'intégrer */
        c = &tabcell[no_courant - 1];
        if (tag_courant) {
            c->tag = tag_courant;
        }
        if (c->nb_paths && nb_idx_paths_tmp) {
            snprintf(buffer_erreur, maxlen_erreur,
                     "Erreur de déclaration de paths.\n"
                     "Il faut que pour une cellule donnée, "
                     "tous les paths\nsoient déclarés d'un coup.\n"
                     "Ce n'est pas le cas pour la cellule no %lu.\n"
                     "Cette derniere a déjà des paths (%d exactement).\n",
                     no_courant, c->nb_paths);
            fclose(hinput);
            return -1;
        }
        if (c->nb_walls && nb_idx_walls_tmp) {
            snprintf(buffer_erreur, maxlen_erreur,
                     "Erreur de déclaration de walls.\n"
                     "Il faut que pour une cellule donnée, "
                     "tous les walls\nsoient déclarés d'un coup.\n"
                     "Ce n'est pas le cas pour la cellule no %lu."
                     "Cette derniere a déjà des walls (%d exactement).\n",
                     no_courant, c->nb_walls);
            fclose(hinput);
            return -1;
        }

        /* on déplace tous les paths */
        if (nb_idx_paths_tmp) {
            c->nb_paths = nb_idx_paths_tmp;
            c->paths = p_links;
            for (m = 0; m < nb_idx_paths_tmp; m++) {
                p_links->link = &tabcell[tab_idx_paths_tmp[m]];
                p_links->tag = 0;
                p_links++;
            }
        }
        /* on déplace tous les walls */
        if (nb_idx_walls_tmp) {
            c->nb_walls = nb_idx_walls_tmp;
            c->walls = p_links;
            for (m = 0; m < nb_idx_walls_tmp; m++) {
                p_links->link = &tabcell[tab_idx_walls_tmp[m]];
                p_links->tag = 0;
                p_links++;
            }
        }
    }
    fclose(hinput);
    return 0;
}
