#include <stdint.h>

#include "../core_runtime/core_runtime_private.h"
#include "coduo_engine_structs.h"
#include "script_compile_private.h"
#include "script_notify_private.h"
#include "script_runtime_private.h"
#include "script_source_positions_private.h"
#include "script_variable_private.h"
#include "script_vm_private.h"

enum {
    SCRIPT_COM_ERROR_DROP = 1,
    SCRIPT_NOTIFY_STACK_KEY = 0x20001,
    SCRIPT_THREAD_MAX_CALL_DEPTH = CODUO_SCRIPT_CALL_STACK_COUNT
};

/* NOT_FROM_ORIGINAL_SOURCE: shared guard for import callbacks entering script code. */
static void ScriptRuntime_ResetWatchdogIfRootCall(void)
{
    if (script_callStackDepth == 0) {
        Scr_ResetTimeout();
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: shared cleanup for import callbacks consuming params. */
static void ScriptRuntime_ClearActiveParams(void)
{
    while (script_parameterCount != 0) {
        ScriptValue_ReleaseValue(script_valueStackTop);
        script_valueStackTop--;
        script_parameterCount--;
    }
}

void IncInParam(void)
{
    ScriptRuntime_ClearActiveParams();

    script_valueStackTop++;
    script_valueStackDepth++;
    if (script_valueStackLimit < script_valueStackTop) {
        Com_Error(SCRIPT_COM_ERROR_DROP, "Internal script stack overflow");
    }
}

uint16_t
VM_ExecuteThread(uint16_t parent,
                 uint8_t *codePos,
                 uint32_t paramCount)
{
    ScriptRuntime_ClearActiveParams();

    VariableValue *stackBase = &script_valueStackTop[-(int32_t)paramCount];
    uint32_t savedDepth =
        script_valueStackDepth - paramCount;

    AddRefToObject(parent);
    uint16_t thread =
        AllocThread(parent);

    if (script_callStackDepth < SCRIPT_THREAD_MAX_CALL_DEPTH) {
        script_callStackCodepos[script_callStackDepth] = codePos;
        script_callStackDepth++;

        coduo_script_variable_type_t savedType = stackBase->type;
        stackBase->type = CODUO_SCRIPT_VAR_CODEPOS;
        script_valueStackDepth = 0;

        thread = VM_Execute(script_valueStackTop, codePos, thread, thread,
                            stackBase);

        stackBase->type = savedType;
        script_valueStackTop = stackBase + 1;
        script_valueStackDepth = savedDepth + 1;
        ClearVariableValue(script_tempValueHandle);
        return thread;
    }

    KillThread(thread);
    script_valueStackDepth = savedDepth + 1;
    uint32_t valuesToRelease = savedDepth;
    while (valuesToRelease != 0) {
        ScriptValue_ReleaseValue(script_valueStackTop);
        script_valueStackTop--;
        valuesToRelease--;
    }

    VariableValue *returnSlot = script_valueStackTop + 1;
    script_valueStackTop = returnSlot;
    returnSlot->type = CODUO_SCRIPT_VAR_UNDEFINED;
    RuntimeError(
        codePos, 0,
        "script stack overflow (too many embedded function calls)", NULL);
    return thread;
}

uint16_t
Scr_ExecThread(uint32_t codeOffset,
                        uint32_t paramCount)
{
    ScriptRuntime_ResetWatchdogIfRootCall();

    uint8_t *codePos = script_codeBase + codeOffset;
    Scr_IsInOpcodeMemory(codePos);

    uint16_t thread =
        VM_ExecuteThread(script_levelHandle, codePos, paramCount);

    ScriptValue_ReleaseValue(script_valueStackTop);
    script_valueStackTop->type = CODUO_SCRIPT_VAR_UNDEFINED;
    script_valueStackTop--;
    script_valueStackDepth--;
    return thread;
}

uint16_t
Scr_ExecEntThreadNum(int32_t entityNum, int32_t classNum,
                              uint32_t codeOffset,
                              uint32_t paramCount)
{
    ScriptRuntime_ResetWatchdogIfRootCall();

    uint8_t *codePos = script_codeBase + codeOffset;
    Scr_IsInOpcodeMemory(codePos);

    uint16_t object =
        Scr_GetEntityId(entityNum, classNum);
    uint16_t thread =
        VM_ExecuteThread(object, codePos, paramCount);

    ScriptValue_ReleaseValue(script_valueStackTop);
    script_valueStackTop->type = CODUO_SCRIPT_VAR_UNDEFINED;
    script_valueStackTop--;
    script_valueStackDepth--;
    return thread;
}

void Scr_AddExecThread(uint32_t codeOffset,
                       uint32_t paramCount)
{
    ScriptRuntime_ResetWatchdogIfRootCall();

    uint8_t *codePos = script_codeBase + codeOffset;
    Scr_IsInOpcodeMemory(codePos);

    uint16_t thread =
        VM_ExecuteThread(script_levelHandle, codePos, paramCount);
    RemoveRefToObject(thread);
}

void Scr_AddExecEntThreadNum(int32_t entityNum, int32_t classNum,
                             uint32_t codeOffset,
                             uint32_t paramCount)
{
    ScriptRuntime_ResetWatchdogIfRootCall();

    uint8_t *codePos = script_codeBase + codeOffset;
    Scr_IsInOpcodeMemory(codePos);

    uint16_t object =
        Scr_GetEntityId(entityNum, classNum);
    uint16_t thread =
        VM_ExecuteThread(object, codePos, paramCount);
    RemoveRefToObject(thread);
}

void Scr_FreeThread(uint16_t thread)
{
    RemoveRefToObject(thread);
}

void VM_SetTime(void)
{
    if (script_timeArrayHandle == 0) {
        return;
    }

    uint16_t timeSlot =
        FindVariable(script_timeArrayHandle,
                                 script_currentTimeKey);
    if (timeSlot == 0) {
        return;
    }

    uint16_t threadList =
        FindObject(timeSlot);
    VM_Resume(threadList);
    SafeRemoveVariable(script_timeArrayHandle,
                                       script_currentTimeKey);
}
