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

#include "opts.h"
#include "utils.h"
#include "cell.h"
#include "calcule_montees.h"
#include "ecriture.h"

/* pour pouvoir éventuellement être entièrement silencieux : */
#define OUT( x ) if (opt_silent_mode==0) { printf( x ); }

void tout_walls_sauf(struct cell *tabcells,
                     unsigned long x,
                     unsigned long y,
                     unsigned long z,
                     unsigned long path_no,
                     unsigned long taille_x, unsigned long taille_y)
{
    struct cell *c =
        &tabcells[x + (y * taille_x) + (z * taille_x * taille_y)];
    while (c->nb_links) {
        if (c->links[0].link->no == path_no) {
            cell_carre_deplacer_link_vers_paths(c, 0);
        } else {
            cell_carre_deplacer_link_vers_walls(c, 0);
        }
    }
}

void link_to_wall_selon_no(struct cell *c, unsigned long no)
{
    unsigned long i;
    for (i = 0; i < c->nb_links; i++) {
        if (c->links[i].link->no == no) {
            printf("Cell n.%lu : link n.%lu => walls (cell n.%lu) : %ld\n",
                   c->no, i, no, cell_carre_deplacer_link_vers_walls(c,
                                                                     i));
        }
    }
}

void link_to_path_selon_no(struct cell *c, unsigned long no)
{
    unsigned long i;
    for (i = 0; i < c->nb_links; i++) {
        if (c->links[i].link->no == no) {
            printf("Cell n.%lu : link n.%lu => paths (cell n.%lu) : %ld\n",
                   c->no, i, no, cell_carre_deplacer_link_vers_paths(c,
                                                                     i));
        }
    }
}

int verif_liens_bidirectionnels(unsigned long total_cellules,
                                cell * tabcell,
                                char *buffer_erreur, int maxlen_erreur)
{
    unsigned long i, j, m;
    struct cell *pcell;
    struct cell *plink;
    int ret;

    ret = 0;
    for (i = 0; i < total_cellules; i++) {
        pcell = &tabcell[i];
        /* boucle sur tous les liens de la cellule */
        for (j = 0; j < pcell->nb_links; j++) {
            plink = pcell->links[j].link;
            /* 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 :\n"
                         "La cellule %lu : le lien %lu pointe la cellule %lu.\n"
                         "Cette dernière n'a pas de lien dans l'autre sens en retour, pas normal.\n",
                         i, j, plink->no);
                return -1;
            }
        }
    }
    return ret;
}


int main(int argc, char *argv[])
{
    /* (!) gros buffer erreur, les messages sont détaillés : */
    char buffer_erreur[1000];
    char buffer_tmp[1000];

    /* options du programme */
    unsigned long taille_x;
    unsigned long taille_y;
    unsigned long taille_x_y;
    unsigned long taille_x_y_z;
    unsigned long taille_z;
    char nom_fichier_sortie[500] = "";
    /* défaut = pas afficher aide */
    unsigned char help = 0;
    /* défaut = afficher tout     */
    unsigned char opt_silent_mode = 0;

    unsigned long i, x, y, z;

    unsigned long total_cells = 0;
    unsigned long total_links = 0;
    unsigned long base;

    struct cell *tabcells;
    struct cell *c;
    struct cell_link *tablinks;
    struct cell_link *l;

    /*------------------------------------*/
    if (valide_opts
        (argc, argv, &help, &opt_silent_mode, nom_fichier_sortie,
         sizeof(nom_fichier_sortie) - 1,
         &taille_x, &taille_y, &taille_z,
         buffer_erreur, sizeof(buffer_erreur) - 1)) {
        /* buffer erreur entièrement initialisé */
        OUT(buffer_erreur);
        exit(EXIT_FAILURE);
    }
    if (help) {
        snprintf(buffer_tmp, sizeof(buffer_tmp) - 1, "Aide demandée :\n");
        STRCAT_STR_USAGE(buffer_tmp, sizeof(buffer_tmp) - 1);
        OUT(buffer_tmp);
        exit(EXIT_SUCCESS);
    }

    OUT("   Génération d'un template pour Trackmania...\n");
    sprintf_time_desc(buffer_tmp, sizeof(buffer_tmp) - 1,
                      "-  Début du programme");
    OUT(buffer_tmp);

    OUT("1) Fichier en sortie : <");
    OUT(nom_fichier_sortie);
    OUT(">\n");

    snprintf(buffer_tmp, sizeof(buffer_tmp) - 1,
             "   Taille : %lu x %lu x %lu\n", taille_x, taille_y,
             taille_z);
    OUT(buffer_tmp);

    total_cells = taille_x * taille_y * taille_z;
    OUT("2) Allocations mémoire...\n");
    OUT("   Nombre de cellules       : ");
    snprintf_format_monetaire(buffer_tmp, sizeof(buffer_tmp) - 1,
                              total_cells);
    OUT(buffer_tmp);
    OUT("\n");
    tabcells = calloc(total_cells, sizeof(cell));
    if (tabcells == NULL) {
        snprintf(buffer_erreur, sizeof(buffer_erreur) - 1,
                 "Erreur d'allocation des cellules "
                 "(%lu cellules à allouer).\n", total_cells);
        OUT(buffer_erreur);
        exit(EXIT_FAILURE);
    }
    for (z = 0; z < total_cells; z++) {
        tabcells[z].no = z;
    }

    /* maintenant que les cellules sont allouées,
       on va marquer les cases qui devront "monter",
       et seulement *après* on alloue les liens */
    taille_x_y = taille_x * taille_y;
    taille_x_y_z = taille_x_y * taille_z;
    /* (!) astuce : empêcher de pouvoir poser
       des montées près du départ et de l'arrivée : */
    tabcells[0].tag = TAG_FORCER_PATH_S | TAG_FORCER_WALL_E;
    tabcells[taille_x].tag =
        TAG_FORCER_PATH_N | TAG_FORCER_PATH_E | TAG_FORCER_PATH_S;

    tabcells[taille_x_y].tag = TAG_FORCER_PATH_S;
    tabcells[taille_x_y + taille_x].tag =
        TAG_FORCER_PATH_N | TAG_FORCER_PATH_E | TAG_FORCER_PATH_S;

    /* pareil pour arrivée : forcer lien vers le haut */
    tabcells[(taille_x_y_z - taille_x) - 1].tag =
        TAG_FORCER_PATH_E | TAG_FORCER_PATH_O | TAG_FORCER_PATH_S;
    tabcells[taille_x_y_z - 1].tag = TAG_FORCER_PATH_N | TAG_FORCER_WALL_O;
    for (i = 0; i < total_cells; i++) {
        cell_print(&(tabcells[i]));
    }
    print_laby(tabcells, taille_x, taille_y, taille_z);
    calcule_montees(tabcells, taille_x, taille_y, taille_z, &total_links);
    print_laby(tabcells, taille_x, taille_y, taille_z);
    /* dans total_links il est inscrit le nb de montées créées
       => faire x 2 pour les liens des montées : */
    printf("Nb montées faites : %lu\n", total_links);
    total_links *= 2;
    /* y ajouter les liens classiques : */
    total_links +=
        (total_cells * 4) - ((taille_x + taille_y) * taille_z * 2);
    OUT("   Nombre de liens          : ");
    snprintf_format_monetaire(buffer_tmp, sizeof(buffer_tmp) - 1,
                              total_links);
    OUT(buffer_tmp);
    OUT("\n");
    tablinks = calloc(total_links, sizeof(cell_link));
    if (tablinks == NULL) {
        snprintf(buffer_erreur, sizeof(buffer_erreur) - 1,
                 "Erreur d'allocation des liens "
                 "(%lu liens à allouer).\n", total_links);
        OUT(buffer_erreur);
        free(tabcells);
        exit(EXIT_FAILURE);
    }
    OUT("   Allocations réussies.\n");
    OUT("3) Création de tous les liens...\n");
    l = tablinks;
    for (z = 0; z < taille_z; z++) {
        for (y = 0; y < taille_y; y++) {
            for (x = 0; x < taille_x; x++) {
                base = z * taille_x * taille_y;
                c = &tabcells[x + (y * taille_x) + base];
                c->nb_links = 0;
                c->links = l;
                if (x > 0) {
                    c->nb_links += 1;
                    l[0].link = &tabcells[x - 1 + (y * taille_x) + base];
                    l[0].tag = 4;
                    l++;
                }
                if (x < (taille_x - 1)) {
                    c->nb_links += 1;
                    l[0].link = &tabcells[x + 1 + (y * taille_x) + base];
                    l[0].tag = 2;
                    l++;
                }
                if (y > 0) {
                    c->nb_links += 1;
                    l[0].link = &tabcells[x + ((y - 1) * taille_x) + base];
                    l[0].tag = 1;
                    l++;
                }
                if (y < (taille_y - 1)) {
                    c->nb_links += 1;
                    l[0].link = &tabcells[x + ((y + 1) * taille_x) + base];
                    l[0].tag = 3;
                    l++;
                }
                /* rappel : soit tag pointe vers la cellule dont il faut
                   faire un "force_path" soit tag est très gros
                   (tag > taille totale) et signifie "force_wall" */
                if (c->tag & TAGS_FORCER_PATHS_UD) {
                    c->nb_links += 1;
                    printf
                        ("Cellule n.%lu, tag %lu, (%lu,%lu,%lu), ajout path ",
                         c->no, c->tag, x, y, z);
                    if (c->tag & TAG_FORCER_PATH_U) {
                        printf("UP\n");
                        l[0].link = &tabcells[c->no + taille_x_y];
                    } else {
                        l[0].link = &tabcells[c->no - taille_x_y];
                        printf("DOWN\n");
                    }
                    l[0].tag = 5;
                    l++;
                }
            }
        }
    }
    OUT("   Ok.\n");
    for (i = 0; i < total_cells; i++) {
        cell_print(&(tabcells[i]));
    }
    for (z = 0; z < taille_z; z++) {
        for (y = 0; y < taille_y; y++) {
            for (x = 0; x < taille_x; x++) {
                c = &tabcells[x + (y * taille_x) +
                              (z * taille_x * taille_y)];
                if (c->tag & TAG_FORCER_WALL_N) {
                    link_to_wall_selon_no(c,
                                          (unsigned long) c->no -
                                          taille_x);
                }
                if (c->tag & TAG_FORCER_WALL_E) {
                    link_to_wall_selon_no(c, (unsigned long) c->no + 1);
                }
                if (c->tag & TAG_FORCER_WALL_S) {
                    link_to_wall_selon_no(c,
                                          (unsigned long) c->no +
                                          taille_x);
                }
                if (c->tag & TAG_FORCER_WALL_O) {
                    link_to_wall_selon_no(c, (unsigned long) c->no - 1);
                }

                if (c->tag & TAG_FORCER_PATH_N) {
                    link_to_path_selon_no(c,
                                          (unsigned long) c->no -
                                          taille_x);
                }
                if (c->tag & TAG_FORCER_PATH_E) {
                    link_to_path_selon_no(c, (unsigned long) c->no + 1);
                }
                if (c->tag & TAG_FORCER_PATH_S) {
                    link_to_path_selon_no(c,
                                          (unsigned long) c->no +
                                          taille_x);
                }
                if (c->tag & TAG_FORCER_PATH_O) {
                    link_to_path_selon_no(c, (unsigned long) c->no - 1);
                }
                if (c->tag & TAG_FORCER_PATH_U) {
                    link_to_path_selon_no(c,
                                          (unsigned long) c->no +
                                          taille_x_y);
                }
                if (c->tag & TAG_FORCER_PATH_D) {
                    link_to_path_selon_no(c,
                                          (unsigned long) c->no -
                                          taille_x_y);
                }
            }
        }
    }
    /* maintenant tous les paths + walls à faire sont finis.
       il faut finaliser tous les sauts en mettant ce qu'il leur reste en walls */
    for (z = 0; z < taille_z; z++) {
        for (y = 0; y < taille_y; y++) {
            for (x = 0; x < taille_x; x++) {
                c = &tabcells[x + (y * taille_x) + (z * taille_x_y)];
                if (c->tag & TAGS_FORCER_PATHS_UD) {
                    printf
                        ("Cellule n.%lu a un lien vers le %s\n",
                         c->no,
                         ((c->tag & TAG_FORCER_PATH_D) >
                          0 ? "bas" : "haut"));
                    printf("Tout ce qu'il reste en walls\n");
                    printf("Avant :\n");
                    cell_print(c);
                    while (c->nb_links) {
                        cell_carre_deplacer_link_vers_walls(c, 0);
                    }
                    printf("Après :\n");
                    cell_print(c);
                }
            }
        }
    }
    for (z = 0; z < taille_z; z++) {
        for (y = 0; y < taille_y; y++) {
            for (x = 0; x < taille_x; x++) {
                c = &tabcells[x + (y * taille_x) + (z * taille_x_y)];
                if ((c->nb_links == 0) && (c->nb_paths == 0)) {
                    printf("Cellule n.%lu n'a que des murs.\n", c->no);
                    cell_print(c);
                }
            }
        }
    }

    print_laby(tabcells, taille_x, taille_y, taille_z);
    snprintf(buffer_erreur, sizeof(buffer_erreur) - 1, "Tout est ok !\n");
    printf("Verif liens = %d\n",
           verif_liens_bidirectionnels(total_cells, tabcells,
                                       buffer_erreur,
                                       sizeof(buffer_erreur) - 1));
    printf("%s\n", buffer_erreur);
    for (i = 0; i < total_cells; i++) {
        cell_print(&(tabcells[i]));
    }
    OUT("4) Génération du script...\n");
    if (genere_fichier(nom_fichier_sortie,
                       total_cells,
                       total_links,
                       tabcells,
                       taille_x, taille_y, taille_z,
                       buffer_erreur, sizeof(buffer_erreur) - 1)) {
        snprintf(buffer_erreur, sizeof(buffer_erreur) - 1,
                 "Erreur d'allocation des liens "
                 "(%lu liens à allouer).\n", total_links);
        OUT(buffer_erreur);
        free(tablinks);
        free(tabcells);
        exit(EXIT_FAILURE);
    }

    free(tablinks);
    free(tabcells);
    sprintf_time_desc(buffer_tmp, sizeof(buffer_tmp) - 1,
                      "-  Fin du programme");
    OUT(buffer_tmp);
    exit(EXIT_SUCCESS);
}
