#include <stdio.h>
#include <stdlib.h>
#include <time.h>

#include "cell.h"
#include "calcule_montees.h"

#define P_CELL(x,y,z,tx,ty) (&tabcells[(x)+((y)*tx)+((z)*(tx)*(ty))])

#define NORD_OK(bz)  ((tabcells[ (bz) - taille_x ]).tag == 0)
#define EST_OK(bz)   ((tabcells[ (bz) + 1        ]).tag == 0)
#define SUD_OK(bz)   ((tabcells[ (bz) + taille_x ]).tag == 0)
#define OUEST_OK(bz) ((tabcells[ (bz) - 1        ]).tag == 0)

/*
 --
 Contraintes de l'éditeur pour pouvoir faire une montée :
 --
 (1) il faut que les cellules de base et au-dessus soient libres

 (2) prolongement = nord->sud ou sud->nord ou est->ouest ou ouest->est
 il faut que les cellules bas prolongement haut prolongement
 soient libres

 (3) dernière contrainte prise de tête à trouver :
 il faut que les cellules des deux côtés haut prolongement
 soient libres

 ex : prolongement nord->sud : il faut
 (1) cellule bas + cellule nord bas libre
 (2) cellule haut + cellule haut sud libre
 (3) cellules est ouest de la cellule sud du haut libres
 */

void print_tags(struct cell *tabcells, unsigned long taille_x,
                unsigned long taille_y, unsigned long taille_z)
{
    unsigned long x, y, z, t;
    unsigned long taille_x_y;
    taille_x_y = taille_x * taille_y;

    for (z = 0; z < taille_z; z++) {
        printf("--------------------\n");
        for (y = 0; y < taille_y; y++) {
            printf("%4lu - %4lu%c", (z * taille_x_y) + (y * taille_x),
                   (z * taille_x_y) + (y * taille_x) + taille_x - 1,
                   (char) 9);
            for (x = 0; x < taille_x; x++) {
                t = P_CELL(x, y, z, taille_x, taille_y)->tag;
                printf("(%s%s%s%s%s%s|%s%s%s%s) ",
                       (t & TAG_FORCER_PATH_N ? "N" : " "),
                       (t & TAG_FORCER_PATH_E ? "E" : " "),
                       (t & TAG_FORCER_PATH_S ? "S" : " "),
                       (t & TAG_FORCER_PATH_O ? "O" : " "),
                       (t & TAG_FORCER_PATH_U ? "U" : " "),
                       (t & TAG_FORCER_PATH_D ? "D" : " "),
                       (t & TAG_FORCER_WALL_N ? "N" : " "),
                       (t & TAG_FORCER_WALL_E ? "E" : " "),
                       (t & TAG_FORCER_WALL_S ? "S" : " "),
                       (t & TAG_FORCER_WALL_O ? "O" : " "));
            }
            printf("\n");
        }
    }
    printf("--------------------\n");

}

void add_montees_possibles_z(struct cell *tabcells,
                             unsigned long *tab_montees,
                             unsigned long *nb_montees,
                             unsigned long taille_x,
                             unsigned long taille_y, unsigned long z)
{
    unsigned long x, y;
    unsigned long base;
    unsigned long base_haut;
    unsigned long taille_x_y;
/*
    printf(".................................\n");
    printf("add_montees_possibles_z()\n");
*/
    taille_x_y = taille_x * taille_y;
    for (y = 1; y < (taille_y - 1); y++) {
        for (x = 1; x < (taille_x - 1); x++) {
/*
            printf("Test sur (%lu,%lu,%lu)\n", x, y, z);
*/
            base = x + (y * taille_x) + (z * taille_x_y);
            base_haut = base + taille_x_y;
            if (tabcells[base].tag || tabcells[base_haut].tag) {
                continue;
            }
            if (!(NORD_OK(base) && SUD_OK(base)
                  && EST_OK(base) && OUEST_OK(base)
                  && NORD_OK(base_haut) && SUD_OK(base_haut)
                  && EST_OK(base_haut) && OUEST_OK(base_haut)
                  && EST_OK(base_haut - taille_x)
                  && OUEST_OK(base_haut - taille_x)
                  && EST_OK(base_haut + taille_x)
                  && OUEST_OK(base_haut + taille_x)
                  && NORD_OK(base_haut - 1)
                  && SUD_OK(base_haut - 1)
                  && NORD_OK(base_haut + 1)
                  && SUD_OK(base_haut + 1))) {
                continue;
            }
            /* voir long commentaire en début de
               fichier sur les contraintes TM */
            if ((y > 0) && (y < (taille_y - 1))) {
/*
                printf("Test Nord <=> Sud possible\n");
*/
                if (NORD_OK(base) && SUD_OK(base)
                    && NORD_OK(base_haut) && SUD_OK(base_haut)) {
                    if (EST_OK(base_haut - taille_x)
                        && OUEST_OK(base_haut - taille_x)) {
                        tab_montees[(*nb_montees)++] = base;
                        continue;
                    }
                    if (EST_OK(base_haut + taille_x)
                        && OUEST_OK(base_haut + taille_x)) {
                        tab_montees[(*nb_montees)++] = base;
                        continue;
                    }
                }
            }
            if ((x > 0) && (x < (taille_x - 1))) {
/*
                printf("Test Est <=> Ouest possible\n");
*/
                if (EST_OK(base) && OUEST_OK(base)
                    && EST_OK(base_haut) && OUEST_OK(base_haut)) {
                    if (NORD_OK(base_haut + 1)
                        && SUD_OK(base_haut + 1)) {
                        tab_montees[(*nb_montees)++] = base;
                        continue;
                    }
                    if (NORD_OK(base_haut - 1)
                        && SUD_OK(base_haut - 1)) {
                        tab_montees[(*nb_montees)++] = base;
                        continue;
                    }
                }
            }
        }
    }
}

void creer_montee(struct cell *tabcells,
                  unsigned long base,
                  unsigned long taille_x, unsigned long taille_y,
                  unsigned long taille_z)
{
    unsigned long i;
    unsigned long x, y, z;
    unsigned long taille_x_y;
    unsigned long base_haut;
    unsigned char tab_dir[5];
    unsigned long nb_dir;
    long diff, tmp;

    taille_x_y = taille_x * taille_y;
    z = base / taille_x_y;
    y = (base % taille_x_y) / taille_x;
    x = base % taille_x;

    base_haut = base + taille_x_y;
    printf("Créér montée : Cellule (%lu,%lu,%lu) - ", x, y, z);
    printf("(base = %lu, base_haut = %lu)\n", base, base_haut);
/*
*/
    if (tabcells[base].tag || tabcells[base_haut].tag) {
        printf("Tag <>0 => pas normal...\n");
        return;
    }
    /* voir long commentaire en début de
       fichier sur les contraintes TM */
    nb_dir = 0;
    if (NORD_OK(base) && SUD_OK(base)
        && NORD_OK(base_haut) && SUD_OK(base_haut)) {
/*
            printf("Nord Sud possible\n");
*/
        if (EST_OK(base_haut - taille_x)
            && OUEST_OK(base_haut - taille_x)) {
            tab_dir[nb_dir++] = 1;
        }
        if (EST_OK(base_haut + taille_x)
            && OUEST_OK(base_haut + taille_x)) {
            tab_dir[nb_dir++] = 3;
        }
    }
    if (EST_OK(base) && OUEST_OK(base)
        && EST_OK(base_haut) && OUEST_OK(base_haut)) {
/*
            printf("Ouest Est possible\n");
*/
        if (NORD_OK(base_haut + 1)
            && SUD_OK(base_haut + 1)) {
            tab_dir[nb_dir++] = 2;
        }
        if (NORD_OK(base_haut - 1)
            && SUD_OK(base_haut - 1)) {
            tab_dir[nb_dir++] = 4;
        }
    }
    if (nb_dir == 0) {
        printf("nb_dir == 0 => pas normal...\n");
        return;
    }
    /* forcer le lien haut <=> bas */
    tabcells[base].tag = TAG_FORCER_PATH_U;
    tabcells[base_haut].tag = TAG_FORCER_PATH_D;
    /* tirer une dir au hasard */
    i = tab_dir[rand() % nb_dir];
    switch (i) {
    case 1:
/*
        printf("=> Bas Nord Haut Sud choisi\n");
*/
        tabcells[base].tag |= (TAG_FORCER_PATH_N | TAGS_FORCER_WALLS_EO);
        tabcells[base_haut].tag |=
            (TAG_FORCER_PATH_S | TAGS_FORCER_WALLS_EO);
        tabcells[base - taille_x].tag = TAG_FORCER_PATH_S;
        tabcells[base + taille_x].tag = TAG_FORCER_WALL_N;
        tabcells[base_haut - taille_x].tag = TAG_FORCER_WALL_S;
        tabcells[base_haut + taille_x].tag = TAG_FORCER_PATH_N;
        break;
    case 3:
/*
        printf("=> Bas Sud Haut Nord choisi\n");
*/
        tabcells[base].tag |= (TAG_FORCER_PATH_S | TAGS_FORCER_WALLS_EO);
        tabcells[base_haut].tag |=
            (TAG_FORCER_PATH_N | TAGS_FORCER_WALLS_EO);
        tabcells[base - taille_x].tag = TAG_FORCER_WALL_S;
        tabcells[base + taille_x].tag = TAG_FORCER_PATH_N;
        tabcells[base_haut - taille_x].tag = TAG_FORCER_PATH_S;
        tabcells[base_haut + taille_x].tag = TAG_FORCER_WALL_N;
        break;
    case 2:
/*
        printf("=> Bas Ouest Haut Est choisi\n");
*/
        tabcells[base].tag |= (TAG_FORCER_PATH_O | TAGS_FORCER_WALLS_NS);
        tabcells[base_haut].tag |=
            (TAG_FORCER_PATH_E | TAGS_FORCER_WALLS_NS);
        tabcells[base - 1].tag = TAG_FORCER_PATH_E;
        tabcells[base + 1].tag = TAG_FORCER_WALL_O;
        tabcells[base_haut - 1].tag = TAG_FORCER_WALL_E;
        tabcells[base_haut + 1].tag = TAG_FORCER_PATH_O;
        break;
    case 4:
/*
        printf("=> Bas Est Haut Ouest choisi\n");
*/
        tabcells[base].tag |= (TAG_FORCER_PATH_E | TAGS_FORCER_WALLS_NS);
        tabcells[base_haut].tag |=
            (TAG_FORCER_PATH_O | TAGS_FORCER_WALLS_NS);
        tabcells[base - 1].tag = TAG_FORCER_WALL_E;
        tabcells[base + 1].tag = TAG_FORCER_PATH_O;
        tabcells[base_haut - 1].tag = TAG_FORCER_PATH_O;
        tabcells[base_haut + 1].tag = TAG_FORCER_WALL_E;
        break;
    default:
        break;
    }
    print_tags(tabcells, taille_x, taille_y, taille_z);
/*
    tmp = 0;
    for (z = 0; z < taille_z; z++) {
        for (y = 0; y < taille_y; y++) {
            for (x = 0; x < taille_x; x++) {
                i = x + (y * taille_x) + (z * taille_x_y);
                diff = tabcells[i].no - tabcells[i].tag;
                if (diff == -((long) taille_x_y)) {
                    printf("Cell (%lu, %lu, %lu) (n. %lu) : tag = %lu\n",
                           x, y, z, tabcells[i].no, tabcells[i].tag);
                    tmp++;
                }
            }
        }
    }
    printf("Nb cellules trouvées liées haut ou bas : %ld\n", tmp);
*/
}

void calcule_montees(struct cell *tabcells, unsigned long taille_x,
                     unsigned long taille_y, unsigned long taille_z,
                     unsigned long *nb_montees_creees)
{
    unsigned long *tab_montees;
    unsigned long nb_montees;
    unsigned long i, z;
    i = time(NULL);
    printf("%lu\n", i);
    srand(i);
    *nb_montees_creees = 0;

    /* allouer le max théorique de montées */
    tab_montees =
        (unsigned long *) calloc(taille_x * taille_y *
                                 taille_z, sizeof(unsigned long));
    /* étape 1 : pour chaque étage, prendre une montée possible
       et l'intégrer, pour s'assurer qu'il y aura au moins
       une montée par étage */
    for (z = 0; z < (taille_z - 1); z++) {
        nb_montees = 0;
        add_montees_possibles_z(tabcells,
                                tab_montees,
                                &nb_montees, taille_x, taille_y, z);
        /* on a les montées possibles dans tab_montees[] 
           => créer une montée parmi celles-là :
           printf("-------------------------\n");
           printf("Etage %lu : nb montées trouvées : %lu\n", z, nb_montees);
           for (i = 0; i < nb_montees; i++) {
           printf("Montée possible cell n.%lu\n", tab_montees[i]);
           }
         */
        if (nb_montees == 0) {
            break;
        }
        creer_montee(tabcells,
                     tab_montees[rand() % nb_montees],
                     taille_x, taille_y, taille_z);
        (*nb_montees_creees)++;
    }

    /* ok, on a terminé de faire toutes les montées "forcées",
       maintenant on fait un listing comme un bourrin de *toutes*
       les montées possibles à *tous* les étages, et tant qu'on
       peut faire des montées on en fait !
     */
    while (1) {
        nb_montees = 0;
        for (z = 0; z < (taille_z - 1); z++) {
            add_montees_possibles_z(tabcells,
                                    tab_montees,
                                    &nb_montees, taille_x, taille_y, z);
        }
        /* on a les montées possibles dans tab_montees[]
           => créer une montée parmi celles-là :
           printf("-------------------------\n");
           printf("Nb montées trouvées : %lu\n", nb_montees);
         */
        if (nb_montees == 0) {
            break;
        }
/*
        for (i = 0; i < nb_montees; i++) {
            printf("Montée possible cell n.%lu\n", tab_montees[i]);
        }
*/
        creer_montee(tabcells,
                     tab_montees[rand() % nb_montees],
                     taille_x, taille_y, taille_z);
        (*nb_montees_creees)++;
    }
    /* libérer les montées */
    free(tab_montees);
}
