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

#include "../core_math/core_math_private.h"
#include "../core_runtime/core_runtime_private.h"
#include "coduo_engine_structs.h"
#include "coduo_native_x87.h"
#include "script_compile_private.h"
#include "script_runtime_private.h"
#include "script_variable_private.h"

enum {
    SCRIPT_USAGE_STACK_ENTRY_CODEPOS = CODUO_SCRIPT_VAR_CODEPOS,
    SCRIPT_USAGE_STACK_ENTRY_ARRAY = CODUO_SCRIPT_VAR_OBJECT,
    SCRIPT_USAGE_MAX_STACK_DEPTH = 32,
    SCRIPT_USAGE_STACK_NODE_BITS = 0x60,
    SCRIPT_USAGE_STACK_SIGNATURE_NODE_TYPE = CODUO_SCRIPT_VAR_STACK,
    SCRIPT_USAGE_PRINT_CHANNEL = 0
};

typedef struct script_usage_stack_signature_s {
    coduo_script_codepos_t codePosStack[SCRIPT_USAGE_MAX_STACK_DEPTH];
    int32_t depth;
    float variableUsage;
    float endonUsage;
} script_usage_stack_signature_t;

float Scr_GetObjectUsage(uint16_t object);
float Scr_GetEndonUsage(uint16_t localVars);

/* NOT_FROM_ORIGINAL_SOURCE: reads the portable packed frame representation,
 * whose code-position carriers are base-relative dwords rather than the
 * original i386 frame's four-byte pointers. */
static void coduomp_script_usage_read_stack_entry_value(
    const VariableStackBufferValue *entry, VariableValue *value)
{
    coduo_script_value_payload_t payload;

    value->type = (coduo_script_variable_type_t)entry->type;
    memcpy(&payload, entry->payload, sizeof(payload));
    if (value->type == CODUO_SCRIPT_VAR_CODEPOS) {
        value->payload = (coduo_script_value_payload_t)
            (script_codeBase + (uint32_t)payload);
    } else if (value->type == CODUO_SCRIPT_VAR_INT) {
        value->payload = SCRIPT_VALUE_U32_PAYLOAD(payload);
    } else {
        value->payload = payload;
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: stack variables carry live frame pointers. */
static VariableStackBuffer *
coduomp_script_usage_stack_frame_for_node(uint16_t handle)
{
    VariableValue value;

    GetVariableValue(handle, &value);
    return (VariableStackBuffer *)value.payload;
}

/* NOT_FROM_ORIGINAL_SOURCE: centralizes recovered entity usage name access. */
static const char *
coduomp_script_usage_entity_type_name(
    const coduo_script_entity_type_usage_t *usage)
{
    return usage->name;
}

int ThreadInfoCompare(const void *left, const void *right)
{
    const script_usage_stack_signature_t *leftStack = left;
    const script_usage_stack_signature_t *rightStack = right;
    int32_t sharedDepth = leftStack->depth < rightStack->depth
                              ? leftStack->depth
                              : rightStack->depth;

    for (int32_t index = 0; index < sharedDepth; ++index) {
        coduo_script_codepos_t leftCodePos = leftStack->codePosStack[index];
        coduo_script_codepos_t rightCodePos = rightStack->codePosStack[index];

        if (leftCodePos != rightCodePos) {
            return (int32_t)(
                (uint32_t)(uintptr_t)leftCodePos -
                (uint32_t)(uintptr_t)rightCodePos);
        }
    }

    return (int32_t)(leftStack->depth - rightStack->depth);
}

float Scr_GetEntryUsage(coduo_script_variable_type_t type,
                        uint16_t object)
{
    if (type == CODUO_SCRIPT_VAR_OBJECT &&
        GetVarType(object) == CODUO_SCRIPT_VAR_ARRAY) {
        /* Stock 0x80a940f adds 1.0 to the fild-exact count (no float cast). */
        return Scr_GetObjectUsage(object) /
               (script_variableNodes[object].payload.halves.valueOrRefCount + 1.0f);
    }

    return 0.0f;
}

/* Stock 0x80a944f returns the sum as raw st(0) with no float store. */
long double
Scr_GetEntryUsageForNode(coduo_script_variable_node_t *node)
{
    return 1.0f + Scr_GetEntryUsage(
                      (coduo_script_variable_type_t)(
                          node->packedTypeIndex &
                          CODUO_SCRIPT_VARIABLE_NODE_TYPE_MASK),
                      node->payload.halves.valueOrRefCount);
}

float Scr_GetEndonUsage(uint16_t localVars)
{
    uint16_t pauseEntry =
        FindObjectVariable(script_pauseArrayHandle, localVars);

    if (pauseEntry == 0) {
        return 0.0f;
    }

    return Scr_GetObjectUsage(FindObject(pauseEntry));
}

float Scr_GetObjectUsage(uint16_t object)
{
    float usage = 1.0f;

    for (uint16_t child = FindNextSibling(object);
         child != 0; child = FindNextSibling(child)) {
        usage += Scr_GetEntryUsageForNode(&script_variableNodes[child]);
    }

    return usage;
}

float Scr_GetThreadUsage(VariableStackBuffer *frame,
                         float *endonUsage)
{
    float variableUsage = Scr_GetObjectUsage(frame->localId);

    *endonUsage = Scr_GetEndonUsage(frame->localId);

    uint32_t remaining = frame->size;
    const VariableStackBufferValue *entry = frame->buf + remaining;

    while (remaining != 0) {
        uint8_t type = entry[-1].type;

        if (type == SCRIPT_USAGE_STACK_ENTRY_CODEPOS) {
            VariableValue priorValue;

            /* ORIGINAL_BINARY_BUG: stock 0x080a998f..0x080a99a5 assumes a
             * preceding entry.  With one remaining serialized entry it reads
             * before frame->buf and wraps the remaining count to UINT32_MAX. */
            coduomp_script_usage_read_stack_entry_value(
                entry - 2,
                &priorValue);
            uint16_t localVars =
                (uint16_t)priorValue.payload;

            entry -= 2;
            remaining -= 2;
            variableUsage += Scr_GetObjectUsage(localVars);
            *endonUsage += Scr_GetEndonUsage(localVars);
        } else {
            VariableValue value;

            coduomp_script_usage_read_stack_entry_value(
                entry - 1,
                &value);
            variableUsage += Scr_GetEntryUsage(
                type, (uint16_t)value.payload);
            entry--;
            remaining--;
        }
    }

    return variableUsage;
}

void Scr_DumpScriptThreads(void)
{
    script_usage_stack_signature_t *signatures =
        Z_Malloc((size_t)CODUO_SCRIPT_VARIABLE_NODE_COUNT *
                 sizeof(*signatures));
    int32_t signatureCount = 0;

    for (uint32_t handle = 1; handle < CODUO_SCRIPT_VARIABLE_NODE_COUNT;
         ++handle) {
        coduo_script_variable_node_t *node = &script_variableNodes[handle];

        if ((node->packedTypeIndex & SCRIPT_USAGE_STACK_NODE_BITS) == 0 ||
            (node->packedTypeIndex & CODUO_SCRIPT_VARIABLE_NODE_TYPE_MASK) !=
                SCRIPT_USAGE_STACK_SIGNATURE_NODE_TYPE) {
            continue;
        }

        script_usage_stack_signature_t *signature =
            &signatures[signatureCount++];
        VariableStackBuffer *frame =
            coduomp_script_usage_stack_frame_for_node((uint16_t)handle);
        int32_t depth = 0;
        const VariableStackBufferValue *entry = frame->buf;

        /* ORIGINAL_BINARY_BUG: stock 0x080a5652 and 0x080a5669 append
         * code positions without bounding depth to the 32-slot array. */
        for (uint32_t remaining = frame->size; remaining != 0;
             --remaining) {
            VariableValue value;

            coduomp_script_usage_read_stack_entry_value(entry, &value);
            entry++;
            if (value.type == CODUO_SCRIPT_VAR_CODEPOS) {
                signature->codePosStack[depth++] =
                    (coduo_script_codepos_t)value.payload;
            }
        }

        signature->codePosStack[depth++] =
            script_codeBase + frame->pos;
        signature->variableUsage =
            Scr_GetThreadUsage(frame, &signature->endonUsage);
        signature->depth = depth;

        int32_t tail = depth - 1;
        /* ORIGINAL_BINARY_BUG: stock 0x080a56b2..0x080a56e6 reverses this
         * stack in place from front to back, so the already-overwritten front
         * half is reread and older frames are lost. */
        for (int32_t index = 0; index < depth; ++index) {
            signature->codePosStack[index] =
                signature->codePosStack[tail - index];
        }
    }

    qsort(signatures, signatureCount, sizeof(*signatures),
          ThreadInfoCompare);

    Com_Printf("********************************\n");
    int32_t index = 0;
    while (index < signatureCount) {
        script_usage_stack_signature_t *signature = &signatures[index];
        int32_t count = 0;
        float variableUsage = 0.0f;
        float endonUsage = 0.0f;

        do {
            count++;
            variableUsage += signatures[index].variableUsage;
            endonUsage += signatures[index].endonUsage;
            index++;
        } while (index < signatureCount &&
                 ThreadInfoCompare(signature, &signatures[index]) == 0);

#if defined(__x86_64__)
        coduo_x87_truncation_control_t conversionControl;
        int32_t variableUsageInteger = CODUO_X87_TRUNCATE_I32_FIRST(
            &conversionControl, (long double)variableUsage);
        int32_t endonUsageInteger = CODUO_X87_TRUNCATE_I32_NEXT(
            &conversionControl, (long double)endonUsage);
#else
        int32_t variableUsageInteger = (int32_t)variableUsage;
        int32_t endonUsageInteger = (int32_t)endonUsage;
#endif
        Com_Printf("count: %d, var usage: %d, endon usage: %d\n", count,
                   variableUsageInteger, endonUsageInteger);
        Scr_PrintPrevCodePos(SCRIPT_USAGE_PRINT_CHANNEL,
                                 signature->codePosStack[0], 0);
        for (int32_t stackIndex = 1; stackIndex < signature->depth;
             ++stackIndex) {
            Com_Printf("called from:\n");
            Scr_PrintPrevCodePos(SCRIPT_USAGE_PRINT_CHANNEL,
                                     signature->codePosStack[stackIndex], 0);
        }
    }

    Z_Free(signatures);
    Com_Printf("********************************\n");

    for (uint32_t usageIndex = 0;
         usageIndex < script_entityTypeUsageCount; ++usageIndex) {
        uint16_t classEntry =
            FindVariable(script_entityTypeClassMapRoot, usageIndex);

        if (classEntry == 0) {
            continue;
        }

        float variableUsage = 0.0f;
        int32_t count = 0;
        uint16_t classObject =
            FindObject(classEntry);

        for (uint16_t child =
                 GetArraySize(classObject);
             child != 0; child = FindNextSibling(child)) {
            count++;
            variableUsage += Scr_GetObjectUsage(
                script_variableNodes[child].payload.halves.valueOrRefCount);
        }

#if defined(__x86_64__)
        int32_t variableUsageInteger =
            CODUO_X87_TRUNCATE_I32((long double)variableUsage);
#else
        int32_t variableUsageInteger = (int32_t)variableUsage;
#endif
        Com_Printf("ent type '%s'... count: %d, var usage: %d\n",
                   coduomp_script_usage_entity_type_name(
                       &script_entityTypeUsageRecords[usageIndex]),
                   count, variableUsageInteger);
    }

    Com_Printf("********************************\n");
}

void Scr_DumpScriptVariables(void)
{
}
