#include <stdint.h>
#include <stddef.h>
#include <string.h>
#include <ctype.h>

#include "../animation/xanim_private.h"
#include "../core_memory/core_memory_private.h"
#include "coduo_ctype_compat.h"
#include "coduo_engine_structs.h"
#include "script_compile_private.h"
#include "script_memory_private.h"
#include "script_source_positions_private.h"
#include "script_variable_private.h"

enum {
    SCRIPT_LOAD_SCRIPT_PATH_BUFFER_SIZE = 64,
    SCRIPT_LOAD_SCRIPT_NAME_COPY_LIMIT = 0x40,
    SCRIPT_STRING_USAGE_RUNTIME = 1,
    SCRIPT_STRING_USAGE_CANONICAL = 2,
    SCRIPT_PARSE_LOCAL_BUFFER_SIZE = 0x4010,
    SCRIPT_PARSE_FLEX_BUFFER_SIZE = 0x4000,
    SCRIPT_PARSE_INITIAL_YY_START_STATE = 3
};

qboolean Scr_IsIdentifier(const char *text)
{
    while (*text != '\0') {
        int32_t ch = (int32_t)(signed char)*text;

        if (isalnum(coduo_ctype_signed_byte_arg(ch)) == 0 && ch != '_') {
            return qfalse;
        }

        text++;
    }

    return qtrue;
}

uint32_t Scr_GetFunctionHandle(const char *scriptName,
                                        const char *functionName)
{
    uint16_t canonicalScript =
        Scr_CreateCanonicalFilename(scriptName);
    uint16_t scriptHandle =
        FindVariable(script_loadScriptHandleRoot, canonicalScript);
    SL_RemoveRefToString(canonicalScript);

    if (scriptHandle == 0) {
        return 0;
    }

    uint16_t functionRoot =
        FindObject(scriptHandle);
    uint16_t functionNameHandle =
        SL_FindLowercaseString(functionName);
    if (functionNameHandle == 0) {
        return 0;
    }

    uint16_t functionHandle =
        FindVariable(functionRoot, functionNameHandle);
    if (functionHandle == 0) {
        return 0;
    }

    VariableValue *value = GetVariableValueAddress(functionHandle);
    coduo_script_codepos_t codePos = (coduo_script_codepos_t)value->payload;
    if (Scr_IsInOpcodeMemory(codePos) == qfalse) {
        return 0;
    }

    return (uint32_t)(codePos - script_codeBase);
}

void Scr_BeginLoadScripts(void)
{
    script_loadScriptsActive = qtrue;
    InitOpcodeLookup();
    Scr_InitDeveloperOpcodes();
    script_loadScriptHandleRoot = Scr_AllocArray();
    script_loadScriptCodeRoot = Scr_AllocArray();
    script_codeBase = Hunk_AllocLow(0);
    script_codeSize = 0;
    script_codeEnd = NULL;
    SL_BeginLoadScripts();
    script_parseErrorCount = 0;
    Scr_BeginLoadAnimTrees();
}

void Scr_BeginLoadAnimTrees(void)
{
    int32_t slot = xanim_activePoolPayloadSlot;

#if UINTPTR_MAX > UINT32_MAX
    coduo_engine_script_anim_release_unresolved_ref_sidecars();
#endif
    script_loadAnimTreesActive = qtrue;
    script_animTreeCounts[slot] = 0;
    script_animTrees[slot][0] = NULL;
    script_animTreeRoot = Scr_AllocArray();
    script_animCurrentUsingTree = 0;
}

void ScriptImport_ParseSource(char *source, coduo_scr_ast_node_t **out)
{
    char localBuffer[SCRIPT_PARSE_LOCAL_BUFFER_SIZE];
    coduo_script_yy_buffer_t buffer;

    script_yyInputCursor = source;
    script_yyCurrentSourcePos = 0;
    script_yyPreviousSourcePos = 0;
    script_yyInit = qtrue;

    buffer.bufSize = SCRIPT_PARSE_FLEX_BUFFER_SIZE;
    buffer.chBuf = localBuffer;
    buffer.isOurBuffer = qfalse;
    yy_init_buffer(&buffer, NULL);

    script_yyCurrentBuffer = &buffer;
    script_yyStart = 0;
    script_yyStart = SCRIPT_PARSE_INITIAL_YY_START_STATE;
    yyparse();
    *out = script_parseRoot;
}

qboolean Scr_LoadScript(const char *filename)
{
    char scriptPath[SCRIPT_LOAD_SCRIPT_PATH_BUFFER_SIZE];
    coduo_scr_ast_node_t *scriptNode;
    uint16_t canonicalFilename;
    uint16_t scriptHandle;

    canonicalFilename = Scr_CreateCanonicalFilename(filename);
    scriptHandle =
        FindVariable(script_loadScriptCodeRoot, canonicalFilename);
    if (scriptHandle != 0) {
        SL_RemoveRefToString(canonicalFilename);
        return FindVariable(script_loadScriptHandleRoot,
                                        canonicalFilename) != 0
                   ? qtrue
                   : qfalse;
    }

    GetVariable(script_loadScriptCodeRoot, canonicalFilename);
    SL_RemoveRefToString(canonicalFilename);
    TempMemoryReset();

    /* ORIGINAL_BINARY_BUG: a 64-byte-or-longer canonical name leaves this
     * 64-byte destination unterminated before strncat scans and appends. */
    strncpy(scriptPath, SL_ConvertToString(canonicalFilename),
            SCRIPT_LOAD_SCRIPT_NAME_COPY_LIMIT);
    /* Keep the original 64-byte source limit in a local so GCC does not
     * diagnose the deliberately preserved stock overflow under -Werror. */
    size_t appendLimit = SCRIPT_LOAD_SCRIPT_NAME_COPY_LIMIT;
    strncat(scriptPath, ".gsc", appendLimit);

    const char *savedSourcePos = script_sourcePos;
    char *source = Scr_AddSourceBuffer(
        scriptPath, Hunk_AllocLow(0), script_codeRelocationEnd);
    const char *savedSourceFilename = script_sourceFilename;
    if (source == NULL) {
        script_sourceFilename = savedSourceFilename;
        return qfalse;
    }

    script_sourceBufferOffset = 0;
    script_pendingScriptLoadCount = 0;
    script_sourceFilename = scriptPath;
    ScriptImport_ParseSource(source, &scriptNode);

    uint16_t functionRoot =
        GetObject(GetVariable(
            script_loadScriptHandleRoot, canonicalFilename));
    ScriptCompile(scriptNode, functionRoot);

    script_sourceFilename = savedSourceFilename;
    script_sourcePos = savedSourcePos;
    return qtrue;
}

void Scr_EndLoadScripts(void)
{
    script_loadScriptsActive = qfalse;
    SL_EndLoadScripts();
    ClearObject(script_loadScriptCodeRoot);
    RemoveRefToObject(script_loadScriptCodeRoot);
    script_loadScriptCodeRoot = 0;
    ClearObject(script_loadScriptHandleRoot);
    RemoveRefToObject(script_loadScriptHandleRoot);
    script_loadScriptHandleRoot = 0;
    Scr_InsertDeveloperOpcodes();
    SL_ShutdownSystem(SCRIPT_STRING_USAGE_CANONICAL);
}

void Scr_PrecacheAnimTrees(
    coduo_script_anim_tree_alloc_fn allocCallback)
{
    for (int32_t treeIndex = 1;
         treeIndex <= script_animTreeCounts[xanim_activePoolPayloadSlot];
         ++treeIndex) {
        Scr_LoadAnimTreeAtIndex(treeIndex, allocCallback);
    }
}

void Scr_EndLoadAnimTrees(void)
{
    script_loadAnimTreesActive = qfalse;
#if UINTPTR_MAX > UINT32_MAX
    coduo_engine_script_anim_release_unresolved_ref_sidecars();
#endif
    ClearObject(script_animTreeRoot);
    RemoveRefToObject(script_animTreeRoot);
    script_animTreeRoot = 0;

    if (script_animCurrentUsingTree != 0) {
        RemoveRefToObject(script_animCurrentUsingTree);
    }

    SL_ShutdownSystem(SCRIPT_STRING_USAGE_CANONICAL);
    script_codeEnd = Hunk_AllocLow(0);
}

void Scr_FreeScripts(uint8_t unused)
{
    (void)unused;

    if (script_loadScriptsActive != qfalse) {
        Scr_EndLoadScripts();
    }

    if (script_loadAnimTreesActive != qfalse) {
        Scr_EndLoadAnimTrees();
    }

    script_codeBase = NULL;
    script_codeSize = 0;
    script_codeEnd = NULL;
    script_codeChecksum = 0;
    script_loadScriptsActive = qfalse;
    SL_ShutdownSystem(SCRIPT_STRING_USAGE_RUNTIME);
    ShutdownOpcodeLookup();
    Scr_ShutdownDeveloperOpcodes();
}
