#include "common.h"
#include "huftree.h"
#include "buffer.h"

#include <assert.h>
#include <stdio.h>
#include <string.h>

/**
 * Trouver les deux sommets de valeur la plus faible parmi
 * tous les sommets pointés par le tableau. Place l'indice
 * du minimum dans `min` et de l'avant-dernier dans `sec`.
 */
static void _findMinimalVertices(HufVertex**, size_t, size_t* min, size_t* sec);

/**
 * Créer une nouvelle chaîne contenant la chaîne donnée
 * suffixée du caractère donné
 */
static char* _appendToString(char*, size_t, char);

/**
 * Remplit le tableau d'étiquettes données avec les étiquettes
 * des feuilles trouvées dans la sous-partie de l'arbre dont
 * le sommet donné est la racine. Toutes les étiquettes ajoutées
 * au tableau seront préfixées de la chaîne passée en paramètre
 */
static void _labelVertex(HufVertex, char**, char*, size_t);

HufTree createTree(bytecount* counts) {
    // Comptage du nombre de caractères différents dans le fichier :
    // il s'agit du nombre de feuilles dans l'arbre binaire
    size_t leaves_count = 0;

    for (size_t i = 0; i < NUM_CHARS; i++) {
        if (counts[i] > 0) {
            leaves_count++;
        }
    }

    // Allocation d'un tableau de `leaves_count` pointeurs vers sommets.
    // Initialement, ce tableau contient les feuilles du futur arbre.
    // Chaque feuille correspond à un caractère du corpus original
    HufVertex** remaining = malloc(leaves_count * sizeof(*remaining));
    size_t next_index = 0;

    for (int i = 0; i < NUM_CHARS; i++) {
        if (counts[i] > 0) {
            remaining[next_index] = malloc(sizeof(*remaining[next_index]));

            remaining[next_index]->value = i;
            remaining[next_index]->count = counts[i];
            remaining[next_index]->child_l = NULL;
            remaining[next_index]->child_r = NULL;

            next_index++;
        }
    }

    // Coeur de l'algorithme. On itère jusqu'à ce que le tableau `remaining`
    // ne contienne plus qu'un élément : la racine de l'arbre. À toute
    // itération, `remaining` pointe sur les sommets qui doivent encore
    // être traités (c-à-d les sommets sans parent), et `remaining_count`
    // est le nombre de sommets à traiter
    size_t remaining_count = leaves_count;

    while (remaining_count > 1) {
        // Recherche des deux sommets A et B de valeurs les plus faibles
        // parmi les sommets pointés par le tableau `remaining`
        size_t min_vert_index, sec_min_vert_index;

        _findMinimalVertices(
            remaining, remaining_count,
            &min_vert_index, &sec_min_vert_index
        );

        HufVertex* min_vert = remaining[min_vert_index];
        HufVertex* sec_min_vert = remaining[sec_min_vert_index];

        // Création d'un sommet parent P pour A et B
        HufVertex* parent = malloc(sizeof(*parent));

        parent->value = -1;
        parent->count = min_vert->count + sec_min_vert->count;
        parent->child_l = min_vert;
        parent->child_r = sec_min_vert;

        // Modification du tableau de pointeurs `remaining` pour
        // faire sortir A et B, faire entrer P, et réduire la longueur
        // de `remaining` de 1 sommet
        remaining[min_vert_index] = parent;
        remaining[sec_min_vert_index] = remaining[remaining_count - 1];
        remaining[remaining_count - 1] = NULL;

        remaining_count--;
    }

    HufTree tree;

    if (leaves_count == 0) {
        // Cas particulier : aucun caractère dans le fichier, dans ce
        // cas l'arbre est l'arbre vide
        tree = NULL;
    } else if (leaves_count == 1) {
        // Cas particulier : un seul type de caractère dans le fichier,
        // dans ce cas on crée un noeud artificiel et une racine. Sans
        // cette opération, on aurait un arbre avec un unique sommet et
        // donc une étiquette vide
        HufTree child_l = remaining[0];
        HufTree child_r = malloc(sizeof(*child_r));
        tree = malloc(sizeof(*tree));

        tree->value = -1;
        tree->count = -1;
        tree->child_l = child_l;
        tree->child_r = child_r;

        child_r->value = -1;
        child_r->count = -1;
        child_r->child_l = NULL;
        child_r->child_r = NULL;
    } else {
        // Sinon, la racine de l'arbre est le dernier sommet encore
        // dans le tableau `remaining`
        tree = remaining[0];
    }

    // Il est désormais possible de désallouer les pointeurs sur les
    // sommets de l'arbre, car la seule connaissance de la racine permet
    // d'accéder à tout l'arbre
    free(remaining);
    return tree;
}

void _findMinimalVertices(
    HufVertex** vertices, size_t size,
    size_t* min_index, size_t* sec_min_index
) {
    // Initialisation de telle sorte qu'initialement
    // on ait `count(min_index) < count(sec_min_index)`
    if (vertices[0]->count < vertices[1]->count) {
        *min_index = 0;
        *sec_min_index = 1;
    } else {
        *min_index = 1;
        *sec_min_index = 0;
    }

    for (size_t i = 2; i < size; i++) {
        double freq = vertices[i]->count;

        if (freq < vertices[*min_index]->count) {
            // Sommet de valeur inférieure au minimum trouvé, la valeur
            // devient le nouveau minimum, le minimum devient second minimum
            *sec_min_index = *min_index;
            *min_index = i;
        } else if (freq < vertices[*sec_min_index]->count) {
            // Sommet de valeur entre le minimum et le second minimum trouvé,
            // la valeur devient le nouveau second minimum
            *sec_min_index = i;
        }
    }
}

void writeTree(HufTree tree, WriteBuffer* buffer) {
    if (tree == NULL) {
        // Écriture de l'arbre vide dans le fichier : aucun
        // bit n'est ajouté
        return;
    }

    if (tree->child_l != NULL && tree->child_r != NULL) {
        // Bit "1" indiquant que le sommet a des enfants
        putBuffer(1, buffer);

        // Écriture du fils gauche et du fils droit
        writeTree(tree->child_l, buffer);
        writeTree(tree->child_r, buffer);
    } else {
        // Bit "0" indiquant que le sommet n'a pas d'enfants
        putBuffer(0, buffer);

        // Écriture de la valeur du sommet
        for (int i = 7; i >= 0; i--) {
            putBuffer((tree->value & (1 << i)) != 0, buffer);
        }
    }
}

HufTree readTree(ReadBuffer* buffer) {
    HufTree tree = malloc(sizeof(*tree));

    char bit = getBuffer(buffer);
    // FIXME: gérer la fin inattendue du fichier

    if (bit == 1) {
        // Sommet avec enfants
        tree->value = -1;
        tree->count = -1;
        tree->child_l = readTree(buffer);
        tree->child_r = readTree(buffer);
    } else if (bit == 0) {
        // Feuille de l'arbre
        int value = 0;

        for (int i = 7; i >= 0; i--) {
            value |= getBuffer(buffer) << i;
        }

        tree->value = value;
        tree->count = 0;
        tree->child_l = NULL;
        tree->child_r = NULL;
    }

    return tree;
}

void freeTree(HufTree tree) {
    if (tree != NULL && tree->child_l != NULL && tree->child_r != NULL) {
        freeTree(tree->child_l);
        freeTree(tree->child_r);
    }

    free(tree);
}

char** createTreeLabels(HufTree input) {
    char** labels = malloc(NUM_CHARS * sizeof(*labels));

    // Initialisation des étiquettes à NULL
    for (size_t i = 0; i < NUM_CHARS; i++) {
        labels[i] = NULL;
    }

    if (input != NULL) {
        _labelVertex(*input, labels, NULL, 0);
    }

    return labels;
}

void _labelVertex(HufVertex vertex, char** labels, char* label, size_t length) {
    if (vertex.child_l != NULL && vertex.child_r != NULL) {
        // Si le sommet a des enfants, poursuite de la récursion.
        // Étiquetage de la partie gauche avec '...0' et de la partie
        // droite avec '...1'
        _labelVertex(
            *vertex.child_l, labels,
            _appendToString(label, length, '0'),
            length + 1
        );

        _labelVertex(
            *vertex.child_r, labels,
            _appendToString(label, length, '1'),
            length + 1
        );

        // Libération de l'étiquette passée en paramètre,
        // qui a désormais été propagée dans les enfants
        free(label);
    } else {
        // Si le sommet est une feuille, étiquetage du caractère
        // associé avec l'étiquette passée en paramètre. Fin de la récursion
        if (vertex.value != -1) {
            labels[vertex.value] = label;
        }
    }
}

char* _appendToString(char* orig, size_t length, char append) {
    char* result = malloc((length + 2) * sizeof(*result));

    if (orig != NULL) {
        strcpy(result, orig);
    }

    result[length] = append;
    result[length + 1] = '\0';
    return result;
}

void freeTreeLabels(char** labels) {
    for (size_t i = 0; i < NUM_CHARS; i++) {
        free(labels[i]);
    }

    free(labels);
}
