#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 = calloc(DEFAULT_SIZE, sizeof(struct type_def*));
    scope->type_cap = DEFAULT_SIZE;
    scope->vars = calloc(DEFAULT_SIZE, sizeof(struct var_def*));
    scope->var_cap = DEFAULT_SIZE;
}

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

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

unsigned long long hash_name(const char* name) {
    unsigned long long hash = 0, i = 0;
    while (name[i] != 0) hash = (hash << 5) - hash + name[i++];
    return hash;
}

static struct type_def** type_cell(
    const struct scope* scope,
    const char* name
) {
    unsigned long long orig_idx = hash_name(name) % scope->type_cap;
    unsigned long long 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);

    /* TODO: rehashing :'( */
    fprintf(stderr, "ccc: would have to rehash and I don't wanna\n");
    exit(1);
}

static struct var_def** var_cell(
    const struct scope* scope,
    const char* name
) {
    unsigned long long orig_idx = hash_name(name) % scope->var_cap;
    unsigned long long 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);

    /* TODO: rehashing :'( */
    fprintf(stderr, "ccc: would have to rehash and I don't wanna\n");
    exit(1);
}

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

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

void scope_install_default_types(struct scope* scope) {
    scope_define_type(scope, (struct type_def) {
        .name = "void",
        .size = 0,
    });
    scope_define_type(scope, (struct type_def) {
        .name = "bool",
        .size = 1,
    });
    scope_define_type(scope, (struct type_def) {
        .name = "char",
        .size = 1,
    });
    scope_define_type(scope, (struct type_def) {
        .name = "short",
        .size = 2,
    });
    scope_define_type(scope, (struct type_def) {
        .name = "int",
        .size = 4,
    });
    scope_define_type(scope, (struct type_def) {
        .name = "float",
        .size = 4,
    });
    scope_define_type(scope, (struct type_def) {
        .name = "long",
        .size = 8,
    });
    scope_define_type(scope, (struct type_def) {
        .name = "double",
        .size = 8,
    });
}

bool scope_get_type(
    const struct scope* scope,
    struct type_def* p_entry,
    const char* name
) {
    for (; scope != NULL; scope = scope->next_out) {
        struct type_def* type_def = *type_cell(scope, name);
        if (type_def == NULL) continue;
        if (p_entry != NULL) *p_entry = *type_def;
        return true;
    }
    return false;
}

void scope_define_type(struct scope* scope, struct type_def type) {
    struct type_def** cell = type_cell(scope, type.name);
    if (*cell != NULL) {
        fprintf(stderr, "ccc: error: redefined type %s\n", type.name);
        exit(1);
    }
    *cell = calloc(1, sizeof(struct type_def));
    if (*cell == NULL) {
        fprintf(stderr, "ccc: out of memory\n");
        exit(1);
    }
    **cell = type;
}

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* var_def = *var_cell(scope, name);
        if (var_def == NULL) continue;
        if (p_entry != NULL) *p_entry = *var_def;
        return true;
    }
    return false;
}

void scope_define_var(struct scope* scope, struct var_def var) {
    struct var_def** cell = var_cell(scope, var.name);
    if (*cell == NULL) {
        *cell = calloc(1, sizeof(struct var_def));
        if (*cell == NULL) {
            fprintf(stderr, "ccc: out of memory\n");
            exit(1);
        }
    }
    /* technically C allows redefinition :/ */
    **cell = var;
}
