#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "cell.h"

struct tab_cell {
    unsigned int nb_pcells;
    cell **tab_pcells;
};

int plugin_algo_carre_arbre(unsigned long total_cellules,
                            unsigned long total_liens,
                            cell * tabcell,
                            char *params,
                            char *buffer_erreur, int maxlen_erreur)
{
    /* retour = ok par défaut : */
    int ret = 0;

    /* les variables 'XXX_tmp' sont utilisées pour les realloc */
    struct cell **tab_todo;
    struct cell **tab_todo_tmp;
    struct tab_cell *tab_tree;
    struct tab_cell *tab_tree_tmp;
    cell **tab_pcells_tmp;

    struct cell *pcell;
    struct cell *plink;
    unsigned long i, j, k, m, n, q;
    unsigned long total_leaves;
    unsigned long nb_cells_to_do;
    unsigned char fusion;
    unsigned char boucle_a_faire;

    /*
       tab_tree est composé ainsi :

       tag 1 | tag 2 | tag 3 | tag 4 | tag 5 | tag ...

       chaque tag est un tableau de pointeurs vers des cellules
     */
    tab_tree = NULL;
    total_leaves = 0;

    /* construire une liste avec toutes les cellules du laby :
       (en profiter pour mettre les tag de chaque cellule à 0)
     */
    tab_todo =
        (struct cell **) calloc(total_cellules, sizeof(struct cell *));
    for (i = 0; i < total_cellules; i++) {
        tab_todo[i] = &(tabcell[i]);
        tabcell[i].tag = 0;
    }
    /* boucle principale
     */
    nb_cells_to_do = total_cellules;
    while (nb_cells_to_do) {
        /*  tirer une cellule parmi les cellules non faites */
        i = rand() % nb_cells_to_do;
        pcell = tab_todo[i];
        /*  tirer un lien au hasard */
        j = rand() % (pcell->nb_links);
        plink = pcell->links[j].link;
        k = plink->tag;
        if (k == 0) {
            /* si le lien pointe sur une case PAS faite :
               nouveau tag leaves */
            /* allouer un nouveau tableau à la fin de tabtree */
            tab_tree_tmp =
                (struct tab_cell *) realloc(tab_tree,
                                            (sizeof(struct tab_cell)) *
                                            (total_leaves + 1));
            if (tab_tree_tmp == NULL) {
                snprintf(buffer_erreur, maxlen_erreur,
                         "Erreur d'allocation : manque de mémoire.l\n");
                ret = -1;
                goto plugin_algo_carre_arbre_fin;
            }
            /* ré-allocation réussie => assigner */
            tab_tree = tab_tree_tmp;
            /* a ce nouveau tableau, allouer la place pour
               le premier pointeur cell */
            tab_tree[total_leaves].nb_pcells = 1;
            tab_pcells_tmp =
                (struct cell **) malloc(sizeof(struct cell *));
            if (tab_pcells_tmp == NULL) {
                snprintf(buffer_erreur, maxlen_erreur,
                         "Erreur d'allocation : manque de mémoire.l\n");
                ret = -1;
                goto plugin_algo_carre_arbre_fin;
            }
            /* allocation réussie => assigner */
            tab_tree[total_leaves].tab_pcells = tab_pcells_tmp;
            /* premier pointeur cell = cell en cours */
            tab_tree[total_leaves].tab_pcells[0] = pcell;
            pcell->tag = (++total_leaves);
        } else {
            /* si le lien pointe sur une case déja faite :
               tag cellule en cours = tag cellule dest */
            pcell->tag = k;
            /* (!) k = tag = (index + 1) du tableau => faire -1 */
            k--;
            /* le tableau existe déjà dans tabtree,
               il faut allouer une entrée supplémentaire */
            tab_tree[k].nb_pcells++;
            tab_pcells_tmp =
                (struct cell **) realloc(tab_tree[k].tab_pcells,
                                         sizeof(struct cell *) *
                                         tab_tree[k].nb_pcells);
            if (tab_pcells_tmp == NULL) {
                snprintf(buffer_erreur, maxlen_erreur,
                         "Erreur d'allocation : manque de mémoire.l\n");
                ret = -1;
                goto plugin_algo_carre_arbre_fin;
            }
            /* ré-allocation réussie => assigner */
            tab_tree[k].tab_pcells = tab_pcells_tmp;
            /* mettre le noeud dans la liste */
            tab_tree[k].tab_pcells[tab_tree[k].nb_pcells - 1] = pcell;
            /* creuser un passage entre les deux cellules : */
            /* rechercher le lien opposé de l'autre côté */
            for (m = 0; m < plink->nb_links; m++) {
                if (plink->links[m].link == pcell)
                    break;
            }
            if (m == plink->nb_links) {
                snprintf(buffer_erreur, maxlen_erreur,
                         "Erreur de recherche de cellule, pas normal\n");
                ret = -1;
                goto plugin_algo_carre_arbre_fin;
            }
            cell_deplacer_link_vers_paths(pcell, j);
            cell_deplacer_link_vers_paths(plink, m);
        }
        /* cellule courante est sauvée => elle ne 
           sert plus à rien dans tab_todo
           => l'écraser par la dernière cellule */
        tab_todo[i] = tab_todo[nb_cells_to_do - 1];
        nb_cells_to_do--;
        /* libérer de la mémoire par paquets */
        if (nb_cells_to_do > 2) {
            if ((nb_cells_to_do % 2) == 0) {
                tab_todo_tmp =
                    (struct cell **) realloc(tab_todo,
                                             nb_cells_to_do *
                                             sizeof(struct cell *));
                if (tab_todo_tmp == NULL) {
                    snprintf(buffer_erreur, maxlen_erreur,
                             "Erreur d'allocation : manque de mémoire.\n");
                    ret = -1;
                    goto plugin_algo_carre_arbre_fin;
                }
                /* ré-allocation réussie => assigner */
                tab_todo = tab_todo_tmp;

            }
        }
    }
    /*
       maintenant, toutes les cases sont "groupées" dans
       des tableaux de cases (dans chaque tableau, toutes les
       cases sont reliées entre elles par un lien).
       => il faut fusionner les tableaux
       => chercher des cellules qui se touchent entre deux 
       tableaux.
       => il faut parcourir chaque tableau et faire :
       - pour chaque case regarder sur les liens restants
       - si l'un des liens pointe vers une cellule présente
       dans un autre tableau, alors fusionner les tableaux
       - sinon passer à la cellule suivante 
     */

    /* boucle sur les groupes de tableaux : 
       tant qu'il y a plus de un tableau */
    i = 0;
    fusion = 0;
    boucle_a_faire = 0;
    while (i < total_leaves) {
        j = 0;
        while (j < tab_tree[i].nb_pcells) {
            pcell = tab_tree[i].tab_pcells[j];
            m = pcell->tag;
            /* just pour vérif, on devrait toujours avoir
               tag = (index + 1) : */
            if (m != (i + 1)) {
                snprintf(buffer_erreur, maxlen_erreur,
                         "Erreur : (m != (i+1)) => (%lu != (%lu))\n",
                         m, i + 1);
                ret = -1;
                goto plugin_algo_carre_arbre_fin;
            }
            /* pour chaque élément, parcourir ses liens : */
            for (k = 0; k < pcell->nb_links; k++) {
                plink = pcell->links[k].link;
                n = plink->tag;
                if (n != m) {
                    /* le lien est dans un autre "groupe"
                       => fusionner ces deux groupes :
                     */
                    /* (!) m = tag = (index + 1) du tableau => faire -1 */
                    n--;
                    /* assigner à tout le groupe 2 le tag du groupe 1 */
                    for (q = 0; q < tab_tree[n].nb_pcells; q++) {
                        tab_tree[n].tab_pcells[q]->tag = m;
                    }
                    /* allocation tableau tab tmp = 
                       taille groupe 1 + taille groupe 2 */
                    q = tab_tree[i].nb_pcells + tab_tree[n].nb_pcells;
                    tab_pcells_tmp = (struct cell **)
                        malloc(q * sizeof(struct cell *));
                    if (tab_pcells_tmp == NULL) {
                        snprintf(buffer_erreur, maxlen_erreur,
                                 "Erreur d'allocation : manque de mémoire.l\n");
                        ret = -1;
                        goto plugin_algo_carre_arbre_fin;
                    }
                    /* copie du groupe 1 dans tab tmp */
                    memcpy(tab_pcells_tmp, tab_tree[i].tab_pcells,
                           tab_tree[i].nb_pcells * sizeof(struct cell *));
                    /* libérer le groupe 1 et y mettre tab tmp */
                    free(tab_tree[i].tab_pcells);
                    /* assigner groupe 1 = tab tmp */
                    tab_tree[i].tab_pcells = tab_pcells_tmp;
                    /* déplacer tab tmp pour copie groupe 2 */
                    tab_pcells_tmp += tab_tree[i].nb_pcells;
                    /* tab tmp est à la fin du groupe 1 
                       => copie du groupe 2 */
                    memcpy(tab_pcells_tmp, tab_tree[n].tab_pcells,
                           tab_tree[n].nb_pcells * sizeof(struct cell *));
                    /* nb de cellules = nb groupe 1 + nb groupe 2 */
                    tab_tree[i].nb_pcells = q;
                    /* libérer le groupe 2 */
                    free(tab_tree[n].tab_pcells);
                    tab_tree[n].tab_pcells = NULL;
                    tab_tree[n].nb_pcells = 0;

                    /* enfin, faire une ouverture sur les deux cellules */
                    /* rechercher le lien opposé de l'autre côté */
                    for (q = 0; q < plink->nb_links; q++) {
                        if (plink->links[q].link == pcell)
                            break;
                    }
                    if (q == plink->nb_links) {
                        snprintf(buffer_erreur, maxlen_erreur,
                                 "Erreur de recherche de cellule, pas normal\n");
                        ret = -1;
                        goto plugin_algo_carre_arbre_fin;
                    }
                    cell_deplacer_link_vers_paths(pcell, k);
                    cell_deplacer_link_vers_paths(plink, q);

                    /* fusion = 1 signie : des tableaux
                       ont été fusionnés, refaire une passe */
                    i = n;
                    boucle_a_faire = 1;
                    fusion = 1;
                    break;
                }
            }
            if (fusion) {
                break;
            }
            j++;
        }
        if (fusion) {
            /* des tableaux ont été fusionnés
               => refaire une passe au complet */
            fusion = 0;
            i = 0;
        } else {
            i++;
            if (i == total_leaves) {
                if (boucle_a_faire) {
                    /* autoriser à refaire une passe 
                       (mais que une seule) */
                    boucle_a_faire = 0;
                    i = 0;
                }
            }
        }
    }
  plugin_algo_carre_arbre_fin:
    /* libération de toutes les ressources */
    for (i = 0; i < total_leaves; i++) {
        free(tab_tree[i].tab_pcells);
    }
    free(tab_tree);
    free(tab_todo);

    if (ret == 0) {
        /* tout s'est bien passé correctement 
           => on a fait toutes les ouvertures
           => tous les liens qui restent sont
           des murs :
         */
        for (i = 0; i < total_cellules; i++) {
            pcell = &(tabcell[i]);
            while (pcell->nb_links) {
                cell_deplacer_link_vers_walls(pcell, 0);
            }
        }
    }
    return ret;
}
