// symtab.c -- DVX BASIC symbol table implementation

#include "symtab.h"
#include "thirdparty/stb_ds_wrap.h"

#include <ctype.h>
#include <stdlib.h>
#include <string.h>


// Function prototypes (alphabetical)
BasSymbolT *basSymTabAdd(BasSymTabT *tab, const char *name, BasSymKindE kind, uint8_t dataType);
int32_t basSymTabAllocSlot(BasSymTabT *tab);
void basSymTabEnterFormScope(BasSymTabT *tab, const char *formName);
void basSymTabEnterLocal(BasSymTabT *tab);
BasSymbolT *basSymTabFind(BasSymTabT *tab, const char *name);
BasSymbolT *basSymTabFindGlobal(BasSymTabT *tab, const char *name);
void basSymTabInit(BasSymTabT *tab);
int32_t basSymTabLeaveFormScope(BasSymTabT *tab);
void basSymTabLeaveLocal(BasSymTabT *tab);
static bool namesEqual(const char *a, const char *b);

BasSymbolT *basSymTabAdd(BasSymTabT *tab, const char *name, BasSymKindE kind, uint8_t dataType) {
    // Determine scope: local > form > global.
    // Only variables get SCOPE_FORM; SUBs/FUNCTIONs/CONSTs remain global.
    BasScopeE scope;

    if (tab->inLocalScope) {
        scope = SCOPE_LOCAL;
    } else if (tab->inFormScope && kind == SYM_VARIABLE) {
        scope = SCOPE_FORM;
    } else {
        scope = SCOPE_GLOBAL;
    }

    // Check for duplicate in current scope (skip ended form symbols)
    for (int32_t i = 0; i < tab->count; i++) {
        if (tab->symbols[i]->formScopeEnded) {
            continue;
        }

        if (tab->symbols[i]->scope == scope && namesEqual(tab->symbols[i]->name, name)) {
            return NULL;  // duplicate
        }
    }

    BasSymbolT *sym = (BasSymbolT *)calloc(1, sizeof(BasSymbolT));

    if (!sym) {
        return NULL;
    }

    strncpy(sym->name, name, BAS_MAX_SYMBOL_NAME - 1);
    sym->name[BAS_MAX_SYMBOL_NAME - 1] = '\0';
    sym->kind      = kind;
    sym->scope     = scope;
    sym->dataType  = dataType;
    sym->isDefined = true;

    if (scope == SCOPE_FORM && tab->formScopeName[0]) {
        strncpy(sym->formName, tab->formScopeName, BAS_MAX_SYMBOL_NAME - 1);
        sym->formName[BAS_MAX_SYMBOL_NAME - 1] = '\0';
    }

    arrput(tab->symbols, sym);
    tab->count = (int32_t)arrlen(tab->symbols);
    return sym;
}


int32_t basSymTabAllocSlot(BasSymTabT *tab) {
    if (tab->inLocalScope) {
        return tab->nextLocalIdx++;
    }

    if (tab->inFormScope) {
        return tab->nextFormVarIdx++;
    }

    return tab->nextGlobalIdx++;
}


void basSymTabEnterFormScope(BasSymTabT *tab, const char *formName) {
    tab->inFormScope = true;
    strncpy(tab->formScopeName, formName, BAS_MAX_SYMBOL_NAME - 1);
    tab->formScopeName[BAS_MAX_SYMBOL_NAME - 1] = '\0';
    tab->nextFormVarIdx   = 0;
    tab->formScopeSymStart = tab->count;
}


void basSymTabEnterLocal(BasSymTabT *tab) {
    tab->inLocalScope = true;
    tab->nextLocalIdx = 0;
}


BasSymbolT *basSymTabFind(BasSymTabT *tab, const char *name) {
    // Search local scope first
    if (tab->inLocalScope) {
        for (int32_t i = tab->count - 1; i >= 0; i--) {
            if (tab->symbols[i]->scope == SCOPE_LOCAL && namesEqual(tab->symbols[i]->name, name)) {
                return tab->symbols[i];
            }
        }
    }

    // Search form scope and global scope
    for (int32_t i = tab->count - 1; i >= 0; i--) {
        if (tab->symbols[i]->formScopeEnded) {
            continue;
        }

        if ((tab->symbols[i]->scope == SCOPE_FORM || tab->symbols[i]->scope == SCOPE_GLOBAL) &&
            namesEqual(tab->symbols[i]->name, name)) {
            return tab->symbols[i];
        }
    }

    return NULL;
}


BasSymbolT *basSymTabFindGlobal(BasSymTabT *tab, const char *name) {
    for (int32_t i = 0; i < tab->count; i++) {
        if (tab->symbols[i]->formScopeEnded) {
            continue;
        }

        if (tab->symbols[i]->scope == SCOPE_GLOBAL && namesEqual(tab->symbols[i]->name, name)) {
            return tab->symbols[i];
        }
    }

    return NULL;
}


void basSymTabInit(BasSymTabT *tab) {
    memset(tab, 0, sizeof(*tab));
}


int32_t basSymTabLeaveFormScope(BasSymTabT *tab) {
    int32_t varCount = tab->nextFormVarIdx;

    // Mark all form-scope symbols added since BEGINFORM as ended
    for (int32_t i = tab->formScopeSymStart; i < tab->count; i++) {
        if (tab->symbols[i]->scope == SCOPE_FORM) {
            tab->symbols[i]->formScopeEnded = true;
        }
    }

    tab->inFormScope       = false;
    tab->formScopeName[0]  = '\0';
    tab->nextFormVarIdx    = 0;
    tab->formScopeSymStart = 0;

    return varCount;
}


void basSymTabLeaveLocal(BasSymTabT *tab) {
    // Remove all local symbols, freeing their dynamic arrays and the
    // symbol struct itself.
    int32_t newCount = 0;

    for (int32_t i = 0; i < tab->count; i++) {
        if (tab->symbols[i]->scope == SCOPE_LOCAL) {
            arrfree(tab->symbols[i]->patchAddrs);
            arrfree(tab->symbols[i]->fields);
            free(tab->symbols[i]);
        } else {
            if (i != newCount) {
                tab->symbols[newCount] = tab->symbols[i];
            }

            newCount++;
        }
    }

    arrsetlen(tab->symbols, newCount);
    tab->count        = newCount;
    tab->inLocalScope = false;
    tab->nextLocalIdx = 0;
}


// ============================================================
// Case-insensitive name comparison
// ============================================================
static bool namesEqual(const char *a, const char *b) {
    while (*a && *b) {
        char ca = *a >= 'a' && *a <= 'z' ? *a - 32 : *a;
        char cb = *b >= 'a' && *b <= 'z' ? *b - 32 : *b;

        if (ca != cb) {
            return false;
        }

        a++;
        b++;
    }

    return *a == *b;
}
