#include "scope.h"
#include <stdlib.h>
#include <string.h>

static integral_t hash_name(const char* name, integral_t cap) {
    integral_t hash = 0, i = 0;
    while (name[i] != 0) ADVANCE_HASH(hash, name[i++], cap);
    return hash;
}

static integral_t hash_type(const struct type_alias* type, integral_t cap) {
    return hash_name(type->name, cap);
}

static bool type_eq(const struct type_alias* a, const struct type_alias* b) {
    return strcmp(a->name, b->name) == 0;
}

static integral_t hash_var(const struct var_def* var, integral_t cap) {
    return hash_name(var->name, cap);
}

static bool var_eq(const struct var_def* a, const struct var_def* b) {
    return strcmp(a->name, b->name) == 0;
}

static void var_destroy(struct var_def* var) {
    free(var->name);
}

static void scope_init(struct scope* scope) {
    hm_init(
        &scope->types,
        (hm_hash_fn) hash_type,
        (hm_eq_fn) type_eq,
        NULL);
    hm_init(
        &scope->vars,
        (hm_hash_fn) hash_var,
        (hm_eq_fn) var_eq,
        (hm_destroy_fn) var_destroy);
}

void scope_destroy(struct scope* scope) {
    hm_destroy(&scope->types);
    hm_destroy(&scope->vars);
}

void scope_push(struct scope** p_scope) {
    struct scope* inner_scope = ccc_alloc(sizeof(struct scope));
    scope_init(inner_scope);
    inner_scope->next_out = *p_scope;
    *p_scope = inner_scope;
}

void scope_pop(struct scope** p_scope) {
    *p_scope = (*p_scope)->next_out;
}

bool scope_get_type(
    const struct scope* scope,
    const struct type_alias** p_entry,
    const char* name
) {
    for (; scope != NULL; scope = scope->next_out) {
        const struct type_alias** cell = (const struct type_alias**) hm_cell_r(
            &scope->types,
            &(struct type_alias) { .name = name });
        if (cell == NULL || *cell == NULL) continue;
        if (p_entry != NULL) *p_entry = *cell;
        return true;
    }
    return false;
}

const struct type_alias* scope_define_type(
    struct scope* scope,
    struct type_alias type
) {
    struct type_alias** cell =
        (struct type_alias**) hm_cell_w(&scope->types, &type);

    /* redefinition leaks memory, so refuse */
    if (*cell != NULL) return NULL;

    *cell = ccc_alloc(sizeof(struct type_alias));
    **cell = type;
    return *cell;
}

bool scope_get_var(
    const struct scope* scope,
    struct var_def** p_entry,
    const char* name
) {
    for (; scope != NULL; scope = scope->next_out) {
        struct var_def** cell = (struct var_def**) hm_cell_r(
            &scope->vars,
            &(struct var_def) {.name = (char*) name});
        if (cell == NULL || *cell == NULL) continue;
        if (p_entry != NULL) *p_entry = *cell;
        return true;
    }
    return false;
}

struct var_def* scope_define_var(struct scope* scope, struct var_def var) {
    struct var_def** cell = (struct var_def**) hm_cell_w(&scope->vars, &var);

    /* redefinition leaks memory, so refuse */
    if (*cell != NULL) return NULL;

    if (*cell == NULL) *cell = ccc_alloc(sizeof(struct var_def));
    **cell = var;
    return *cell;
}
