#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <math.h>
#include <stdbool.h>
#include <unistd.h>
#include <termios.h>
#include <limits.h>
#include <time.h>

#include "hash.h"

// ------------------------------------------------------------------------------------------------------- //

bool linkedList_mem(linkedList lst, int chx, int chy) {
    if(lst == NULL) {
        return false ;
    } else if(lst->chx == chx && lst->chy == chy) {
        return true ;
    }
    return linkedList_mem(lst->next, chx, chy) ;
}

room linkedList_find(linkedList lst, int chx, int chy) {
    if(lst == NULL) {
        fprintf(stderr, "ERROR : (%d, %d) is not a valid key in hash\n", chx, chy);
        exit(1);
    } else if(lst->chx == chx && lst->chy == chy) {
        return *(lst->area) ;
    }
    return linkedList_find(lst->next, chx, chy) ;
}

room* linkedList_find_opt(linkedList lst, int chx, int chy) {
    if(lst == NULL) {
        return NULL ;
    } else if(lst->chx == chx && lst->chy == chy) {
        return (lst->area) ;
    }
    return linkedList_find_opt(lst->next, chx, chy) ;
}

void linkedList_add(linkedList lst, int chx, int chy, room* area) {
    if(lst == NULL) {
        fprintf(stderr, "ERROR : cannot add to NULL\n");
        exit(1) ;
    } else if(lst->next == NULL) {
        cell* nex = malloc(sizeof(cell));
        nex->area = area ;
        nex->chx = chx ;
        nex->chy = chy ;
        nex->next = NULL ;
        lst->next = nex ;
    } else {
        linkedList_add(lst->next, chx, chy, area) ;
    }
}

void free_all_cubes(room* r) {
    if(r != NULL) {
        for(int k = 0; k < r->map_size; k++) {
            free(r->map[k]);
        }
        for(int k = 0; k < r->tps_size; k++) {
            free(r->tps[k]->hitbox);
            free(r->tps[k]);
        }
        for(int k = 0; k < r->ent_memlen; k++) {
            //free(r->ents[k]->metach1);
            //free(r->ents[k]->metach2);
            free(r->ents[k]->hitpoints);
            free(r->ents[k]->pos);
            free(r->ents[k]);
        }
        free(r->ents);
        free(r->tps);
        free(r->map);
    }
}

void linkedList_free(linkedList lst) {
    // frees rooms as well (requires all created rooms to be there)
    if(lst != NULL) {
        printf("freeing (%d, %d)\n", lst->chx, lst->chy);
        free_all_cubes(lst->area);
        linkedList_free(lst->next);
        free(lst->area);
        free(lst);
    }
}

// ------------------------------------------------------------------------------------------------------- //

int cantor(int x, int y) {
    return ((x + y) * (x + y + 1) / 2) + y;
}

int generate_hash(int x, int y) {
    if (x < 0) {
        x = -2*x - 1;
    } else {
        x = 2 * x;
    }
    if (y < 0) {
        y = -2*y - 1;
    } else {
        y = 2 * y;
    }
    return cantor(x, y);
}

hashtbl hashtbl_generate(int initial_size) {
    hashtbl h = malloc(sizeof(hashtbl_0));
    h->insertedElts = 0 ;
    h->tabLength = initial_size ;
    h->maxInserted = initial_size/2 ;
    h->tab = malloc(sizeof(linkedList)*initial_size) ;
    for(int k = 0; k < initial_size; k++) {
        h->tab[k] = NULL ;
    }
    return h ;
}

bool hashtbl_mem(hashtbl tbl, int chx, int chy) {
    int id = generate_hash(chx, chy)%(tbl->tabLength) ;
    return linkedList_mem(tbl->tab[id], chx, chy) ;
}

room hashtbl_find(hashtbl tbl, int chx, int chy) {
    int id = generate_hash(chx, chy)%(tbl->tabLength) ;
    return linkedList_find(tbl->tab[id], chx, chy) ;
}

room* hashtbl_find_opt(hashtbl tbl, int chx, int chy) {
    int id = generate_hash(chx, chy)%(tbl->tabLength) ;
    return linkedList_find_opt(tbl->tab[id], chx, chy) ;
}

void hashtbl_add(hashtbl tbl, int chx, int chy, room* area) {
    int id = generate_hash(chx, chy)%(tbl->tabLength) ;
    tbl->insertedElts += 1 ;
    if(tbl->tab[id] != NULL) {
        linkedList_add(tbl->tab[id], chx, chy, area);
    } else {
        linkedList nex = malloc(sizeof(cell));
        nex->area = area ;
        nex->chx = chx ;
        nex->chy = chy ;
        nex->next = NULL ;
        tbl->tab[id] = nex ;
    }
}

void hashtbl_free(hashtbl tbl) {
    for(int k = 0; k < tbl->tabLength; k++) {
        linkedList_free(tbl->tab[k]);
    }
    free(tbl->tab);
    free(tbl);
}