#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"

#define MAX_LONGUEUR_ORDRES (unsigned long)60
/*#define AJOUT_ORDRES( z ) printf("%s\n", z); ajouter_ordres(&houtput, z, MAX_LONGUEUR_ORDRES)*/
#define AJOUT_ORDRES( z ) ajouter_ordres(&houtput, z, MAX_LONGUEUR_ORDRES)

#define TM_X      32
#define TM_Y      32
#define TM_Z      32

/* nb max d'objets différents possibles dans le tableau */
#define MAX_OBJETS 30

/* énumération des objets */
#define OBJET_VIDE            0
#define OBJET_BLOC            1
#define OBJET_SAUT            2
#define OBJET_SAUT_COMPLEMENT 3
#define OBJET_DEPART          4
#define OBJET_ARRIVEE         5
#define OBJET_LUMIERE         6

unsigned long total_cells = 0;
unsigned long total_links = 0;
unsigned long largeur = 0;
unsigned long longueur = 0;
unsigned long hauteur = 0;

char ordres[200] = "";

void ecrire_ordres(FILE ** houtput)
{
    if (strlen(ordres)) {
        fprintf(*houtput, "TMExecute( \"%s\" )\n", ordres);
        ordres[0] = 0;
    }
}

void ajouter_ordres(FILE ** houtput, char *nouveaux_ordres,
                    unsigned long max_longueur)
{
    strcat(ordres, nouveaux_ordres);
    if (strlen(ordres) > max_longueur) {
        ecrire_ordres(houtput);
    }
}

void fichier_ecrire_entete(FILE ** houtput)
{
    fprintf(*houtput, ";-------------------------------\n");
    fprintf(*houtput, "; Fichier de génération de labyrinthe\n;\n");
    fprintf(*houtput, "; (c) Olivier Pons\n;\n");
    fprintf_time_desc(*houtput, "; fichier généré ");
    fprintf(*houtput, ";\n");

    fprintf(*houtput, "#Include Tm.ahk\n\n");
    fprintf(*houtput, "TailleX := %lu\n", largeur);
    fprintf(*houtput, "TailleY := %lu\n", longueur);
    fprintf(*houtput, "TailleZ := %lu\n\n", hauteur);
    fprintf(*houtput, ";-----------------------------------\n");
    fprintf(*houtput, "\n");
    fprintf(*houtput, "ActiverFenetre( \"TrackMania\" )\n");
    fprintf(*houtput, "Sleep, 3000\n");
    fprintf(*houtput, "\n");
    fprintf(*houtput,
            ";TMCarresARelierPoser( TailleX, TailleY, TailleZ )\n");
}

/* fonction appelée lors de la vérification de la validité
   du fichier en entrée.
   fonction qui intègre les paramètres éventuels
   du fichier en entrée */
int analyse_params(char *param_lu, char
                   *buffer_erreur, int maxlen_erreur)
{
    int ok;
    unsigned long i;


    /* déclaration de la largeur en tant que paramètre */
    ok = sscanf(param_lu, "params:largeur=%lu", &i);
    if (ok == 1) {
        if (largeur) {
            snprintf(buffer_erreur, maxlen_erreur,
                     "Erreur : deux fois déclaration de la largeur.\n");
            return -1;
        }
        largeur = i;
    }

    /* déclaration de la longueur en tant que paramètre */
    ok = sscanf(param_lu, "params:longueur=%lu", &i);
    if (ok == 1) {
        if (longueur) {
            snprintf(buffer_erreur, maxlen_erreur,
                     "Erreur : deux fois déclaration "
                     "de la longueur.\n");
            return -1;
        }
        longueur = i;
    }

    /* déclaration de la hauteur en tant que paramètre */
    ok = sscanf(param_lu, "params:hauteur=%lu", &i);
    if (ok == 1) {
        if (hauteur) {
            snprintf(buffer_erreur, maxlen_erreur,
                     "Erreur : deux fois déclaration "
                     "de la hauteur.\n");
            return -1;
        }
        hauteur = i;
    }

    return 0;
}


/* fonction appelée lors de la vérification de la validité
   du fichier en entrée.
   fonction de vérification que les paramètres sont ok */
int verif_params_ok(char *buffer_erreur, int maxlen_erreur)
{
    if (largeur == 0) {
        snprintf(buffer_erreur, maxlen_erreur,
                 "Déclaration de cellule trop tôt :\n"
                 "La largeur n'est pas encore déclarée.\n");
        return -1;
    }
    if (longueur == 0) {
        snprintf(buffer_erreur, maxlen_erreur,
                 "Déclaration de cellule trop tôt :\n"
                 "La longueur n'est pas encore déclarée.\n");
        return -1;
    }
    if (hauteur == 0) {
        snprintf(buffer_erreur, maxlen_erreur,
                 "Déclaration de cellule trop tôt :\n"
                 "La hauteur n'est pas encore déclarée.\n");
        return -1;
    }

    return 0;
}

struct cell_tm {
    unsigned char objet;
    unsigned char nb_rotations;
};

void assigne_si_vide(struct cell_tm *c_tm, int id_objet)
{
    if (c_tm->objet == OBJET_VIDE) {
        c_tm->objet = id_objet;
    }
}
int main(int argc, char *argv[])
{
    FILE *houtput;
    int ret;
    char buffer_erreur[800];
    struct cell *tabcells;
    struct cell_link *tablinks;

    struct cell_tm tab_tm[TM_X][TM_Y][TM_Z];
    long i, j, k, x, y, z;
    long add_x, add_y, add_z;

    /* pour les optimisations */
    long size, diff;
    struct cell *c;
    long objet_courant;
    long tm_x, tm_y, tm_z;
    char buf[2];

    int tab_objets[MAX_OBJETS];
    int nb_objets;

    if (argc != 3) {
        printf("%s [nom fichier maze référence] [nom fichier sortie]\n",
               argv[0]);
        exit(EXIT_FAILURE);
    }

    printf_time_debut_programme();

    printf("    Génération des macros "
           "Autohotkey pour Trackmania...\n");

    printf("(1) Vérification de cohérence du fichier...\n");
    ret = laby_verif_fichier(argv[1],
                             &total_cells,
                             &total_links,
                             &analyse_params,
                             &verif_params_ok,
                             buffer_erreur, sizeof(buffer_erreur) - 1);
    if (ret) {
        printf("Erreur de vérification "
               "du fichier labyrinthe :\n%s", buffer_erreur);
        exit(EXIT_FAILURE);
    }

    printf("    Aucune incohérence détectée.\n");

    printf("(2) Allocations mémoire...\n");
    printf("    Nombre de cellules       : ");
    print_format_monetaire(total_cells);
    printf("\n    Nombre de liens          : ");
    print_format_monetaire(total_links);
    printf("\n");
    tabcells = calloc(total_cells, sizeof(cell));
    if (tabcells == NULL) {
        printf("Erreur d'allocation des cellules "
               "(%lu cellules à allouer).\n", total_cells);
        exit(EXIT_FAILURE);
    }
    for (i = 0; i < (long) total_cells; i++)
        tabcells[i].no = i;

    tablinks = calloc(total_links, sizeof(cell_link));
    if (tablinks == NULL) {
        printf("Erreur d'allocation des liens "
               "(%lu liens à allouer).\n", total_links);
        free(tabcells);
        exit(EXIT_FAILURE);
    }
    printf("    Allocations réussies.\n");

    printf("(3) Lecture du labyrinthe...\n");
    ret =
        laby_lecture(argv[1], tabcells, tablinks, buffer_erreur,
                     sizeof(buffer_erreur) - 1);
    if (ret) {
        printf("Erreur de lecture du "
               "du fichier labyrinthe :\n%s", buffer_erreur);
        free(tablinks);
        free(tabcells);
        exit(EXIT_FAILURE);
    }
    printf("    Lecture réussie.\n");


    printf("(4) Génération du script...\n");

    /* pour chaque cellule, remplissage au bon endroit d'un bloc au centre
       et des blocs N / E / S / O
     */
    for (x = 0; x < TM_X; x++) {
        for (y = 0; y < TM_Y; y++) {
            for (z = 0; z < TM_Z; z++) {
                tab_tm[x][y][z].objet = OBJET_VIDE;
                tab_tm[x][y][z].nb_rotations = 0;
            }
        }
    }
    size = largeur * longueur;
    for (i = 0; i < (long) total_cells; i++) {
        z = i / size;
        x = (i % size) % largeur;
        y = (i % size) / largeur;
        z *= 2;
        x *= 2;
        y *= 2;
        c = &tabcells[i];

        /* Bloc Centre cellule */
        x++;
        y++;
        tab_tm[x][y][z].objet = OBJET_BLOC;
        tab_tm[x][y][z].nb_rotations = 0;
        for (j = 0; j < c->nb_paths; j++) {
            diff = (long) c->no - (long) c->paths[j].link->no;
            if (diff == (-size)) {
                /* lien vers le haut */
                /*
                if (z) {
                    tab_tm[x - 1][y - 1][z].objet = OBJET_LUMIERE;
                    tab_tm[x + 1][y - 1][z].objet = OBJET_LUMIERE;
                    tab_tm[x - 1][y + 1][z].objet = OBJET_LUMIERE;
                    tab_tm[x + 1][y + 1][z].objet = OBJET_LUMIERE;
                    tab_tm[x - 1][y - 1][z + 1].objet = OBJET_LUMIERE;
                    tab_tm[x + 1][y - 1][z + 1].objet = OBJET_LUMIERE;
                    tab_tm[x - 1][y + 1][z + 1].objet = OBJET_LUMIERE;
                    tab_tm[x + 1][y + 1][z + 1].objet = OBJET_LUMIERE;
                }
                */
                tab_tm[x][y][z].objet = OBJET_VIDE;
                /* toujours 2 paths : lien haut et sortie */
                if (c->nb_paths != 2) {
                    printf("Erreur : le fichier de labyrinthe "
                           "n'est pas construit pour Trackmania\n");
                    free(tablinks);
                    free(tabcells);
                    exit(EXIT_FAILURE);
                }
                /* prendre l'autre lien */
                j = (j == 0 ? 1 : 0);
                diff = (long) c->no - (long) c->paths[j].link->no;
                if (diff == 1) {
                    /* Ouest */
                    tab_tm[x - 1][y][z].objet = OBJET_SAUT;
                    tab_tm[x - 1][y][z].nb_rotations = 1;
                    printf("Ouest %ld %ld %ld => diff = %ld\n", x, y, z,
                           diff);
                } else if (diff == (-1)) {
                    /* Est */
                    printf("Est %ld %ld %ld => diff = %ld\n", x, y, z,
                           diff);
                    tab_tm[x + 1][y][z].objet = OBJET_SAUT;
                    tab_tm[x + 1][y][z].nb_rotations = 3;
                } else if (diff == (-(long) largeur)) {
                    /* Sud */
                    printf("Sud %ld %ld %ld => diff = %ld\n", x, y, z,
                           diff);
                    tab_tm[x][y + 1][z].objet = OBJET_SAUT;
                } else if (diff == (long) largeur) {
                    /* Nord */
                    printf("Nord %ld %ld %ld => diff = %ld\n", x, y, z,
                           diff);
                    tab_tm[x][y - 1][z].objet = OBJET_SAUT;
                    tab_tm[x][y - 1][z].nb_rotations = 2;
                }
                break;
            } else if (diff == size) {
                /* lien vers le bas */ 
                /*
                if (z) {
                    tab_tm[x - 1][y - 1][z].objet = OBJET_LUMIERE;
                    tab_tm[x + 1][y - 1][z].objet = OBJET_LUMIERE;
                    tab_tm[x - 1][y + 1][z].objet = OBJET_LUMIERE;
                    tab_tm[x + 1][y + 1][z].objet = OBJET_LUMIERE;
                    tab_tm[x - 1][y - 1][z + 1].objet = OBJET_LUMIERE;
                    tab_tm[x + 1][y - 1][z + 1].objet = OBJET_LUMIERE;
                    tab_tm[x - 1][y + 1][z + 1].objet = OBJET_LUMIERE;
                    tab_tm[x + 1][y + 1][z + 1].objet = OBJET_LUMIERE;
                }
                */
                tab_tm[x][y][z].objet = OBJET_VIDE;
                tab_tm[x][y][z - 1].objet = OBJET_SAUT_COMPLEMENT;
                /* toujours 2 paths : lien bas et sortie */
                if (c->nb_paths != 2) {
                    printf("Erreur : le fichier de labyrinthe "
                           "n'est pas construit pour Trackmania\n");
                    free(tablinks);
                    free(tabcells);
                    exit(EXIT_FAILURE);
                }
                /* prendre l'autre lien */
                j = (j == 0 ? 1 : 0);
                diff = (long) c->no - (long) c->paths[j].link->no;
                if (diff == 1) {
                    /* Ouest */
                    tab_tm[x][y][z - 1].nb_rotations = 3;
                } else if (diff == (-1)) {
                    /* Est */
                    tab_tm[x][y][z - 1].nb_rotations = 1;
                } else if (diff == (-(long) largeur)) {
                    /* Sud */
                    tab_tm[x][y][z - 1].nb_rotations = 2;
                } else if (diff == (long) largeur) {
                    /* Nord */
                    tab_tm[x][y][z - 1].nb_rotations = 0;
                }

                break;
            } else if (diff == 1) {
                /* Ouest */
                assigne_si_vide(&tab_tm[x - 1][y][z], OBJET_BLOC);
            } else if (diff == (-1)) {
                /* Est */
                assigne_si_vide(&tab_tm[x + 1][y][z], OBJET_BLOC);
            } else if (diff == (-(long) largeur)) {
                /* Sud */
                assigne_si_vide(&tab_tm[x][y + 1][z], OBJET_BLOC);
            } else if (diff == (long) largeur) {
                /* Nord */
                assigne_si_vide(&tab_tm[x][y - 1][z], OBJET_BLOC);
            }
        }
    }
/*  ajout des murs exterieurs
*/
    for (x = 0; x < ((long) largeur + 1); x++) {
        for (z = 0; z < ((long) hauteur * 2); z++) {
            tab_tm[x * 2][0][z].objet = OBJET_BLOC;
            tab_tm[x * 2][longueur * 2][z].objet = OBJET_BLOC;
        }
    }
    for (y = 0; y < ((long) longueur + 1); y++) {
        for (z = 0; z < ((long) hauteur * 2); z++) {
            tab_tm[0][y * 2][z].objet = OBJET_BLOC;
            tab_tm[largeur * 2][y * 2][z].objet = OBJET_BLOC;
        }
    }
    /* poser le départ tout en haut à gauche */
    tab_tm[1][0][1].objet = OBJET_DEPART;
    tab_tm[1][0][1].nb_rotations = 2;
    /* poser l'arrivé le plus en bas à droite */
    x = (largeur * 2) - 1;
    y = (longueur * 2) + 1;
    z = (hauteur - 2) * 2;
    if (y >= TM_Y) {
        y = TM_Y - 1;
    }
    tab_tm[x][y][z + 1].objet = OBJET_VIDE;
    tab_tm[x][y][z + 2].objet = OBJET_VIDE;
    tab_tm[x][y][z + 1].objet = OBJET_ARRIVEE;
    tab_tm[x][y][z + 1].nb_rotations = 2;

    /* faire l'inventaire de tous les types de blocs recensés */
    nb_objets = 0;
    for (x = 0; x < TM_X; x++) {
        for (y = 0; y < TM_Y; y++) {
            for (z = 0; z < TM_Z; z++) {
                j = tab_tm[x][y][z].objet;
                if (j == OBJET_VIDE) {
                    continue;
                }
                /* chercher si on a déjà l'objet dans la liste */
                for (i = 0; i < nb_objets; i++) {
                    if (tab_objets[i] == j) {
                        break;
                    }
                }
                if (i == nb_objets) {
                    /* pas trouvé => ajout dans tableau */
                    tab_objets[nb_objets++] = j;
                }
            }
        }
    }
    printf("    Nombre de blocs différents : %d\n", nb_objets);
    /* inventaire fini */

    /* ecriture de l'entete */
    houtput = fopen(argv[2], "wb+");
    if (houtput == NULL) {
        printf("Erreur d'ecriture dans le fichier <%s>.\n", argv[2]);
        free(tablinks);
        free(tabcells);
        exit(EXIT_FAILURE);
    }
    fichier_ecrire_entete(&houtput);
    /* pour chaque type de bloc, déplacer le curseur là où il 
       faut et poser le bloc */
    /* inits à ne faire qu'une fois */
    i = tm_x = tm_y = tm_z = 0;
    z = y = x = 0;
    add_x = add_y = add_z = 1;
    AJOUT_ORDRES("H");
    buf[1] = 0;
    /* boucle de parcours du tableau */
    while (i < nb_objets) {
        objet_courant = tab_objets[i];
        j = 0;
        while (1) {
            if (tab_tm[x][y][z].objet == objet_courant) {
                if (j == 0) {
                    /* pas encore demandé d'écrire
                       l'objet courant => le faire */
                    j = 1;
                    ecrire_ordres(&houtput);
                    switch (objet_courant) {
                    case OBJET_VIDE:
                        break;
                    case OBJET_BLOC:
                        fprintf(houtput, "TMBlocBasiqueSelectionner()\n");
                        break;
                    case OBJET_SAUT:
                        fprintf(houtput, "TMSautSelectionner()\n");
                        break;
                    case OBJET_SAUT_COMPLEMENT:
                        fprintf(houtput,
                                "TMSautComplementSelectionner()\n");
                        break;
                    case OBJET_DEPART:
                        fprintf(houtput, "TMDepartSelectionner()\n");
                        break;
                    case OBJET_ARRIVEE:
                        fprintf(houtput, "TMArriveeSelectionner()\n");
                        break;
                    case OBJET_LUMIERE:
                        fprintf(houtput, "TMLumiereSelectionner()\n");
                        break;
                    }
                }
/*
                printf("%ld %ld %ld\n", x, y, z);
                printf("%ld %ld %ld ==> %ld %ld %ld\n", tm_x, tm_y,
                       tm_z, x, y, z);
*/
                if (tm_z != z) {
                    while (z != tm_z) {
                        diff = ((tm_z - z) < 0 ? 1 : -1);
                        buf[0] = (diff > 0 ? '+' : '-');
                        AJOUT_ORDRES(buf);
                        tm_z += diff;
                    }
                }
                if (tm_y != y) {
                    diff = ((tm_y - y) < 0 ? 1 : -1);
                    buf[0] = (diff > 0 ? 'D' : 'U');
                    while (tm_y != y) {
                        tm_y += diff;
                        AJOUT_ORDRES(buf);
                    }
                }
                if (tm_x != x) {
                    diff = ((tm_x - x) < 0 ? 1 : -1);
                    buf[0] = (diff > 0 ? 'R' : 'L');
                    while (tm_x != x) {
                        tm_x += diff;
                        AJOUT_ORDRES(buf);
                    }
                }
                for (k = 0; k < tab_tm[x][y][z].nb_rotations; k++) {
                    AJOUT_ORDRES("T");
                }
                AJOUT_ORDRES("*");
                while ((k++) % 4) {
                    AJOUT_ORDRES("T");
                }
            }
            x += add_x;
            if (x < 0) {
                /* X arrivé au bout => demi tour */
                x = 0;
                add_x = 1;
                y += add_y;
            } else if (x == TM_X) {
                /* X arrivé au bout => demi tour */
                x = TM_X - 1;
                add_x = -1;
                y += add_y;
            }
            /* si X arrivé au bout tester Y : */
            if (y < 0) {
                add_y = 1;
                y = 0;
                z += add_z;
            } else if (y == TM_Y) {
                add_y = -1;
                y = TM_Y - 1;
                z += add_z;
            }
            /* si Y arrivé au bout tester Z : */
            if (z < 0) {
                add_z = 1;
                z = 0;
                break;
            } else if (z == TM_Z) {
                add_z = -1;
                z = TM_Z - 1;
                break;
            }
        }

        i++;
    }

    ecrire_ordres(&houtput);
    fclose(houtput);
    printf("    Génération terminée.\n");
    free(tablinks);
    free(tabcells);
    printf_time_fin_programme();
    exit(EXIT_SUCCESS);
}
