#include "scope.h"
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#define DEFAULT_SIZE 16

static void scope_init(struct scope* scope) {
    scope->types = ccc_alloc(DEFAULT_SIZE * sizeof(struct type_def*));
    scope->type_cap = DEFAULT_SIZE;
    scope->vars = ccc_alloc(DEFAULT_SIZE * sizeof(struct var_def*));
    scope->var_cap = DEFAULT_SIZE;
}

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

void scope_destroy(struct scope* scope) {
    for (integral_t i = 0; i < scope->type_cap; i++) {
        if (scope->types[i] != NULL) {
            free(scope->types[i]);
        }
    }
    free(scope->types);

    for (integral_t i = 0; i < scope->var_cap; i++) {
        if (scope->vars[i] != NULL) {
            var_destroy(scope->vars[i]);
            free(scope->vars[i]);
        }
    }
    free(scope->vars);
}

static inline integral_t advance_hash(
    integral_t hash,
    integral_t val,
    integral_t cap
) {
    return ((hash << 5) - hash + val) % cap;;
}

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

static struct type_alias** type_cell(
    const struct scope* scope,
    const char* name
) {
    integral_t orig_idx = hash_name(name, scope->type_cap);
    integral_t idx = orig_idx;

    do {
        if (scope->types[idx] == NULL
                || strcmp(name, scope->types[idx]->name) == 0)
            return &scope->types[idx];
    } while ((idx = (idx + 1) % scope->type_cap) != orig_idx);
    return NULL;
}

static void rehash_types(struct scope* scope) {
    struct type_alias** old_types = scope->types;
    integral_t old_cap = scope->type_cap;

    scope->type_cap = (scope->type_cap + 1) << 1;
    scope->types = ccc_alloc(scope->type_cap * sizeof(struct type_def*));

    for (integral_t i = 0; i < old_cap; i++) {
        if (old_types[i] == NULL) continue;

        struct type_alias** cell = type_cell(scope, old_types[i]->name);
        if (cell == NULL) {
            fprintf(stderr, "ccc: types rehash failed, likely a bug\n");
            exit(1);
        }
        *cell = old_types[i];
    }

    free(old_types);
}

static struct var_def** var_cell(
    const struct scope* scope,
    const char* name
) {
    integral_t orig_idx = hash_name(name, scope->var_cap);
    integral_t idx = orig_idx;

    do {
        if (scope->vars[idx] == NULL
                || strcmp(name, scope->vars[idx]->name) == 0)
            return &scope->vars[idx];
    } while ((idx = (idx + 1) % scope->var_cap) != orig_idx);
    return NULL;
}

static void rehash_vars(struct scope* scope) {
    struct var_def** old_vars = scope->vars;
    integral_t old_cap = scope->var_cap;

    scope->var_cap = (scope->var_cap + 1) << 1;
    scope->vars = ccc_alloc(scope->var_cap * sizeof(struct var_def*));

    for (integral_t i = 0; i < old_cap; i++) {
        if (old_vars[i] == NULL) continue;

        struct var_def** cell = var_cell(scope, old_vars[i]->name);
        if (cell == NULL) {
            fprintf(stderr, "ccc: vars rehash failed, likely a bug\n");
            exit(1);
        }
        *cell = old_vars[i];
    }

    free(old_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) {
        struct type_alias** cell = type_cell(scope, 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 = type_cell(scope, type.name);
    while (cell == NULL) {
        rehash_types(scope);
        cell = type_cell(scope, type.name);
    }

    /* 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 = var_cell(scope, 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 = var_cell(scope, var.name);
    while (cell == NULL) {
        rehash_vars(scope);
        cell = var_cell(scope, var.name);
    }

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

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