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

#include "../core_memory/core_memory_private.h"
#include "../filesystem/fs_private.h"
#include "../scripting/script_memory_private.h"
#include "coduo_engine_structs.h"
#include "xanim_private.h"

typedef void (*coduo_xanim_copy_callback_t)(void *data, size_t size);

void XAnimSetLeafNode(XAnim *tree,
                      uint16_t nodeIndex,
                      const char *animName)
{
    fileData_t *asset;
    XAnimEntry *entry;

    asset = FS_GetDataForFile("xanim", animName, "");
    if (asset == NULL) {
        Com_Error(1, "\x15" "Cannot find 'xanim/%s'", animName);
        return;
    }

    entry = &tree->entries[nodeIndex];
    entry->childCount = 0;
    entry->payload.leafAsset = asset;
}

void XAnimSetParentNode(XAnim *tree,
                        uint16_t nodeIndex,
                        const char *unusedName,
                        uint16_t firstChildIndex,
                        uint16_t childCount,
                        uint16_t flags)
{
    XAnimEntry *entry;

    (void)unusedName;

    entry = &tree->entries[nodeIndex];
    entry->childCount = childCount;
    entry->payload.parent.flags = flags;
    entry->payload.parent.firstChildIndex = firstChildIndex;

    for (int32_t child = 0; child < childCount; ++child) {
        tree->entries[firstChildIndex + child].parentIndex = nodeIndex;
    }
}

XAnim *XAnimAllocTree(const char *name,
                                   uint32_t nodeCount,
                                   coduo_xanim_alloc_callback_t allocCallback)
{
    XAnim *tree;

    tree = allocCallback(sizeof(XAnim) +
                         (size_t)nodeCount *
                             sizeof(XAnimEntry));
    tree->name = name;
    tree->nodeCount = nodeCount;
    return tree;
}

XAnimTree *XAnimAllocRuntimeTree(
    XAnim *sourceTree, coduo_xanim_alloc_callback_t allocCallback)
{
    size_t runtimeTreeSize;
    XAnimTree *runtimeTree;

    runtimeTreeSize = offsetof(XAnimTree, poolNodeHandles) +
                      (size_t)sourceTree->nodeCount *
                          sizeof(runtimeTree->poolNodeHandles[0]) +
                      (size_t)sourceTree->nodeCount *
                          sizeof(coduo_xanim_runtime_tree_tail_span_t) +
                      sizeof(uint16_t);
    runtimeTree = allocCallback(runtimeTreeSize);
    Com_Memset(runtimeTree, 0, runtimeTreeSize);
    runtimeTree->sourceTree = sourceTree;
    return runtimeTree;
}

void XAnimCopyRuntimeTree(XAnimTree *runtimeTree,
                          coduo_xanim_copy_callback_t copyCallback)
{
    XAnim *sourceTree;

    sourceTree = runtimeTree->sourceTree;
    copyCallback(runtimeTree,
                 offsetof(XAnimTree, poolNodeHandles) +
                     (size_t)sourceTree->nodeCount *
                         sizeof(runtimeTree->poolNodeHandles[0]) +
                     (size_t)sourceTree->nodeCount *
                         sizeof(coduo_xanim_runtime_tree_tail_span_t) +
                     sizeof(uint16_t));
}

/* NOT_FROM_ORIGINAL_SOURCE: shared readable lookup for debug notify weights. */
static float coduo_engine_xanim_debug_notify_weight(XAnimEntry *entry,
                                    int16_t notifyIndex)
{
    if (notifyIndex < 0) {
        return 1.0f;
    }

    return entry->payload.leafAsset->data.xanimParts
        ->noteTracks[notifyIndex]
        .time;
}

void XAnimDisplay(XAnimTree *runtimeTree,
                  uint32_t nodeIndex, int32_t depth)
{
    XAnim *tree = runtimeTree->sourceTree;
    XAnimEntry *entry = &tree->entries[nodeIndex];
    uint16_t poolNodeHandle =
        runtimeTree->poolNodeHandles[nodeIndex];

    if (poolNodeHandle == 0) {
        return;
    }

    XAnimInfo *poolNode = &xanim_pool[poolNodeHandle];
    XAnimState *payload =
        &poolNode->states[xanim_activePoolPayloadSlot];

    if (payload->currentWeight == 0.0f) {
        return;
    }

    for (int32_t indent = 0; indent < depth; ++indent) {
        Com_Printf("  ");
    }

    if (entry->childCount == 0) {
        XAnimParts *loadedRecord =
            entry->payload.leafAsset->data.xanimParts;
        float realTimeDelta = payload->time - payload->oldTime;

        if (realTimeDelta < 0.0f) {
            realTimeDelta += 1.0f;
        }

        if (loadedRecord->frequency != 0.0f) {
            realTimeDelta /= loadedRecord->frequency;
        } else {
            realTimeDelta = 0.0f;
        }

        if (xanim_activePoolPayloadSlot == 0 || poolNode->notifyName == 0) {
            Com_Printf("(name) %s: (weight) %f -> %f, "
                       "(time) %f -> %f, (realtimedelta) %f\n",
                       entry->payload.leafAsset->name,
                       (double)payload->currentWeight,
                       (double)payload->targetWeight,
                       (double)payload->oldTime,
                       (double)payload->time,
                       (double)realTimeDelta);
        } else {
            const char *notifyName =
                SL_ConvertToString(poolNode->notifyName);
            float notifyWeight =
                coduo_engine_xanim_debug_notify_weight(entry, poolNode->notifyIndex);

            Com_Printf("(name) %s: (weight) %f -> %f, "
                       "(time) %f -> %f, (realtimedelta) %f, '%s' (%f)\n",
                       entry->payload.leafAsset->name,
                       (double)payload->currentWeight,
                       (double)payload->targetWeight,
                       (double)payload->oldTime,
                       (double)payload->time,
                       (double)realTimeDelta, notifyName,
                       (double)notifyWeight);
        }

        return;
    }

    if (xanim_activePoolPayloadSlot == 0 || poolNode->notifyName == 0) {
        if (XAnimHasTime(tree, nodeIndex) == qfalse) {
            Com_Printf("(index) %d: (weight) %f -> %f\n", nodeIndex,
                       (double)payload->currentWeight,
                       (double)payload->targetWeight);
        } else {
            Com_Printf("(index) %d: (weight) %f -> %f, (time) %f -> %f\n",
                       nodeIndex, (double)payload->currentWeight,
                       (double)payload->targetWeight,
                       (double)payload->oldTime,
                       (double)payload->time);
        }
    } else {
        const char *notifyName =
            SL_ConvertToString(poolNode->notifyName);

        if (XAnimHasTime(tree, nodeIndex) == qfalse) {
            Com_Printf("(index) %d: (weight) %f -> %f, '%s'\n", nodeIndex,
                       (double)payload->currentWeight,
                       (double)payload->targetWeight, notifyName);
        } else if (poolNode->notifyChildIndex == 0) {
            Com_Printf("(index) %d: (weight) %f -> %f, "
                       "(time) %f -> %f, '%s'\n",
                       nodeIndex, (double)payload->currentWeight,
                       (double)payload->targetWeight,
                       (double)payload->oldTime,
                       (double)payload->time, notifyName);
        } else {
            XAnimEntry *sourceEntry =
                &tree->entries[poolNode->notifyChildIndex];
            float notifyWeight = coduo_engine_xanim_debug_notify_weight(
                sourceEntry, poolNode->notifyIndex);

            Com_Printf("(index) %d: (weight) %f -> %f, "
                       "(time) %f -> %f, '%s', (%f)\n",
                       nodeIndex, (double)payload->currentWeight,
                       (double)payload->targetWeight,
                       (double)payload->oldTime,
                       (double)payload->time, notifyName,
                       (double)notifyWeight);
        }
    }

    for (int32_t child = 0; child < entry->childCount; ++child) {
        XAnimDisplay(runtimeTree, entry->payload.parent.firstChildIndex + child,
                     depth + 1);
    }
}

void DObjDisplayAnim(DObj *state)
{
    if (state->runtimeTree == NULL) {
        Com_Printf("NO TREE\n");
        return;
    }

    XAnimDisplay(state->runtimeTree, 0, 0);
    Com_Printf("\n");
}
