// The MIT License (MIT)
//
// Copyright (C) 2026 Scott Duensing
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to
// deal in the Software without restriction, including without limitation the
// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
// sell copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.  IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.

// codegen.c -- DVX BASIC p-code emitter implementation

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

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <strings.h>


// Function prototypes (alphabetical)
uint16_t basAddConstant(BasCodeGenT *cg, const char *text, int32_t len);
void basAddData(BasCodeGenT *cg, BasValueT val);
void basCodeGenAddDebugVar(BasCodeGenT *cg, const char *name, uint8_t scope, uint8_t dataType, int32_t index, int32_t procIndex, const char *formName);
BasModuleT *basCodeGenBuildModule(BasCodeGenT *cg);
BasModuleT *basCodeGenBuildModuleWithProcs(BasCodeGenT *cg, void *symtab);
void basCodeGenFree(BasCodeGenT *cg);
void basCodeGenInit(BasCodeGenT *cg);
int32_t basCodePos(const BasCodeGenT *cg);
void basEmit16(BasCodeGenT *cg, int16_t v);
void basEmit8(BasCodeGenT *cg, uint8_t b);
static void basEmitBytes(BasCodeGenT *cg, const void *data, int32_t len);
void basEmitDouble(BasCodeGenT *cg, double v);
void basEmitFloat(BasCodeGenT *cg, float v);
void basEmitU16(BasCodeGenT *cg, uint16_t v);
const BasProcEntryT *basModuleFindProc(const BasModuleT *mod, const char *name);
void basPatch16(BasCodeGenT *cg, int32_t pos, int16_t val);
static uint32_t constantHash(const char *text, int32_t len);
static bool isEmittableProc(const BasSymbolT *s);
static bool needsGlobalInit(const BasSymbolT *s);

uint16_t basAddConstant(BasCodeGenT *cg, const char *text, int32_t len) {
    // Intern: compare the precomputed hash and length before touching the
    // string bytes so the scan over the pool is a few integer compares per
    // entry instead of a memcmp.
    uint32_t h = constantHash(text, len);

    for (int32_t i = 0; i < cg->constCount; i++) {
        if (cg->constHashes[i] == h && cg->constants[i]->len == len && memcmp(cg->constants[i]->data, text, len) == 0) {
            return (uint16_t)i;
        }
    }

    if (cg->constCount >= BAS_MAX_CONSTANTS) {
        cg->overflow = true;
        return 0;
    }

    uint16_t idx = (uint16_t)cg->constCount;
    BasStringT *s = basStringNew(text, len);
    arrput(cg->constants, s);
    arrput(cg->constHashes, h);
    cg->constCount = (int32_t)arrlen(cg->constants);
    return idx;
}


void basAddData(BasCodeGenT *cg, BasValueT val) {
    BasValueT copy = basValCopy(val);
    arrput(cg->dataPool, copy);
    cg->dataCount = (int32_t)arrlen(cg->dataPool);
}


void basCodeGenAddDebugVar(BasCodeGenT *cg, const char *name, uint8_t scope, uint8_t dataType, int32_t index, int32_t procIndex, const char *formName) {
    BasDebugVarT dv;
    memset(&dv, 0, sizeof(dv));
    snprintf(dv.name, BAS_MAX_IDENT, "%s", name);
    dv.scope     = scope;
    dv.dataType  = dataType;
    dv.index     = index;
    dv.procIndex = procIndex;

    if (formName && formName[0]) {
        snprintf(dv.formName, BAS_MAX_IDENT, "%s", formName);
    }
    arrput(cg->debugVars, dv);
    cg->debugVarCount = (int32_t)arrlen(cg->debugVars);
}


BasModuleT *basCodeGenBuildModule(BasCodeGenT *cg) {
    // basVmLoadModule refuses a module with more globals than it has
    // slots; never build one (the parser reports the error with a
    // message, this is the last line of defence for other front ends).
    if (cg->globalCount > BAS_VM_MAX_GLOBALS) {
        return NULL;
    }

    BasModuleT *mod = (BasModuleT *)calloc(1, sizeof(BasModuleT));

    if (!mod) {
        return NULL;
    }

    // Copy code
    mod->code = (uint8_t *)malloc(cg->codeLen);

    if (!mod->code) {
        free(mod);
        return NULL;
    }

    memcpy(mod->code, cg->code, cg->codeLen);
    mod->codeLen = cg->codeLen;

    // Copy constant pool (share string refs)
    if (cg->constCount > 0) {
        mod->constants = (BasStringT **)malloc(cg->constCount * sizeof(BasStringT *));

        if (!mod->constants) {
            free(mod->code);
            free(mod);
            return NULL;
        }

        for (int32_t i = 0; i < cg->constCount; i++) {
            mod->constants[i] = basStringRef(cg->constants[i]);
        }
    }

    mod->constCount  = cg->constCount;
    mod->globalCount = cg->globalCount;
    mod->entryPoint  = 0;

    // Copy data pool
    if (cg->dataCount > 0) {
        mod->dataPool = (BasValueT *)malloc(cg->dataCount * sizeof(BasValueT));

        if (!mod->dataPool) {
            // Release the constant pool refs taken above before freeing.
            for (int32_t i = 0; i < cg->constCount; i++) {
                basStringUnref(mod->constants[i]);
            }

            free(mod->constants);
            free(mod->code);
            free(mod);
            return NULL;
        }

        for (int32_t i = 0; i < cg->dataCount; i++) {
            mod->dataPool[i] = basValCopy(cg->dataPool[i]);
        }
    }

    mod->dataCount = cg->dataCount;

    // Copy form variable info
    if (cg->formVarInfoCount > 0) {
        mod->formVarInfo = (BasFormVarInfoT *)malloc(cg->formVarInfoCount * sizeof(BasFormVarInfoT));

        if (mod->formVarInfo) {
            memcpy(mod->formVarInfo, cg->formVarInfo, cg->formVarInfoCount * sizeof(BasFormVarInfoT));
            // Keep count in sync with the pointer: on OOM the array stays
            // NULL, so leaving count nonzero would make consumers deref NULL.
            mod->formVarInfoCount = cg->formVarInfoCount;
        }
    }

    // Copy debug variable info
    if (cg->debugVarCount > 0) {
        mod->debugVars = (BasDebugVarT *)malloc(cg->debugVarCount * sizeof(BasDebugVarT));

        if (mod->debugVars) {
            memcpy(mod->debugVars, cg->debugVars, cg->debugVarCount * sizeof(BasDebugVarT));
            mod->debugVarCount = cg->debugVarCount;
        }
    }

    // Copy UDT type definitions for debugger
    if (cg->debugUdtDefCount > 0) {
        mod->debugUdtDefs = (BasDebugUdtDefT *)malloc(cg->debugUdtDefCount * sizeof(BasDebugUdtDefT));

        if (mod->debugUdtDefs) {
            for (int32_t i = 0; i < cg->debugUdtDefCount; i++) {
                mod->debugUdtDefs[i] = cg->debugUdtDefs[i];
                mod->debugUdtDefs[i].fields = NULL;

                if (cg->debugUdtDefs[i].fieldCount > 0 && cg->debugUdtDefs[i].fields) {
                    mod->debugUdtDefs[i].fields = (BasDebugFieldT *)malloc(
                        cg->debugUdtDefs[i].fieldCount * sizeof(BasDebugFieldT));

                    if (mod->debugUdtDefs[i].fields) {
                        memcpy(mod->debugUdtDefs[i].fields, cg->debugUdtDefs[i].fields,
                               cg->debugUdtDefs[i].fieldCount * sizeof(BasDebugFieldT));
                    }
                }

                // Keep count in sync with the pointer: on OOM the fields
                // array stays NULL, so leaving the copied count nonzero
                // would make consumers deref NULL.
                if (mod->debugUdtDefs[i].fields == NULL) {
                    mod->debugUdtDefs[i].fieldCount = 0;
                }
            }

            mod->debugUdtDefCount = cg->debugUdtDefCount;
        }
    }

    return mod;
}


BasModuleT *basCodeGenBuildModuleWithProcs(BasCodeGenT *cg, void *symtab) {
    BasModuleT *mod = basCodeGenBuildModule(cg);

    if (!mod || !symtab) {
        return mod;
    }

    BasSymTabT *tab = (BasSymTabT *)symtab;

    // Count SUB/FUNCTION entries
    int32_t procCount = 0;

    // Count globals that need runtime type init (currently: STRING).
    // This list survives stripping, unlike debugVars.
    int32_t globalInitCount = 0;

    for (int32_t i = 0; i < tab->count; i++) {
        BasSymbolT *s = tab->symbols[i];

        if (isEmittableProc(s)) {
            procCount++;
        }

        if (needsGlobalInit(s)) {
            globalInitCount++;
        }
    }

    if (globalInitCount > 0) {
        mod->globalInits = (BasGlobalInitT *)malloc(globalInitCount * sizeof(BasGlobalInitT));

        if (mod->globalInits) {
            int32_t gi = 0;

            for (int32_t i = 0; i < tab->count; i++) {
                BasSymbolT *s = tab->symbols[i];

                if (needsGlobalInit(s)) {
                    mod->globalInits[gi].index    = s->index;
                    mod->globalInits[gi].dataType = s->dataType;
                    gi++;
                }
            }

            mod->globalInitCount = gi;
        }
    }

    if (procCount == 0) {
        return mod;
    }

    mod->procs = (BasProcEntryT *)malloc(procCount * sizeof(BasProcEntryT));

    if (!mod->procs) {
        return mod;
    }

    int32_t idx = 0;

    for (int32_t i = 0; i < tab->count; i++) {
        BasSymbolT *s = tab->symbols[i];

        if (isEmittableProc(s)) {
            BasProcEntryT *p = &mod->procs[idx++];
            strncpy(p->name, s->name, BAS_MAX_IDENT - 1);
            p->name[BAS_MAX_IDENT - 1] = '\0';
            strncpy(p->formName, s->formName, BAS_MAX_IDENT - 1);
            p->formName[BAS_MAX_IDENT - 1] = '\0';
            p->codeAddr   = s->codeAddr;
            p->paramCount = s->paramCount;
            p->localCount = s->localCount;
            p->returnType = s->dataType;
            p->isFunction = (s->kind == SYM_FUNCTION);
        }
    }

    // Debug local variables carry a procIndex assigned in source-definition
    // (code emission) order, while the symbol table lists procs in
    // first-mention order (DECLARE lines included).  Sort the table by code
    // address so its indices match those procIndex values and the debugger
    // attributes locals to the right procedure.  Insertion sort: the table
    // is small and usually already in order.
    for (int32_t i = 1; i < idx; i++) {
        BasProcEntryT key = mod->procs[i];
        int32_t       j   = i - 1;

        while (j >= 0 && mod->procs[j].codeAddr > key.codeAddr) {
            mod->procs[j + 1] = mod->procs[j];
            j--;
        }

        mod->procs[j + 1] = key;
    }

    mod->procCount = idx;
    return mod;
}


void basCodeGenFree(BasCodeGenT *cg) {
    for (int32_t i = 0; i < cg->constCount; i++) {
        basStringUnref(cg->constants[i]);
    }

    for (int32_t i = 0; i < cg->dataCount; i++) {
        basValRelease(&cg->dataPool[i]);
    }

    arrfree(cg->code);
    arrfree(cg->constants);
    arrfree(cg->constHashes);
    arrfree(cg->dataPool);
    arrfree(cg->formVarInfo);
    arrfree(cg->debugVars);

    for (int32_t i = 0; i < cg->debugUdtDefCount; i++) {
        free(cg->debugUdtDefs[i].fields);
    }

    arrfree(cg->debugUdtDefs);

    // Leave the generator in the same state as basCodeGenInit so a reuse
    // after Free starts clean (no stale counts or overflow flag).
    basCodeGenInit(cg);
}


void basCodeGenInit(BasCodeGenT *cg) {
    memset(cg, 0, sizeof(*cg));
}


int32_t basCodePos(const BasCodeGenT *cg) {
    return cg->codeLen;
}


void basEmit16(BasCodeGenT *cg, int16_t v) {
    basEmitBytes(cg, &v, (int32_t)sizeof(v));
}


void basEmit8(BasCodeGenT *cg, uint8_t b) {
    arrput(cg->code, b);
    cg->codeLen = (int32_t)arrlen(cg->code);
}


static void basEmitBytes(BasCodeGenT *cg, const void *data, int32_t len) {
    const uint8_t *p = (const uint8_t *)data;

    for (int32_t i = 0; i < len; i++) {
        arrput(cg->code, p[i]);
    }

    cg->codeLen = (int32_t)arrlen(cg->code);
}


void basEmitDouble(BasCodeGenT *cg, double v) {
    basEmitBytes(cg, &v, (int32_t)sizeof(v));
}


void basEmitFloat(BasCodeGenT *cg, float v) {
    basEmitBytes(cg, &v, (int32_t)sizeof(v));
}


void basEmitU16(BasCodeGenT *cg, uint16_t v) {
    basEmitBytes(cg, &v, (int32_t)sizeof(v));
}


const BasProcEntryT *basModuleFindProc(const BasModuleT *mod, const char *name) {
    if (!mod || !mod->procs || !name) {
        return NULL;
    }

    for (int32_t i = 0; i < mod->procCount; i++) {
        if (strcasecmp(mod->procs[i].name, name) == 0) {
            return &mod->procs[i];
        }
    }

    return NULL;
}


void basPatch16(BasCodeGenT *cg, int32_t pos, int16_t val) {
    if (pos >= 0 && pos + 2 <= cg->codeLen) {
        memcpy(&cg->code[pos], &val, 2);
    }
}


// FNV-1a over the constant's bytes (case-sensitive: constants are exact).
static uint32_t constantHash(const char *text, int32_t len) {
    uint32_t h = BAS_FNV1A_OFFSET;

    for (int32_t i = 0; i < len; i++) {
        h ^= (uint32_t)(uint8_t)text[i];
        h *= BAS_FNV1A_PRIME;
    }

    return h;
}


// A SUB/FUNCTION that has a body in this module (not a forward stub or
// a DECLARE LIBRARY extern) and therefore gets a proc-table entry.
static bool isEmittableProc(const BasSymbolT *s) {
    return (s->kind == SYM_SUB || s->kind == SYM_FUNCTION) && s->isDefined && !s->isExtern;
}


// Global scalars that must start as a typed value rather than numeric 0
// (currently STRING); recorded in the module's globalInits table.
static bool needsGlobalInit(const BasSymbolT *s) {
    return s->scope == SCOPE_GLOBAL && s->kind == SYM_VARIABLE && s->dataType == BAS_TYPE_STRING && !s->isArray;
}
