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

#include "coduo_x87emu.h" /* defines EMULATE_X87; x87 shim when it is 1 */
#include "coduo_native_x87.h"
#include "../core_math/core_math_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_COM_ERROR_DROP = 1,
    SCRIPT_RUNTIME_VARIABLE_TYPE_MASK = 0x1f,
    SCRIPT_RUNTIME_VARIABLE_NAME_SHIFT = 8,
    SCRIPT_STRING_INTERN_RUNTIME_TYPE = 0x0e
};

#define SCRIPT_VALUE_FLOAT_EQUAL_EPSILON 1.0e-6f

long double FUN_080b085c(float value);
void FUN_080b0892(void);
qboolean CastWeakerPair(VariableValue *left,
                        VariableValue *right);

/* NOT_FROM_ORIGINAL_SOURCE: script floats occupy the low 32 bits of payload. */
static coduo_script_value_payload_t ScriptValue_FloatPayload(float value)
{
    coduo_script_value_payload_t payload = 0;

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

/* NOT_FROM_ORIGINAL_SOURCE: script floats occupy the low 32 bits of payload. */
static float ScriptValue_PayloadFloat(coduo_script_value_payload_t payload)
{
    float value;

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

void ScriptValue_AddRefValue(VariableValue *value)
{
    AddRefToValue(value->type, value->payload);
}

void ScriptValue_ReleaseValue(VariableValue *value)
{
    RemoveRefToValue(value->type, value->payload);
}

void SetEmptyArray(uint16_t handle)
{
    script_variableNodes[handle].packedTypeIndex |= CODUO_SCRIPT_VAR_OBJECT;
    script_variableNodes[handle].payload.halves.valueOrRefCount = Scr_AllocArray();
}

qboolean ScriptRuntime_PruneGameVariableArray(
    uint16_t handle)
{
    if ((script_variableNodes[handle].packedTypeIndex &
         SCRIPT_RUNTIME_VARIABLE_TYPE_MASK) != CODUO_SCRIPT_VAR_ARRAY) {
        return qfalse;
    }

    for (;;) {
        uint16_t child = FindNextSibling(handle);

        for (;;) {
            if (child == 0) {
                return qtrue;
            }

            coduo_script_variable_node_t *childNode =
                &script_variableNodes[child];
            coduo_script_variable_type_t childType =
                (coduo_script_variable_type_t)(
                    childNode->packedTypeIndex &
                    SCRIPT_RUNTIME_VARIABLE_TYPE_MASK);
            uint32_t childName =
                childNode->packedTypeIndex >> SCRIPT_RUNTIME_VARIABLE_NAME_SHIFT;

            switch (childType) {
            case CODUO_SCRIPT_VAR_CODEPOS:
            case CODUO_SCRIPT_VAR_FUNCTION:
            case CODUO_SCRIPT_VAR_STACK:
            case CODUO_SCRIPT_VAR_ANIMATION:
                RemoveVariable(handle, childName);
                break;

            case CODUO_SCRIPT_VAR_OBJECT:
                if (ScriptRuntime_PruneGameVariableArray(
                        (uint16_t)
                            childNode->payload.halves.valueOrRefCount) == qfalse) {
                    RemoveVariable(handle, childName);
                    break;
                }

                child = FindNextSibling(child);
                continue;

            default:
                child = FindNextSibling(child);
                continue;
            }

            break;
        }
    }
}

void SetEntityFieldValue(uint32_t classnum, int32_t objectNum,
                         int32_t fieldIndex, VariableValue *value)
{
    script_parameterCount = 1;
    script_valueStackTop = value;
    Scr_SetObjectField(classnum, objectNum, fieldIndex);

    while (script_parameterCount != 0) {
        ScriptValue_ReleaseValue(script_valueStackTop);
        script_valueStackTop--;
        script_parameterCount--;
    }
}

void GetEntityFieldValue(uint32_t classnum, int32_t objectNum,
                         int32_t fieldIndex, VariableValue *value)
{
    script_valueStackTop = value - 1;
    value->type = CODUO_SCRIPT_VAR_UNDEFINED;
    Scr_GetObjectField(classnum, objectNum, fieldIndex);
    script_valueStackDepth = 0;
}

void ScriptRuntime_ResetValueRuntime(void)
{
    script_valueStackLimit =
        &script_valueStack[CODUO_SCRIPT_VALUE_STACK_COUNT - 1];
    script_valueStackTop = &script_valueStack[0];
    script_callStackDepth = 0;
    script_errorMessage = NULL;
    script_errorSource = NULL;
    script_errorParameterIndex = 0;
    script_forceErrorReport = qfalse;
    script_parameterCount = 0;
    script_valueStackDepth = 0;
    script_tempValueHandle = Scr_AllocArray();
    script_timeArrayHandle = 0;
    script_pauseArrayHandle = 0;
    script_levelHandle = 0;
    script_gameHandle = 0;
    script_animHandle = 0;
    script_valueStack[0].type = CODUO_SCRIPT_VAR_CODEPOS;
    script_loopWatchdogWarningFlag = 0;
}

void Scr_Init(
    int32_t debugReport,
    int32_t developerScript,
    int32_t developer)
{
    script_runtimeDebugReportFlag = debugReport;
    script_runtimeDeveloperScriptFlag = developerScript;
    script_runtimeDeveloperFlag = developer;
    Var_Init();
    ScriptRuntime_ResetValueRuntime();
    script_loadScriptsActive = qfalse;
    script_loadAnimTreesActive = qfalse;
    script_loadScriptHandleRoot = 0;
    script_loadScriptCodeRoot = 0;
    script_loadAnimTreeRoot = 0;
    script_runtimeActive = qtrue;
}

void Scr_AllocGameVariable(void)
{
    if (script_gameHandle == 0) {
        script_gameHandle = AllocValue();
        SetEmptyArray(script_gameHandle);
    }
}

void Scr_FreeGameVariable(qboolean shutdown)
{
    if (shutdown == qfalse) {
        ScriptRuntime_PruneGameVariableArray(
            script_variableNodes[script_gameHandle].payload.halves.valueOrRefCount);
        return;
    }

    FreeValue(script_gameHandle);
    script_gameHandle = 0;
}

void Scr_GetChecksum(uint32_t checksum[3])
{
    checksum[0] = script_sourceChecksum;
    checksum[1] = script_sourceBufferOffset;
    checksum[2] =
        (uint32_t)(script_sourceBufferEnd - script_sourceBufferStart);
}

/*
 * Checked 2026-06-29: recovered script-runtime leaf returns qfalse and has no
 * maintained callers; no source-level name proven.
 */
qboolean FUN_080aa918(void)
{
    return qfalse;
}

/* NOT_FROM_ORIGINAL_SOURCE: stock stores string handles into value payloads as u16. */
static void ScriptValue_StoreStringHandle(VariableValue *value,
                                          uint16_t stringHandle)
{
    uint16_t *payloadHandle = (uint16_t *)&value->payload;

    *payloadHandle = stringHandle;
}

qboolean ScriptRuntime_StringStartsWithZeroLiteral(const char *text)
{
    while (*text != '\0') {
        /* Stock 0x080aa929 uses a signed-byte comparison against space. */
        if ((int8_t)*text > ' ') {
            if (*text == '-' || *text == '+') {
                ++text;
            }

            if (*text == '0') {
                return qtrue;
            }

            return *text == '.' && text[1] == '0' ? qtrue : qfalse;
        }

        ++text;
    }

    return qfalse;
}

qboolean CastBool(VariableValue *value)
{
    if (value->type == CODUO_SCRIPT_VAR_FLOAT) {
        float floatValue = ScriptValue_PayloadFloat(value->payload);

        value->type = CODUO_SCRIPT_VAR_INT;
        value->payload = floatValue != 0.0f ? qtrue : qfalse;
        return qtrue;
    }

    if (value->type == CODUO_SCRIPT_VAR_STRING) {
        uint16_t stringHandle =
            (uint16_t)value->payload;
        const char *text = SL_ConvertToString(stringHandle);
        value->payload = atoi(text) != 0 ? qtrue : qfalse;

        if (value->payload == qfalse &&
            ScriptRuntime_StringStartsWithZeroLiteral(
                SL_ConvertToString(stringHandle)) == qfalse) {
            script_errorMessage =
                va("cannot cast \"%s\" to bool",
                   SL_ConvertToString(stringHandle));
            SL_RemoveRefToString(stringHandle);
            value->type = CODUO_SCRIPT_VAR_UNDEFINED;
            return qfalse;
        }

        value->type = CODUO_SCRIPT_VAR_INT;
        SL_RemoveRefToString(stringHandle);
        return qtrue;
    }

    script_errorMessage =
        va("cannot cast %s to bool", script_variableTypeNames[value->type]);
    ScriptValue_ReleaseValue(value);
    value->type = CODUO_SCRIPT_VAR_UNDEFINED;
    return qfalse;
}

qboolean CastInt(VariableValue *value)
{
    if (value->type == CODUO_SCRIPT_VAR_INT) {
        return qtrue;
    }

    if (value->type == CODUO_SCRIPT_VAR_FLOAT) {
        float floatValue = ScriptValue_PayloadFloat(value->payload);

        value->type = CODUO_SCRIPT_VAR_INT;
#if defined(__x86_64__)
        value->payload = (uint32_t)
            CODUO_X87_TRUNCATE_I32((long double)floatValue);
#else
        value->payload = SCRIPT_VALUE_INT_PAYLOAD(floatValue);
#endif
        return qtrue;
    }

    if (value->type == CODUO_SCRIPT_VAR_STRING) {
        uint16_t stringHandle =
            (uint16_t)value->payload;
        const char *text = SL_ConvertToString(stringHandle);
        int32_t intValue = atoi(text);

        value->payload = SCRIPT_VALUE_INT_PAYLOAD(intValue);
        if (intValue == 0 &&
            ScriptRuntime_StringStartsWithZeroLiteral(
                SL_ConvertToString(stringHandle)) == qfalse) {
            script_errorMessage =
                va("cannot cast \"%s\" to int",
                   SL_ConvertToString(stringHandle));
            SL_RemoveRefToString(stringHandle);
            value->type = CODUO_SCRIPT_VAR_UNDEFINED;
            return qfalse;
        }

        value->type = CODUO_SCRIPT_VAR_INT;
        SL_RemoveRefToString(stringHandle);
        return qtrue;
    }

    script_errorMessage =
        va("cannot cast %s to int", script_variableTypeNames[value->type]);
    ScriptValue_ReleaseValue(value);
    value->type = CODUO_SCRIPT_VAR_UNDEFINED;
    return qfalse;
}

qboolean CastFloat(VariableValue *value)
{
    if (value->type == CODUO_SCRIPT_VAR_FLOAT) {
        return qtrue;
    }

    if (value->type == CODUO_SCRIPT_VAR_INT) {
        value->type = CODUO_SCRIPT_VAR_FLOAT;
        value->payload = ScriptValue_FloatPayload((float)(int32_t)value->payload);
        return qtrue;
    }

    if (value->type == CODUO_SCRIPT_VAR_STRING) {
        uint16_t stringHandle =
            (uint16_t)value->payload;
        const char *text = SL_ConvertToString(stringHandle);

        float floatValue = (float)atof(text);
        value->payload = ScriptValue_FloatPayload(floatValue);
        if (floatValue == 0.0f &&
            ScriptRuntime_StringStartsWithZeroLiteral(
                SL_ConvertToString(stringHandle)) == qfalse) {
            script_errorMessage =
                va("cannot cast \"%s\" to float",
                   SL_ConvertToString(stringHandle));
            SL_RemoveRefToString(stringHandle);
            value->type = CODUO_SCRIPT_VAR_UNDEFINED;
            return qfalse;
        }

        value->type = CODUO_SCRIPT_VAR_FLOAT;
        SL_RemoveRefToString(stringHandle);
        return qtrue;
    }

    script_errorMessage =
        va("cannot cast %s to float", script_variableTypeNames[value->type]);
    ScriptValue_ReleaseValue(value);
    value->type = CODUO_SCRIPT_VAR_UNDEFINED;
    return qfalse;
}

qboolean CastString(VariableValue *value)
{
    if (value->type == CODUO_SCRIPT_VAR_STRING) {
        return qtrue;
    }

    if (value->type == CODUO_SCRIPT_VAR_INT) {
        value->type = CODUO_SCRIPT_VAR_STRING;
        ScriptValue_StoreStringHandle(
            value, SL_GetStringForInt((int32_t)value->payload));
        return qtrue;
    }

    if (value->type == CODUO_SCRIPT_VAR_FLOAT) {
        float floatValue = ScriptValue_PayloadFloat(value->payload);

        value->type = CODUO_SCRIPT_VAR_STRING;
        ScriptValue_StoreStringHandle(value,
                                      SL_GetStringForFloat(floatValue));
        return qtrue;
    }

    if (value->type == CODUO_SCRIPT_VAR_VECTOR) {
        coduo_script_value_payload_t vectorPayload = value->payload;

        value->type = CODUO_SCRIPT_VAR_STRING;
        ScriptValue_StoreStringHandle(
            value, SL_GetStringForVector((const float *)vectorPayload));
        RemoveRefToVector(vectorPayload);
        return qtrue;
    }

    script_errorMessage =
        va("cannot cast %s to string", script_variableTypeNames[value->type]);
    ScriptValue_ReleaseValue(value);
    value->type = CODUO_SCRIPT_VAR_UNDEFINED;
    return qfalse;
}

qboolean CastIString(VariableValue *value)
{
    if (value->type == CODUO_SCRIPT_VAR_LOCALIZED_STRING) {
        return qtrue;
    }

    script_errorMessage =
        va("cannot cast %s to istring", script_variableTypeNames[value->type]);
    ScriptValue_ReleaseValue(value);
    value->type = CODUO_SCRIPT_VAR_UNDEFINED;
    return qfalse;
}

qboolean CastVector(VariableValue *value)
{
    if (value->type == CODUO_SCRIPT_VAR_VECTOR) {
        return qtrue;
    }

    script_errorMessage =
        va("cannot cast %s to vector", script_variableTypeNames[value->type]);
    ScriptValue_ReleaseValue(value);
    value->type = CODUO_SCRIPT_VAR_UNDEFINED;
    return qfalse;
}

qboolean CastPointer(VariableValue *value)
{
    if (value->type == CODUO_SCRIPT_VAR_OBJECT) {
        return qtrue;
    }

    script_errorMessage =
        va("cannot cast %s to object", script_variableTypeNames[value->type]);
    ScriptValue_ReleaseValue(value);
    value->type = CODUO_SCRIPT_VAR_UNDEFINED;
    return qfalse;
}

void CastVector2(VariableValue values[3])
{
    vec3_t vector;

    for (int32_t index = 2; index >= 0; --index) {
        if (CastFloat(&values[index]) == qfalse) {
            script_errorParameterIndex = index;
            ScriptRuntime_RaiseError();
        } else {
            vector[2 - index] = ScriptValue_PayloadFloat(values[index].payload);
        }
    }

    values[0].type = CODUO_SCRIPT_VAR_VECTOR;
    values[0].payload = AllocVectorCopy(vector);
}

void ClearVector(VariableValue values[3])
{
    for (int32_t index = 2; index >= 0; --index) {
        ScriptValue_ReleaseValue(&values[index]);
    }

    values[0].type = CODUO_SCRIPT_VAR_UNDEFINED;
}

void UnmatchingTypesError(VariableValue *left,
                          VariableValue *right)
{
    script_errorMessage = va("pair has unmatching types '%s' and '%s'",
                             script_variableTypeNames[script_coerceLeftType],
                             script_variableTypeNames[script_coerceRightType]);
    ScriptValue_ReleaseValue(left);
    left->type = CODUO_SCRIPT_VAR_UNDEFINED;
    ScriptValue_ReleaseValue(right);
    right->type = CODUO_SCRIPT_VAR_UNDEFINED;
}

qboolean CastWeakerPair(VariableValue *left,
                        VariableValue *right)
{
    script_coerceLeftType = left->type;
    script_coerceRightType = right->type;

    if (script_coerceLeftType == script_coerceRightType) {
        return qtrue;
    }

    if (script_coerceLeftType < script_coerceRightType) {
        if (script_coerceLeftType == CODUO_SCRIPT_VAR_STRING) {
            if (script_coerceRightType == CODUO_SCRIPT_VAR_FLOAT) {
                float floatValue = ScriptValue_PayloadFloat(right->payload);

                right->type = CODUO_SCRIPT_VAR_STRING;
                ScriptValue_StoreStringHandle(
                    right, SL_GetStringForFloat(floatValue));
                return qtrue;
            }

            if (script_coerceRightType == CODUO_SCRIPT_VAR_VECTOR) {
                coduo_script_value_payload_t vectorPayload = right->payload;

                right->type = CODUO_SCRIPT_VAR_STRING;
                ScriptValue_StoreStringHandle(
                    right,
                    SL_GetStringForVector((const float *)vectorPayload));
                RemoveRefToVector(vectorPayload);
                return qtrue;
            }

            if (script_coerceRightType == CODUO_SCRIPT_VAR_INT) {
                right->type = CODUO_SCRIPT_VAR_STRING;
                ScriptValue_StoreStringHandle(
                    right, SL_GetStringForInt((int32_t)right->payload));
                return qtrue;
            }
        } else if (script_coerceLeftType != CODUO_SCRIPT_VAR_FLOAT) {
            UnmatchingTypesError(left, right);
            return qfalse;
        }

        if (script_coerceRightType == CODUO_SCRIPT_VAR_INT) {
            right->type = CODUO_SCRIPT_VAR_FLOAT;
            right->payload = ScriptValue_FloatPayload(
                (float)(int32_t)right->payload);
            return qtrue;
        }
    } else {
        if (script_coerceRightType == CODUO_SCRIPT_VAR_STRING) {
            if (script_coerceLeftType == CODUO_SCRIPT_VAR_FLOAT) {
                float floatValue = ScriptValue_PayloadFloat(left->payload);

                left->type = CODUO_SCRIPT_VAR_STRING;
                ScriptValue_StoreStringHandle(
                    left, SL_GetStringForFloat(floatValue));
                return qtrue;
            }

            if (script_coerceLeftType == CODUO_SCRIPT_VAR_VECTOR) {
                coduo_script_value_payload_t vectorPayload = left->payload;

                left->type = CODUO_SCRIPT_VAR_STRING;
                ScriptValue_StoreStringHandle(
                    left,
                    SL_GetStringForVector((const float *)vectorPayload));
                RemoveRefToVector(vectorPayload);
                return qtrue;
            }

            if (script_coerceLeftType == CODUO_SCRIPT_VAR_INT) {
                left->type = CODUO_SCRIPT_VAR_STRING;
                ScriptValue_StoreStringHandle(
                    left, SL_GetStringForInt((int32_t)left->payload));
                return qtrue;
            }
        } else if (script_coerceRightType != CODUO_SCRIPT_VAR_FLOAT) {
            UnmatchingTypesError(left, right);
            return qfalse;
        }

        if (script_coerceLeftType == CODUO_SCRIPT_VAR_INT) {
            left->type = CODUO_SCRIPT_VAR_FLOAT;
            left->payload = ScriptValue_FloatPayload(
                (float)(int32_t)left->payload);
            return qtrue;
        }
    }

    UnmatchingTypesError(left, right);
    return qfalse;
}

qboolean CheckEquality(VariableValue *left,
                       VariableValue *right)
{
    if (CastWeakerPair(left, right) == qfalse) {
        return qfalse;
    }

    switch (left->type) {
    case CODUO_SCRIPT_VAR_UNDEFINED:
        left->type = CODUO_SCRIPT_VAR_INT;
        left->payload = qtrue;
        return qtrue;

    case CODUO_SCRIPT_VAR_STRING:
    case CODUO_SCRIPT_VAR_LOCALIZED_STRING: {
        uint16_t leftString =
            (uint16_t)left->payload;
        uint16_t rightString =
            (uint16_t)right->payload;

        left->type = CODUO_SCRIPT_VAR_INT;
        SL_RemoveRefToString(leftString);
        SL_RemoveRefToString(rightString);
        left->payload = leftString == rightString ? qtrue : qfalse;
        return qtrue;
    }

    case CODUO_SCRIPT_VAR_VECTOR: {
        const float *leftVector = (const float *)left->payload;
        const float *rightVector = (const float *)right->payload;
        qboolean equal =
            leftVector[0] == rightVector[0] &&
            leftVector[1] == rightVector[1] &&
            leftVector[2] == rightVector[2];

        left->type = CODUO_SCRIPT_VAR_INT;
        RemoveRefToVector(left->payload);
        RemoveRefToVector(right->payload);
        left->payload = equal;
        return qtrue;
    }

    case CODUO_SCRIPT_VAR_FLOAT: {
        float leftFloat = ScriptValue_PayloadFloat(left->payload);
        float rightFloat = ScriptValue_PayloadFloat(right->payload);
        left->type = CODUO_SCRIPT_VAR_INT;
        left->payload =
            FUN_080b085c(leftFloat - rightFloat) <
                    (long double)SCRIPT_VALUE_FLOAT_EQUAL_EPSILON
                ? qtrue
                : qfalse;
        return qtrue;
    }

    case CODUO_SCRIPT_VAR_INT:
        left->payload =
            (uint32_t)left->payload == (uint32_t)right->payload ? qtrue
                                                                : qfalse;
        return qtrue;

    case CODUO_SCRIPT_VAR_OBJECT: {
        uint16_t leftObject =
            (uint16_t)left->payload;
        uint16_t rightObject =
            (uint16_t)right->payload;

        left->type = CODUO_SCRIPT_VAR_INT;
        RemoveRefToObject(leftObject);
        RemoveRefToObject(rightObject);
        left->payload = leftObject == rightObject ? qtrue : qfalse;
        return qtrue;
    }

    case CODUO_SCRIPT_VAR_ANIMATION:
        left->type = CODUO_SCRIPT_VAR_INT;
        left->payload = left->payload == right->payload ? qtrue : qfalse;
        return qtrue;

    default:
        UnmatchingTypesError(left, right);
        return qfalse;
    }
}

uint16_t VM_ConcatenateStrings(VariableValue *values)
{
    uint16_t leftHandle =
        (uint16_t)values[0].payload;
    uint16_t rightHandle =
        (uint16_t)values[1].payload;
    const char *left = SL_ConvertToString(leftHandle);
    const char *right = SL_ConvertToString(rightHandle);
    uint32_t leftLength = (uint32_t)strlen(left);
    uint32_t rightLength = (uint32_t)strlen(right);
    uint32_t combinedLength = leftLength + rightLength + 1U;
    char combined[combinedLength];

    strcpy(combined, left);
    strcpy(combined + leftLength, right);

    return SL_GetStringOfLen(combined, 0, combinedLength,
                                    SCRIPT_STRING_INTERN_RUNTIME_TYPE);
}

int32_t FastRound(float value)
{
    /* 0x080b0834..0x080b0851 keeps value+0.5f live in x87 until the
     * truncating fistp; there is no intervening binary32 store. */
#if EMULATE_X87
    return x87f_store_i32_trunc(
        x87f_add(x87f_load_f32(value), x87f_load_f32(0.5f)));
#elif defined(__x86_64__)
    static const float half = 0.5f;
    return CODUO_X87_TRUNCATE_I32(
        (long double)value + (long double)half);
#else
    return (int32_t)(value + 0.5f);
#endif
}

long double FUN_080b085c(float value)
{
    return (long double)fabsf(value);
}

uint64_t rdtsc(void)
{
#if defined(__i386__) || defined(__x86_64__)
    uint32_t low;
    uint32_t high;

    __asm__ volatile("rdtsc" : "=a"(low), "=d"(high));
    return ((uint64_t)high << 32) | low;
#else
    return 0;
#endif
}

/* No-op leaf; no source-level name has been proven. */
void FUN_080b0892(void)
{
}
