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

void cell_print(cell * p)
{
    int i;

    printf("Cellule%c%cNo : %lu%c%c Tag : %lu %c%c L (", (char) 9,
           (char) 9, p->no, (char) 9, (char) 9, p->tag, (char) 9,
           (char) 9);
    for (i = 0; i < p->nb_links; i++) {
        if (p->links[i].link != NULL) {
            printf("%lu", p->links[i].link->no);
        } else {
            printf("NULL");
        }
        if (i < (p->nb_links - 1))
            printf(",");
    }
    printf(")%c%c P (", (char) 9, (char) 9);
    for (i = 0; i < p->nb_paths; i++) {
        printf("%lu", p->paths[i].link->no);
        if (i < (p->nb_paths - 1))
            printf(",");
    }
    printf(")%c%c W (", (char) 9, (char) 9);
    for (i = 0; i < p->nb_walls; i++) {
        printf("%lu", p->walls[i].link->no);
        if (i < (p->nb_walls - 1))
            printf(",");
    }
    printf(")\n");
}

void cell_deplacer_link_vers_paths(cell * p, int idx_link)
{
    /* (!) pour optimiser au max,
       les 3 pointeurs links / paths / walls
       pointent sur une même chaine contigüe
       et se suivent : [ links | paths | walls ]
       donc si on veut déplacer une valeur de links (a)
       dans paths, il suffit d'échanger (a) avec
       la dernière valeur de links, et de "reculer"
       paths d'une case => optimisation maximale.
     */

    cell_link *pcurr;
    cell_link *pfin;
    cell_link ltmp;

    /* intervertir avec la dernière valeur */
    if (idx_link < (p->nb_links - 1)) {
        pcurr = &(p->links[idx_link]);
        pfin = &(p->links[p->nb_links - 1]);
        memmove(&ltmp, pcurr, sizeof(cell_link));
        memmove(pcurr, pfin, sizeof(cell_link));
        memmove(pfin, &ltmp, sizeof(cell_link));
    }
    /* (!) optimisation : voir long commentaire au-dessus : */
    if (p->paths) {
        /* (!) décrémenter le pointeur : */
        p->paths--;
    } else {
        p->paths = &p->links[p->nb_links - 1];
    }

    p->nb_links--;
    p->nb_paths++;
}

void cell_deplacer_link_vers_walls(cell * p, int idx_link)
{
    /* (!) pour optimiser au max,
       les 3 pointeurs links / paths / walls
       pointent sur une même chaine contigüe
       et se suivent : [ links | paths | walls ]
       donc si on veut déplacer une valeur de links (a)
       dans walls, deux cas :
       - paths n'est pas initialisé 
       => il suffit d'échanger (a) avec
       la dernière valeur de walls, et de "reculer"
       walls d'une case => optimisation maximale.
       - paths est initialisé
       => mettre à la fin de links la valeur (a)
       => prendre la dernière valeur de paths,
       l'inverser avec (a)
       => décrémenter le pointeur paths
       => décrémenter le pointeur walls
     */
    cell_link *p_l_curr;
    cell_link *p_l_fin;
    cell_link *p_p_fin;
    cell_link ltmp;

    if (p->nb_paths) {
        /* si case à déplacer = pas la dernière valeur de links
           intervertir avec la dernière valeur de links */
        p_l_fin = &(p->links[p->nb_links - 1]);
        p_p_fin = &(p->paths[p->nb_paths - 1]);
        if (idx_link < (p->nb_links - 1)) {
            p_l_curr = &(p->links[idx_link]);
            memmove(&ltmp, p_l_curr, sizeof(cell_link));
            memmove(p_l_curr, p_l_fin, sizeof(cell_link));
        } else {
            memmove(&ltmp, p_l_fin, sizeof(cell_link));
        }
        /* arrivé ici : 
           - dans ltmp ce qu'il faut mettre à la fin de paths 
           => mettre dernière de paths dans dernière de links
           => puis transfert ltmp dans fin de paths */
        memmove(p_l_fin, p_p_fin, sizeof(cell_link));
        memmove(p_p_fin, &ltmp, sizeof(cell_link));

        /* (!) pointeur walls pointe sur dernière valeur de paths */
        p->walls = p_p_fin;
        /* (!) décrémenter le pointeur walls */
        p->paths--;
    } else {
        /* (!) optimisation : voir long commentaire au-dessus : */
        p->walls = &(p->links[p->nb_links - 1]);
        /* intervertir avec la dernière valeur */
        if (idx_link < (p->nb_links - 1)) {
            p_l_curr = &(p->links[idx_link]);
            memmove(&ltmp, p_l_curr, sizeof(cell_link));
            memmove(p_l_curr, p->walls, sizeof(cell_link));
            memmove(p->walls, &ltmp, sizeof(cell_link));
        }
    }

    p->nb_links--;
    p->nb_walls++;
}

/* utilitaire :  */
unsigned long cell_cell_deplacer_link(cell * c1, cell * c2,
                                      void (*fc) (cell * p, int idx_link))
{
    unsigned long m;
    unsigned char ok;
    ok = 0;
    for (m = 0; m < c1->nb_links; m++) {
        if (c1->links[m].link == c2) {
            fc(c1, m);
            ok = 1;
            break;
        }
    }
    if (ok == 0) {
        return 1;
    }
    for (m = 0; m < c2->nb_links; m++) {
        if (c2->links[m].link == c1) {
            fc(c2, m);
            return 0;
        }
    }
    return 1;
}

/* Utilitaire : lorsqu'on veut que les liens de
   deux cellules qui sont reliées (c1 a un lien
   vers c2 *et* c2 a un lien vers c1)
   se transforment (en mur ou en chemin) : */
unsigned long cell_cell_deplacer_link_vers_walls(cell * c1, cell * c2)
{
    return cell_cell_deplacer_link(c1, c2, &cell_deplacer_link_vers_walls);
}

unsigned long cell_cell_deplacer_link_vers_paths(cell * c1, cell * c2)
{
    return cell_cell_deplacer_link(c1, c2, &cell_deplacer_link_vers_paths);
}

/* Utilitaire : pour les labyrinthes carrés,
   c'est toujours la même chose :
   (1) déplacer le lien de c1 avec l'indice idx1
   (2) pour la cellule c2, trouver le lien qui pointe
       vers c1
   (3) déplacer le lien de c2 qui pointe vers c1

   renvoie : 
    - (0) si ok
    - (1) si erreur
 */
unsigned long cell_carre_deplacer_link(cell * c,
                                       unsigned long idx,
                                       void (*fc) (cell * p, int idx_link))
{
    unsigned long m;

    cell *c2 = c->links[idx].link;
    /* rechercher le lien de c2 qui pointe vers c1 */
    for (m = 0; m < c2->nb_links; m++) {
        if (c2->links[m].link == c) {
            fc(c, idx);
            fc(c2, m);
            return 0;
        }
    }
    return 1;
}

/* voir long commentaire sur la fonction précédente
  */
unsigned long cell_carre_deplacer_link_vers_paths(cell * c,
                                                  unsigned long idx)
{
    return cell_carre_deplacer_link(c, idx,
                                    &cell_deplacer_link_vers_paths);
}

/* voir long commentaire sur la fonction précédente
  */
unsigned long cell_carre_deplacer_link_vers_walls(cell * c,
                                                  unsigned long idx)
{
    return cell_carre_deplacer_link(c, idx,
                                    &cell_deplacer_link_vers_walls);
}

int cell_a_un_path_avec_tag(struct cell *c, unsigned long tag)
{
    unsigned long i;
    for (i = 0; i < c->nb_paths; i++) {
        if (c->paths[i].link->tag == tag) {
            return 0;
        }
    }
    return -1;
}

/*
fonction de dump d'un laby
*/
#define CELL(x,y,z,la,lo) (&tabcell[(x)+((y)*la)+((z)*(la)*(lo))])
struct cell *cell_selon_tag(unsigned long tag, struct cell *tabcell,
                            unsigned long total_cells)
{
    unsigned long i;
    for (i = 0; i < total_cells; i++) {
        if (tabcell[i].tag == tag) {
            return &(tabcell[i]);
        }
    }
    return NULL;
}

void print_laby(struct cell *tabcell, unsigned long total_cells,
                unsigned long taille_x,
                unsigned long taille_y, unsigned long taille_z)
{
    unsigned long x, y, z, i, tag;
    struct cell *pcell;

    if (taille_x > 10) {
        printf("Taille de labyrinthe trop grande\n"
               "=> print_laby() ne fait rien");
        return;
    }

    /* affichage au complet du laby résultat */
    printf("print_laby :\n");
    printf("(tableau 1 = id cells, tableau 2 = tags) :\n");
    for (z = 0; z < taille_z; z++) {
        for (y = 0; y < taille_y; y++) {
            /*-------------------------------------------*/
            /* Première ligne = si y=0 mur, sinon voir si espace ou mur */
            /* La boucle for (i) = écrire deux fois :
               une fois pour les nos, et une fois pour les tags : */
            for (i = 0; i < 2; i++) {
                for (x = 0; x < taille_x; x++) {
                    printf("+");
                    if (y > 0) {
                        tag =
                            1 + x + (y * taille_x) +
                            (taille_x * taille_y * z);
                        pcell = cell_selon_tag(tag, tabcell, total_cells);
                        if (pcell == NULL) {
                            /* Case courante introuvable =
                               c'est qu'elle n'est pas déclarée
                               (= forcer mur = p.ex. point noir sur les bmp) */
                            printf("-------");
                            continue;
                        }
                        if (cell_a_un_path_avec_tag
                            (pcell,
                             1 + x + ((y - 1) * taille_x) +
                             (z * taille_x * taille_y)) == 0) {
                            printf("       ");
                        } else {
                            printf("-------");
                        }
                    } else {
                        printf("-------");
                    }
                }
                if (i == 0) {
                    printf("+       ");
                }
            }
            printf("+\n");
            /*-------------------------------------------*/
            /* Deuxième ligne écriture des numéros */
            printf("|");
            /* La boucle for (i) = écrire deux fois :
               une fois pour les nos, et une fois pour les tags : */
            for (i = 0; i < 2; i++) {
                for (x = 0; x < taille_x; x++) {
                    tag =
                        1 + x + (y * taille_x) + (taille_x * taille_y * z);
                    pcell = cell_selon_tag(tag, tabcell, total_cells);
                    if (pcell == NULL) {
                        /* Case courante introuvable =
                           c'est qu'elle n'est pas déclarée
                           (= forcer mur = p.ex. point noir sur les bmp)   
                           (!) Contrairement à mon habitude, ici, c'est uniquement
                           le mur de gauche qu'on dessine 
                         */
                        if (x > 0) {
                            printf("|");
                        }
                        printf("   -   ");
                        continue;
                    }
                    /* (!) Contrairement à mon habitude, ici, c'est uniquement
                       le mur de gauche qu'on dessine */
                    if (x > 0) {
                        if (cell_a_un_path_avec_tag
                            (pcell,
                             x + (y * taille_x) +
                             (z * taille_x * taille_y)) == 0) {
                            printf(" ");
                        } else {
                            printf("|");
                        }
                    }
                    printf(" %3lu   ", pcell->no);
                }
                if (i == 0) {
                    printf("|       |");
                }
            }
            printf("|\n");
        }

        for (x = 0; x < taille_x; x++) {
            printf("+-------");
        }
        printf("+       ");

        for (x = 0; x < taille_x; x++) {
            printf("+-------");
        }

        printf("+\n\n");
    }

}
