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

#include "../animation/xanim_private.h"
#include "../core_runtime/core_runtime_private.h"
#include "coduo_engine_structs.h"
#include "script_compile_private.h"
#include "script_memory_private.h"
#include "script_runtime_private.h"
#include "script_variable_private.h"

enum {
    SCRIPT_ERROR_PARAMETER_OBJECT = -1,
    SCRIPT_TIME_MASK = 0x00ffffff
};

/* NOT_FROM_ORIGINAL_SOURCE: typed parameter stack indexing helper. */
static VariableValue *ScriptImport_ParamValue(uint32_t index)
{
    return &script_valueStackTop[-(int32_t)index];
}

/* NOT_FROM_ORIGINAL_SOURCE: shared source helper for the repeated import guard. */
static qboolean ScriptImport_ParamExists(uint32_t index)
{
    if (index < script_parameterCount) {
        return qtrue;
    }

    Scr_Error(va("parameter %d does not exist", index + 1));
    return qfalse;
}

/* NOT_FROM_ORIGINAL_SOURCE: raises the same parameter conversion failure path. */
static void ScriptImport_RaiseBadParam(uint32_t index)
{
    script_errorParameterIndex = (int32_t)index + 1;
    ScriptRuntime_RaiseError();
}

/* NOT_FROM_ORIGINAL_SOURCE: makes raw float payload stores explicit. */
static coduo_script_value_payload_t ScriptImport_FloatPayload(float value)
{
    coduo_script_value_payload_t payload = 0;

    memcpy(&payload, &value, sizeof(value));
    return payload;
}

/* NOT_FROM_ORIGINAL_SOURCE: makes raw float payload loads explicit. */
static float ScriptImport_FloatValue(coduo_script_value_payload_t payload)
{
    float value;

    memcpy(&value, &payload, sizeof(value));
    return value;
}

/* NOT_FROM_ORIGINAL_SOURCE: copies the packed anim payload into named fields. */
static scr_anim_t
ScriptImport_AnimRef(coduo_script_value_payload_t payload)
{
    scr_anim_t animRef;

    memcpy(&animRef, &payload, sizeof(animRef));
    return animRef;
}

/* NOT_FROM_ORIGINAL_SOURCE: local anim tree table accessor. */
static XAnim *ScriptImport_AnimTreeAt(
    int32_t slot,
    uint32_t treeIndex)
{
    return script_animTrees[slot][treeIndex];
}

/* NOT_FROM_ORIGINAL_SOURCE: shared bad-parameter reset used by anim imports. */
static void ScriptImport_ResetBadParamValue(uint32_t index)
{
    VariableValue *value = ScriptImport_ParamValue(index);

    ScriptValue_ReleaseValue(value);
    value->type = CODUO_SCRIPT_VAR_UNDEFINED;
    script_errorParameterIndex =
        (int32_t)index + 1;
    ScriptRuntime_RaiseError();
}

qboolean Scr_IsSystemActive(uint8_t unused)
{
    (void)unused;
    return script_timeArrayHandle != 0 ? qtrue : qfalse;
}

qboolean Scr_GetBool(uint32_t index)
{
    if (ScriptImport_ParamExists(index) == qfalse) {
        return qfalse;
    }

    VariableValue *value = ScriptImport_ParamValue(index);
    if (value->type == CODUO_SCRIPT_VAR_INT) {
        return value->payload != 0 ? qtrue : qfalse;
    }

    if (CastBool(value) != qfalse) {
        return value->payload != 0 ? qtrue : qfalse;
    }

    ScriptImport_RaiseBadParam(index);
    Scr_Error(va("parameter %d does not exist", index + 1));
    return qfalse;
}

int32_t Scr_GetInt(uint32_t index)
{
    if (ScriptImport_ParamExists(index) == qfalse) {
        return 0;
    }

    VariableValue *value = ScriptImport_ParamValue(index);
    if (CastInt(value) != qfalse) {
        return (int32_t)value->payload;
    }

    ScriptImport_RaiseBadParam(index);
    Scr_Error(va("parameter %d does not exist", index + 1));
    return 0;
}

float Scr_GetFloat(uint32_t index)
{
    if (ScriptImport_ParamExists(index) == qfalse) {
        return 0.0f;
    }

    VariableValue *value = ScriptImport_ParamValue(index);
    if (CastFloat(value) != qfalse) {
        return ScriptImport_FloatValue(value->payload);
    }

    ScriptImport_RaiseBadParam(index);
    Scr_Error(va("parameter %d does not exist", index + 1));
    return 0.0f;
}

uint16_t Scr_GetConstString(uint32_t index)
{
    if (ScriptImport_ParamExists(index) == qfalse) {
        return 0;
    }

    VariableValue *value = ScriptImport_ParamValue(index);
    if (CastString(value) != qfalse) {
        return (uint16_t)value->payload;
    }

    ScriptImport_RaiseBadParam(index);
    Scr_Error(va("parameter %d does not exist", index + 1));
    return 0;
}

const char *Scr_GetString(uint32_t index)
{
    return SL_ConvertToString(Scr_GetConstString(index));
}

const char *Scr_GetDebugString(uint32_t index)
{
    if (ScriptImport_ParamExists(index) == qfalse) {
        return NULL;
    }

    VariableValue *value = ScriptImport_ParamValue(index);
    coduo_script_variable_type_t originalType = value->type;
    coduo_script_variable_type_t debugType = originalType;

    if (originalType == CODUO_SCRIPT_VAR_OBJECT) {
        debugType = GetVarType(
            (uint16_t)value->payload);
    }

    const char *debugString = script_variableTypeNames[debugType];

    if (CastString(value) != qfalse) {
        return SL_ConvertToString(
            (uint16_t)value->payload);
    }

    script_errorMessage = NULL;
    script_errorSource = NULL;
    script_errorParameterIndex = 0;

    if (originalType == CODUO_SCRIPT_VAR_ANIMATION) {
        scr_anim_t animRef = ScriptImport_AnimRef(value->payload);
        XAnim *animTree = Scr_GetAnims(animRef.treeIndex);

        debugString = XAnimGetAnimName(animTree, animRef.animIndex);
    } else if (originalType == CODUO_SCRIPT_VAR_LOCALIZED_STRING) {
        /* ORIGINAL_BINARY_BUG: CastString has already released this
         * localized-string handle.  When it owned the last reference, stock
         * returns a pointer into freed string storage.  See original-bugs/
         * coduomp-scr-getdebugstring-istring-dangling.md. */
        debugString = SL_ConvertToString(
            (uint16_t)value->payload);
    }

    return debugString;
}

uint16_t Scr_GetConstIString(uint32_t index)
{
    if (ScriptImport_ParamExists(index) == qfalse) {
        return 0;
    }

    VariableValue *value = ScriptImport_ParamValue(index);
    if (CastIString(value) != qfalse) {
        return (uint16_t)value->payload;
    }

    ScriptImport_RaiseBadParam(index);
    Scr_Error(va("parameter %d does not exist", index + 1));
    return 0;
}

const char *Scr_GetIString(uint32_t index)
{
    return SL_ConvertToString(Scr_GetConstIString(index));
}

void Scr_GetVector(uint32_t index, vec3_t vector)
{
    if (ScriptImport_ParamExists(index) == qfalse) {
        return;
    }

    VariableValue *value = ScriptImport_ParamValue(index);
    if (CastVector(value) == qfalse) {
        ScriptImport_RaiseBadParam(index);
        Scr_Error(va("parameter %d does not exist", index + 1));
        return;
    }

    const float *source = (const float *)value->payload;
    memcpy(vector, source, sizeof(vec3_t));
}

uint32_t Scr_GetFunc(uint32_t index)
{
    if (ScriptImport_ParamExists(index) == qfalse) {
        return 0;
    }

    VariableValue *value = ScriptImport_ParamValue(index);
    if (value->type == CODUO_SCRIPT_VAR_FUNCTION) {
        return value->payload - (coduo_script_value_payload_t)script_codeBase;
    }

    script_errorMessage = "not a function";
    ScriptImport_RaiseBadParam(index);
    Scr_Error(va("parameter %d does not exist", index + 1));
    return 0;
}

scr_anim_t Scr_GetAnim(uint32_t index, XAnimTree *runtimeTree)
{
    if (ScriptImport_ParamExists(index) == qfalse) {
        return (scr_anim_t){0, 0};
    }

    VariableValue *value = ScriptImport_ParamValue(index);
    if (value->type == CODUO_SCRIPT_VAR_ANIMATION) {
        scr_anim_t out = ScriptImport_AnimRef(value->payload);

        if (runtimeTree == NULL) {
            return out;
        }

        XAnim *animTree =
            Scr_GetAnims(out.treeIndex);
        XAnim *entityTree =
            XAnimRuntimeTreeSourceTree(runtimeTree);

        if (animTree == entityTree) {
            return out;
        }

        const char *entityTreeName = XAnimGetAnimTreeDebugName(
            XAnimRuntimeTreeSourceTree(runtimeTree));
        const char *animTreeName =
            XAnimGetAnimTreeDebugName(animTree);
        const char *animName = XAnimGetAnimName(animTree, out.animIndex);

        script_errorMessage =
            va("anim '%s' in animtree '%s' does not belong to the entity's animtree '%s'",
               animName, animTreeName, entityTreeName);
    } else {
        script_errorMessage =
            va("cannot cast %s to anim", script_variableTypeNames[value->type]);
    }

    ScriptImport_ResetBadParamValue(index);
    Scr_Error(va("parameter %d does not exist", index + 1));
    return (scr_anim_t){0, 0};
}

coduo_script_anim_tree_ref_t Scr_GetAnimTree(uint32_t index)
{
    int32_t slot = xanim_activePoolPayloadSlot;

    if (ScriptImport_ParamExists(index) == qfalse) {
        return (coduo_script_anim_tree_ref_t){
            ScriptImport_AnimTreeAt(slot, 0)};
    }

    VariableValue *value = ScriptImport_ParamValue(index);
    if (value->type == CODUO_SCRIPT_VAR_INT) {
        uint32_t treeIndex = value->payload;

        if (treeIndex <= (uint32_t)script_animTreeCounts[slot] &&
            ScriptImport_AnimTreeAt(slot, treeIndex) != NULL) {
            return (coduo_script_anim_tree_ref_t){
                ScriptImport_AnimTreeAt(slot, treeIndex)};
        }

        script_errorMessage = "bad anim tree";
    } else {
        script_errorMessage = va("cannot cast %s to animtree",
                                 script_variableTypeNames[value->type]);
    }

    ScriptImport_ResetBadParamValue(index);
    Scr_Error(va("parameter %d does not exist", index + 1));
    return (coduo_script_anim_tree_ref_t){ScriptImport_AnimTreeAt(slot, 0)};
}

uint32_t
Scr_GetEntityNum(uint32_t index, int32_t *classNum)
{
    if (ScriptImport_ParamExists(index) == qfalse) {
        return 0;
    }

    VariableValue *value = ScriptImport_ParamValue(index);
    if (CastPointer(value) == qfalse) {
        ScriptImport_RaiseBadParam(index);
        Scr_Error(va("parameter %d does not exist", index + 1));
        return 0;
    }

    uint16_t object =
        (uint16_t)value->payload;
    if (GetVarType(object) == CODUO_SCRIPT_VAR_ENTITY) {
        *classNum = (int32_t)GetEntityType(object);
        uint32_t entityNum = GetEntnum(object);
        return entityNum;
    }

    script_errorMessage = va("cannot cast %s to entity",
                             script_variableTypeNames[
                                 GetVarType(object)]);
    ScriptImport_RaiseBadParam(index);
    Scr_Error(va("parameter %d does not exist", index + 1));
    return 0;
}

coduo_script_variable_type_t Scr_GetType(uint32_t index)
{
    if (ScriptImport_ParamExists(index) == qfalse) {
        return CODUO_SCRIPT_VAR_UNDEFINED;
    }

    return ScriptImport_ParamValue(index)->type;
}

coduo_script_variable_type_t Scr_GetPointerType(uint32_t index)
{
    if (index < script_parameterCount) {
        VariableValue *value = ScriptImport_ParamValue(index);
        if (value->type == CODUO_SCRIPT_VAR_OBJECT) {
            return GetVarType(
                (uint16_t)value->payload);
        }

        Scr_Error(va("parameter %d is not a pointer", index + 1));
    }

    Scr_Error(va("parameter %d does not exist", index + 1));
    return CODUO_SCRIPT_VAR_UNDEFINED;
}

uint32_t Scr_GetNumParam(void)
{
    return script_parameterCount;
}

void Scr_AddBool(qboolean value)
{
    IncInParam();
    script_valueStackTop->type = CODUO_SCRIPT_VAR_INT;
    script_valueStackTop->payload = SCRIPT_VALUE_INT_PAYLOAD(value);
}

void Scr_AddInt(int32_t value)
{
    IncInParam();
    script_valueStackTop->type = CODUO_SCRIPT_VAR_INT;
    script_valueStackTop->payload = SCRIPT_VALUE_INT_PAYLOAD(value);
}

void Scr_AddFloat(float value)
{
    IncInParam();
    script_valueStackTop->type = CODUO_SCRIPT_VAR_FLOAT;
    script_valueStackTop->payload = ScriptImport_FloatPayload(value);
}

void Scr_AddAnim(uint32_t value)
{
    IncInParam();
    script_valueStackTop->type = CODUO_SCRIPT_VAR_ANIMATION;
    script_valueStackTop->payload = value;
}

void Scr_AddUndefined(void)
{
    IncInParam();
    script_valueStackTop->type = CODUO_SCRIPT_VAR_UNDEFINED;
}

void Scr_AddObject(uint16_t object)
{
    IncInParam();
    script_valueStackTop->type = CODUO_SCRIPT_VAR_OBJECT;
    script_valueStackTop->payload = object;
    AddRefToObject(object);
}

void Scr_AddEntityNum(int32_t entityNum, int32_t classNum)
{
    Scr_AddObject(Scr_GetEntityId(entityNum, classNum));
}

void Scr_AddStruct(void)
{
    uint16_t object = AllocObject();

    Scr_AddObject(object);
    RemoveRefToObject(object);
}

void Scr_AddString(const char *value)
{
    IncInParam();
    script_valueStackTop->type = CODUO_SCRIPT_VAR_STRING;
    script_valueStackTop->payload = SL_GetString(value, qfalse);
}

void Scr_AddIString(const char *value)
{
    IncInParam();
    script_valueStackTop->type = CODUO_SCRIPT_VAR_LOCALIZED_STRING;
    script_valueStackTop->payload = SL_GetString(value, qfalse);
}

void Scr_AddConstString(uint16_t value)
{
    IncInParam();
    script_valueStackTop->type = CODUO_SCRIPT_VAR_STRING;
    script_valueStackTop->payload = value;
    SL_AddRefToString(value);
}

void Scr_AddVector(const vec3_t vector)
{
    IncInParam();
    script_valueStackTop->type = CODUO_SCRIPT_VAR_VECTOR;
    script_valueStackTop->payload = AllocVectorCopy(vector);
}

void Scr_MakeArray(void)
{
    IncInParam();
    script_valueStackTop->type = CODUO_SCRIPT_VAR_OBJECT;
    script_valueStackTop->payload = Scr_AllocArray();
}

void Scr_AddArray(void)
{
    script_valueStackTop--;
    script_valueStackDepth--;

    uint16_t array =
        (uint16_t)script_valueStackTop->payload;
    uint16_t child =
        GetArrayVariableBySignedIndex(
            array,
            (int32_t)GetArraySize(array));

    SetNewVariableValue(child, &script_valueStackTop[1]);
}

void Scr_AddArrayStringIndexed(uint16_t name)
{
    script_valueStackTop--;
    script_valueStackDepth--;

    uint16_t array =
        (uint16_t)script_valueStackTop->payload;
    uint16_t child = GetVariable(array, name);

    SetNewVariableValue(child, &script_valueStackTop[1]);
}

void Scr_Error(const char *message)
{
    script_errorMessage = message;
    ScriptRuntime_RaiseError();
}

void Scr_ErrorWithDialogMessage(const char *message,
                                const char *source)
{
    script_errorMessage = message;
    script_errorSource = source;
    ScriptRuntime_RaiseError();
}

void Scr_TerminalError(const char *message)
{
    Scr_DumpScriptThreads();
    Scr_DumpScriptVariables();
    script_forceErrorReport = qtrue;
    Scr_Error(message);
}

void Scr_ParamError(int32_t index, const char *message)
{
    script_errorParameterIndex = index + 1;
    Scr_Error(message);
}

void Scr_ObjectError(const char *message)
{
    script_errorParameterIndex = SCRIPT_ERROR_PARAMETER_OBJECT;
    Scr_Error(message);
}

void Scr_SetTime(uint32_t time)
{
    time &= SCRIPT_TIME_MASK;

    if ((int32_t)(time - script_currentTimeKey) <= 0) {
        script_currentTimeKey = time;
        return;
    }

    do {
        VM_SetTime();
        script_currentTimeKey++;
        script_currentTimeKey &= SCRIPT_TIME_MASK;
    } while (script_currentTimeKey != time);
}

void Scr_RunCurrentThreads(void)
{
    VM_SetTime();
}

void Scr_ResetTimeout(void)
{
    script_loopWatchdogTick = (uint32_t)rdtsc();
}
