#include <stdint.h>
#include <stdio.h>
#include <stddef.h>
#include <string.h>
#include <strings.h>

#include "../core_memory/core_memory_private.h"
#include "../core_runtime/core_runtime_private.h"
#include "../filesystem/fs_private.h"
#include "../scripting/script_compile_private.h"
#include "../scripting/script_memory_private.h"
#include "../scripting/script_source_positions_private.h"
#include "../scripting/script_variable_private.h"
#include "coduo_engine_structs.h"
#include "xanim_tree_private.h"

enum {
    SCRIPT_ANIM_TREE_ROOT_NODE_INDEX = 0,
    SCRIPT_ANIM_TREE_FIRST_CHILD_NODE_INDEX = 1,
    SCRIPT_ANIM_TREE_FIRST_TABLE_INDEX = 1,
    SCRIPT_ANIM_HASH_MULTIPLIER = 31,
    SCRIPT_ANIM_STRING_USER_NONE = 0,
    SCRIPT_ANIM_TREE_STRING_USER = 2,
    SCRIPT_ANIM_NAME_TYPE = 4,
    SCRIPT_ANIM_SOURCE_NONE = 0,
    SCRIPT_ANIM_OWNER_ERROR_DROP = 1,
    SCRIPT_ANIM_HUNK_ALIGNMENT = 4,
    SCRIPT_ANIM_TREE_FILENAME_BUFFER_SIZE = 64
};

extern void XAnimLoadFile(const char *animName,
                          coduo_script_anim_tree_alloc_fn allocCallback);

#if UINTPTR_MAX > UINT32_MAX
/* NOT_FROM_ORIGINAL_SOURCE: the original Mac signature uses char * for the
 * full-width address of a byte-packed four-byte animation cell. Original i386
 * overloads that word: unresolved-list pointer first, packed uint32_t
 * animation reference after resolution. On 64-bit the pointer is wider, but
 * SCRIPT_OP_GET_ANIMATION reads four bytes and zero-extends the reference into
 * its host-width value payload. A uintptr_t bytecode word would move following
 * code and would not fit Scr_FindAnim's uint32_t output contract. Keep only
 * the temporary full-width pointer in this sidecar, so no address is
 * truncated. */
typedef struct script_anim_unresolved_ref_s {
    char *target;
    struct script_anim_unresolved_ref_s *next;
    struct script_anim_unresolved_ref_s *allocationNext;
} script_anim_unresolved_ref_t;

static script_anim_unresolved_ref_t *script_animUnresolvedSidecars;

static script_anim_unresolved_ref_t *
/* NOT_FROM_ORIGINAL_SOURCE:
 * Allocate one node of the widened-host sidecar
 * described above.  The target remains a four-byte packed tree/node value.
 */
coduo_engine_script_anim_alloc_unresolved_ref(
    char *target, script_anim_unresolved_ref_t *next)
{
    script_anim_unresolved_ref_t *ref = Z_Malloc(sizeof(*ref));

    ref->target = target;
    ref->next = next;
    ref->allocationNext = script_animUnresolvedSidecars;
    script_animUnresolvedSidecars = ref;

    return ref;
}

/* NOT_FROM_ORIGINAL_SOURCE: the stock i386 unresolved list occupies the
 * bytecode words themselves and therefore owns no allocations. Native64 uses
 * the sidecars above because a host pointer cannot fit in that four-byte word.
 * Keep every sidecar owned by the complete anim-tree load phase: normal
 * resolution writes the packed uint32_t reference, while parse/validation
 * errors may unwind before visiting every per-animation list. Releasing at
 * both load-phase boundaries therefore reclaims resolved or abandoned nodes
 * and establishes an empty owner list before a new phase begins. */
void coduo_engine_script_anim_release_unresolved_ref_sidecars(void)
{
    while (script_animUnresolvedSidecars != NULL) {
        script_anim_unresolved_ref_t *ref = script_animUnresolvedSidecars;

        script_animUnresolvedSidecars = ref->allocationNext;
        Z_Free(ref);
    }
}
#endif

void AnimTreeCompileError(const char *message)
{
    const char *errorPos;
    uint32_t errorOffset;

    errorPos = Com_GetLastTokenPos();
    Com_EndParseSession();
    errorOffset = (uint32_t)(errorPos - script_animParseStart);
    CompileError(errorOffset, "%s", message);
}

void SetAnimCheck(int32_t animCheckEnabled)
{
    script_animCheckEnabled = animCheckEnabled;
}

uint32_t GetAnimTreeParseProperties(void)
{
    uint32_t flags;

    flags = 0;
    for (;;) {
        const char *token = Com_ParseOnLine(&script_animParseState);

        if (*token == '\0') {
            return flags;
        }

        uint32_t propertyIndex = 0;
        while (propertyIndex < CODUO_SCRIPT_ANIM_PROPERTY_NAME_COUNT &&
               strcasecmp(token, script_animPropertyNames[propertyIndex]) !=
                   0) {
            propertyIndex++;
        }

        switch (propertyIndex) {
        case 0:
            flags |= CODUO_SCRIPT_ANIM_PROPERTY_LOOPSYNC;
            break;
        case 1:
            flags |= CODUO_SCRIPT_ANIM_PROPERTY_NONLOOPSYNC;
            break;
        case 2:
            flags |= CODUO_SCRIPT_ANIM_PROPERTY_COMPLETE;
            break;
        default:
            AnimTreeCompileError("unknown anim property");
            break;
        }
    }
}

/* Name/signature: exact Mac symbol
 * Scr_EmitAnimationInternal(char *, unsigned short, unsigned short,
 * unsigned int).  The stock Linux i386 body uses one dword load/store at the
 * output address; memcpy retains those four bytes without imposing alignment. */
void Scr_EmitAnimationInternal(char *animRef,
                               uint16_t animHandle,
                               uint16_t treeHandle,
                               uint32_t source)
{
    uint16_t existingHandle;

    if (script_animCommentDepth != 0) {
        CompileError(
            source, "cannot reference animation from /# ... #/ comment");
        return;
    }

    existingHandle = FindVariable(treeHandle, animHandle);
    if (existingHandle == 0) {
        VariableValue value;

        existingHandle = GetVariable(treeHandle, animHandle);
        const uint32_t emptyRef = 0;
        memcpy(animRef, &emptyRef, sizeof(emptyRef));
#if UINTPTR_MAX > UINT32_MAX
        value.payload = (coduo_script_value_payload_t)
            coduo_engine_script_anim_alloc_unresolved_ref(animRef, NULL);
#else
        value.payload = (coduo_script_value_payload_t)animRef;
#endif
        value.type = CODUO_SCRIPT_VAR_CODEPOS;
        SetVariableValue(existingHandle, &value);
    } else {
        VariableValue *storedValue = GetVariableValueAddress(existingHandle);
#if UINTPTR_MAX > UINT32_MAX
        script_anim_unresolved_ref_t *head =
            (script_anim_unresolved_ref_t *)storedValue->payload;

        const uint32_t emptyRef = 0;
        memcpy(animRef, &emptyRef, sizeof(emptyRef));
        storedValue->payload = (coduo_script_value_payload_t)
            coduo_engine_script_anim_alloc_unresolved_ref(animRef, head);
#else
        const uint32_t previousRef = (uint32_t)storedValue->payload;
        memcpy(animRef, &previousRef, sizeof(previousRef));
        storedValue->payload = (coduo_script_value_payload_t)animRef;
#endif
    }
}

uint32_t Scr_GetAnimsIndex(XAnim *tree)
{
    int32_t slot = xanim_activePoolPayloadSlot;
    int32_t index = script_animTreeCounts[slot];

    while (index != 0 && script_animTrees[slot][index] != tree) {
        index--;
    }

    return index;
}

XAnim *Scr_GetAnims(uint32_t treeIndex)
{
    return script_animTrees[xanim_activePoolPayloadSlot][treeIndex];
}

uint16_t
Scr_UsingTreeInternal(const char *filename,
                      uint32_t *treeIndex)
{
    uint16_t filenameHandle;
    uint16_t treeHandle;
    uint16_t childHandle;
    int32_t slot;

    filenameHandle = Scr_CreateCanonicalFilename(filename);
    treeHandle = FindVariable(script_animTreeRoot, filenameHandle);
    slot = xanim_activePoolPayloadSlot;

    if (treeHandle == 0) {
        treeHandle = GetVariable(script_animTreeRoot, filenameHandle);
        childHandle = GetObject(treeHandle);
        /* ORIGINAL_BINARY_BUG: 0x0809cd04..0x0809cd30 increments and indexes
         * this one-based 128-entry row without a capacity check.  Preserved;
         * see original-bugs/coduomp-animtree-registry-overflow.md. */
        script_animTreeCounts[slot]++;
        script_animTreeHandles[slot][script_animTreeCounts[slot]] = treeHandle;
        *treeIndex = script_animTreeCounts[slot];
    } else {
        childHandle = FindObject(treeHandle);
        *treeIndex = 0;

        for (int32_t index = SCRIPT_ANIM_TREE_FIRST_TABLE_INDEX;
             index <= script_animTreeCounts[slot]; ++index) {
            if (script_animTreeHandles[slot][index] == treeHandle) {
                *treeIndex = index;
                break;
            }
        }
    }

    childHandle =
        GetVariable(childHandle,
                                SCRIPT_ANIM_TREE_ROOT_NODE_INDEX);
    childHandle = GetArray(childHandle);
    SL_RemoveRefToString(filenameHandle);
    return childHandle;
}

coduo_script_anim_tree_ref_t Scr_FindAnimTree(const char *filename)
{
    uint16_t filenameHandle;
    uint16_t treeHandle;
    coduo_script_anim_tree_ref_t outTree;

    filenameHandle = Scr_CreateCanonicalFilename(filename);
    treeHandle = FindVariable(script_animTreeRoot, filenameHandle);
    SL_RemoveRefToString(filenameHandle);

    outTree.tree = NULL;
    if (treeHandle != 0) {
        uint16_t childHandle;
        VariableValue value;

        filenameHandle = GetVariableName(treeHandle);
        treeHandle = FindObject(treeHandle);
        childHandle =
            FindVariable(
                treeHandle, SCRIPT_ANIM_TREE_FIRST_CHILD_NODE_INDEX);
        if (childHandle != 0) {
            GetVariableValue(childHandle, &value);
            outTree.tree = (XAnim *)value.payload;
        }
    }

    return outTree;
}

void Scr_FindAnim(const char *treeName, const char *animName,
                  scr_anim_t *animRef)
{
    uint16_t animHandle;
    uint16_t treeHandle;
    uint32_t treeIndex;

    animHandle = SL_GetLowercaseString_(
        animName, SCRIPT_ANIM_STRING_USER_NONE, SCRIPT_ANIM_NAME_TYPE);
    treeHandle = Scr_UsingTreeInternal(treeName, &treeIndex);
    Scr_EmitAnimationInternal((char *)animRef, animHandle, treeHandle,
                              SCRIPT_ANIM_SOURCE_NONE);
    SL_RemoveRefToString(animHandle);
}

/* Name/signature: exact Mac symbol
 * Scr_EmitAnimation(char *, unsigned short, unsigned int). */
void Scr_EmitAnimation(char *animRef,
                       uint16_t animHandle,
                       uint32_t source)
{
    if (script_animCurrentUsingTree == 0) {
        CompileError(source, "#using_animtree was not specified");
        return;
    }

    Scr_EmitAnimationInternal(animRef, animHandle,
                              script_animCurrentUsingTree, source);
}

qboolean AnimTreeParseInternal(uint16_t parentHandle,
                               uint16_t treeHandle,
                               qboolean addEmptyVoid,
                               qboolean addLoopingVoid,
                               qboolean forceLoadedChildren)
{
    uint16_t animNameHandle;
    uint16_t animNodeHandle;
    uint16_t childNodeHandle;
    uint32_t propertyFlags;
    qboolean missingFromTree;
    qboolean hitEndOfFile;

    animNameHandle = 0;
    animNodeHandle = 0;
    propertyFlags = 0;
    missingFromTree = qfalse;
    hitEndOfFile = qtrue;

    for (;;) {
        const char *token = Com_Parse(&script_animParseState);

        if (script_animParseState == NULL) {
            break;
        }

        if (Scr_IsIdentifier(token) != qfalse) {
            if (missingFromTree != qfalse) {
                RemoveVariable(parentHandle, animNameHandle);
            }

            animNameHandle =
                SL_GetLowercaseString_(
                    token, SCRIPT_ANIM_TREE_STRING_USER,
                    SCRIPT_ANIM_NAME_TYPE);
            if (FindVariable(parentHandle, animNameHandle) != 0) {
                AnimTreeCompileError("duplicate animation");
            }
            animNodeHandle = GetVariable(parentHandle, animNameHandle);

            missingFromTree = qfalse;
            if (forceLoadedChildren == qfalse &&
                FindVariable(treeHandle, animNameHandle) == 0 &&
                script_animCheckEnabled == 0) {
                missingFromTree = qtrue;
            }

            propertyFlags = 0;
            token = Com_ParseOnLine(&script_animParseState);
            if (*token == '\0') {
                continue;
            }

            if (Scr_IsIdentifier(token) != qfalse) {
                AnimTreeCompileError("FIXME: aliases not yet implemented");
            }
            if (*token != ':' || token[1] != '\0') {
                AnimTreeCompileError("bad token");
            }

            propertyFlags = GetAnimTreeParseProperties();
            token = Com_Parse(&script_animParseState);
            if (*token != '{' || token[1] != '\0') {
                AnimTreeCompileError(
                    "properties cannot be applied to primitive animations");
            }
        }

        if (*token == '{') {
            if (token[1] != '\0') {
                AnimTreeCompileError("bad token");
            }

            token = Com_ParseOnLine(&script_animParseState);
            if (*token != '\0') {
                AnimTreeCompileError("token not allowed after '{'");
            }
            if (animNodeHandle == 0) {
                AnimTreeCompileError(
                    "no animation specified for this block");
            }

            childNodeHandle = GetArray(animNodeHandle);
            qboolean childForceLoaded =
                forceLoadedChildren != qfalse ||
                ((propertyFlags & CODUO_SCRIPT_ANIM_PROPERTY_COMPLETE) != 0 &&
                 missingFromTree == qfalse);
            if (AnimTreeParseInternal(
                    childNodeHandle, treeHandle,
                    missingFromTree == qfalse,
                    (propertyFlags & CODUO_SCRIPT_ANIM_PROPERTY_LOOPSYNC) != 0,
                    childForceLoaded) != qfalse) {
                AnimTreeCompileError("unexpected end of file");
            }

            if (GetArraySize(childNodeHandle) == 0) {
                RemoveVariable(parentHandle, animNameHandle);
            } else {
                VariableValue value;

                value.payload = propertyFlags;
                value.type = CODUO_SCRIPT_VAR_INT;
                childNodeHandle = GetArrayVariableBySignedIndex(
                    childNodeHandle, SCRIPT_ANIM_TREE_ROOT_NODE_INDEX);
                SetVariableValue(childNodeHandle, &value);
            }

            animNodeHandle = 0;
            missingFromTree = qfalse;
            continue;
        }

        if (*token == '}') {
            if (token[1] != '\0') {
                AnimTreeCompileError("bad token");
            }
            token = Com_ParseOnLine(&script_animParseState);
            if (*token != '\0') {
                AnimTreeCompileError("token not allowed after '}'");
            }
            hitEndOfFile = qfalse;
            break;
        }

        AnimTreeCompileError("bad token");
    }

    if (missingFromTree != qfalse) {
        RemoveVariable(parentHandle, animNameHandle);
    }

    if (addEmptyVoid != qfalse && GetArraySize(parentHandle) == 0) {
        animNameHandle = SL_GetString_(
            addLoopingVoid != qfalse ? "void_loop" : "void",
            SCRIPT_ANIM_STRING_USER_NONE,
            SCRIPT_ANIM_NAME_TYPE);
        GetVariable(parentHandle, animNameHandle);
        SL_RemoveRefToString(animNameHandle);
    }

    return hitEndOfFile;
}

void Scr_AnimTreeParse(char *source,
                       uint16_t parentHandle,
                       uint16_t treeHandle)
{
    Com_BeginParseSession("Scr_AnimTreeParse");
    script_animParseState = source;
    script_animParseStart = source;
    if (AnimTreeParseInternal(parentHandle, treeHandle, qtrue, qfalse,
                              qfalse) == qfalse) {
        AnimTreeCompileError("bad token");
    }
    Com_EndParseSession();
}

void *Hunk_AllocXAnimPrecache(size_t size)
{
    return Hunk_AllocAlign(size, SCRIPT_ANIM_HUNK_ALIGNMENT);
}

int32_t Scr_GetAnimTreeSize(uint16_t handle)
{
    int32_t nodeCount = 0;

    for (uint16_t child = FindNextSibling(handle);
         child != 0; child = FindNextSibling(child)) {
        uint32_t name = GetVariableName(child);

        if (name < CODUO_SCRIPT_VARIABLE_NODE_COUNT) {
            if (GetVarType(child) == CODUO_SCRIPT_VAR_OBJECT) {
                nodeCount += Scr_GetAnimTreeSize(
                    FindObject(child));
            } else {
                nodeCount++;
            }
        }
    }

    if (nodeCount != 0) {
        nodeCount++;
    }

    return nodeCount;
}

void ConnectScriptToAnim(uint16_t unresolvedRoot,
                         uint16_t nodeIndex,
                         uint16_t treeName,
                         uint16_t animName,
                         int32_t treeIndex)
{
    uint16_t unresolvedHandle;

    unresolvedHandle = FindVariable(unresolvedRoot, animName);
    if (unresolvedHandle == 0) {
        return;
    }

    VariableValue *storedValue = GetVariableValueAddress(unresolvedHandle);
    coduo_script_value_payload_t *head = &storedValue->payload;
    if (*head == 0) {
        const char *treeNameString = SL_ConvertToString(treeName);
        const char *animNameString = SL_ConvertToString(animName);

        Com_Error(SCRIPT_ANIM_OWNER_ERROR_DROP,
                  "\x15"
                  "duplicate animation '%s' in 'animtrees/%s.atr'",
                  animNameString, treeNameString);
    }

    const uint32_t packedAnimRef =
        ((uint32_t)treeIndex << 16) | nodeIndex;
#if UINTPTR_MAX > UINT32_MAX
    script_anim_unresolved_ref_t *nextRef = (script_anim_unresolved_ref_t *)*head;
    while (nextRef != NULL) {
        script_anim_unresolved_ref_t *ref = nextRef;

        nextRef = ref->next;
        memcpy(ref->target, &packedAnimRef, sizeof(packedAnimRef));
    }
#else
    coduo_script_value_payload_t nextRef = *head;
    while (nextRef != 0) {
        char *ref = (char *)nextRef;
        uint32_t nextPayload;

        memcpy(&nextPayload, ref, sizeof(nextPayload));
        nextRef = nextPayload;
        memcpy(ref, &packedAnimRef, sizeof(packedAnimRef));
    }
#endif
    *head = 0;
}

uint32_t Scr_CreateAnimationTree(
    uint16_t sourceHandle,
    uint16_t unresolvedRoot,
    XAnim *tree,
    uint16_t firstChildIndex,
    const char *nodeName,
    uint16_t nodeIndex,
    uint16_t treeName,
    int32_t treeIndex)
{
    uint16_t childCount = 0;
    uint16_t propertyFlags = 0;

    for (uint16_t child = FindNextSibling(sourceHandle);
         child != 0; child = FindNextSibling(child)) {
        if (GetVariableName(child) < CODUO_SCRIPT_VARIABLE_NODE_COUNT) {
            childCount++;
        }
    }

    uint16_t propertyHandle =
        FindArrayVariable(sourceHandle, SCRIPT_ANIM_TREE_ROOT_NODE_INDEX);
    if (propertyHandle != 0) {
        VariableValue *propertyValue =
            GetVariableValueAddress(propertyHandle);
        propertyFlags =
            (uint16_t)propertyValue->payload;
    }

    script_animTreeChecksum =
        script_animTreeChecksum * SCRIPT_ANIM_HASH_MULTIPLIER + nodeIndex;
    script_animTreeChecksum =
        script_animTreeChecksum * SCRIPT_ANIM_HASH_MULTIPLIER + firstChildIndex;
    script_animTreeChecksum =
        script_animTreeChecksum * SCRIPT_ANIM_HASH_MULTIPLIER + childCount;
    script_animTreeChecksum =
        script_animTreeChecksum * SCRIPT_ANIM_HASH_MULTIPLIER + propertyFlags;

    XAnimSetParentNode(tree, nodeIndex, nodeName, firstChildIndex, childCount,
                       propertyFlags);

    uint16_t childNodeIndex = firstChildIndex;
    uint32_t nextFreeNodeIndex = firstChildIndex + childCount;

    for (uint16_t child = FindNextSibling(sourceHandle);
         child != 0; child = FindNextSibling(child)) {
        uint32_t name = GetVariableName(child);

        if (name < CODUO_SCRIPT_VARIABLE_NODE_COUNT) {
            uint16_t animName =
                (uint16_t)name;

            ConnectScriptToAnim(unresolvedRoot, childNodeIndex, treeName,
                                animName, treeIndex);
            if (GetVarType(child) == CODUO_SCRIPT_VAR_OBJECT) {
                const char *animNameString = SL_ConvertToString(animName);
                uint16_t childObject =
                    FindObject(child);

                nextFreeNodeIndex = Scr_CreateAnimationTree(
                    childObject, unresolvedRoot, tree, nextFreeNodeIndex,
                    animNameString, childNodeIndex, treeName, treeIndex);
            } else {
                script_animTreeChecksum =
                    script_animTreeChecksum * SCRIPT_ANIM_HASH_MULTIPLIER +
                    childNodeIndex;
                XAnimSetLeafNode(tree, childNodeIndex,
                                 SL_ConvertToString(animName));
            }
            childNodeIndex++;
        }
    }

    return nextFreeNodeIndex;
}

void Scr_CheckAnimsDefined(uint16_t unresolvedRoot,
                           uint16_t treeName)
{
    for (uint16_t child = FindNextSibling(unresolvedRoot);
         child != 0; child = FindNextSibling(child)) {
        uint16_t animName =
            (uint16_t)GetVariableName(child);
        VariableValue *storedValue = GetVariableValueAddress(child);
        coduo_script_value_payload_t *head = &storedValue->payload;

        if (*head != 0) {
            const char *treeNameString = SL_ConvertToString(treeName);
            const char *animNameString = SL_ConvertToString(animName);
            const char *message =
                va("animation '%s' not defined in anim tree '%s'",
                   animNameString, treeNameString);

#if UINTPTR_MAX > UINT32_MAX
            script_anim_unresolved_ref_t *ref =
                (script_anim_unresolved_ref_t *)*head;
            coduo_script_codepos_t codepos =
                (coduo_script_codepos_t)ref->target;
#else
            coduo_script_codepos_t codepos = (coduo_script_codepos_t)*head;
#endif
            if (Scr_IsInOpcodeMemory(codepos) == qfalse &&
                Scr_IsInDeveloperOpcodeMemory(codepos) == qfalse) {
                Com_Error(SCRIPT_ANIM_OWNER_ERROR_DROP, "\x15%s", message);
            } else {
                CompileError2(codepos, "%s", message);
            }
        }
    }
}

void Scr_PrecacheAnimationTree(uint16_t sourceHandle)
{
    for (uint16_t child = FindNextSibling(sourceHandle);
         child != 0; child = FindNextSibling(child)) {
        uint32_t name = GetVariableName(child);

        if (name < CODUO_SCRIPT_VARIABLE_NODE_COUNT) {
            if (GetVarType(child) == CODUO_SCRIPT_VAR_OBJECT) {
                Scr_PrecacheAnimationTree(
                    FindObject(child));
            } else {
                XAnimLoadFile(SL_ConvertToString(
                                  (uint16_t)name),
                              Hunk_AllocXAnimPrecache);
            }
        }
    }
}

void Scr_UsingTree(const char *animTreeName,
                   uint32_t source)
{
    if (Scr_IsIdentifier(animTreeName) == qfalse) {
        CompileError(source, "bad anim tree name");
        return;
    }

    script_animCurrentUsingTree = Scr_UsingTreeInternal(
        animTreeName, &script_activeAnimTreeHandle);
}

qboolean Scr_LoadAnimTreeInternal(const char *animTreeName,
                                  uint16_t parentHandle,
                                  uint16_t treeHandle)
{
    char filename[SCRIPT_ANIM_TREE_FILENAME_BUFFER_SIZE];
    const char *savedSourcePos;
    const char *savedSourceFilename;

    /* ORIGINAL_BINARY_BUG: 0x0809ce45..0x0809ce5a passes this fixed
     * 64-byte stack buffer to unbounded sprintf without limiting the tree
     * name.  Preserved; see original-bugs/
     * coduomp-loadanimtree-filename-stack-overflow.md. */
    sprintf(filename, "animtrees/%s.atr", animTreeName);
    savedSourcePos = script_sourcePos;

    char *source = Scr_AddSourceBuffer(filename, NULL, NULL);
    if (source == NULL) {
        return qfalse;
    }

    savedSourceFilename = script_sourceFilename;
    script_sourceFilename = filename;
    Scr_AnimTreeParse(source, parentHandle, treeHandle);
    script_sourceFilename = savedSourceFilename;
    script_sourcePos = savedSourcePos;
    Hunk_ClearTempMemoryHigh();

    return GetArraySize(parentHandle) != 0 ? qtrue : qfalse;
}

void Scr_LoadAnimTreeAtIndex(int32_t treeIndex,
                             coduo_script_anim_tree_alloc_fn alloc)
{
    int32_t slot = xanim_activePoolPayloadSlot;
    uint16_t treeRecord = script_animTreeHandles[slot][treeIndex];
    uint16_t treeName = (uint16_t)GetVariableName(treeRecord);
    uint16_t treeData = FindObject(treeRecord);
    uint16_t loadedTreeHandle = FindVariable(
        treeData, SCRIPT_ANIM_TREE_FIRST_CHILD_NODE_INDEX);

    if (loadedTreeHandle != 0) {
        return;
    }

    uint16_t sourceHandle =
        FindVariable(treeData, SCRIPT_ANIM_TREE_ROOT_NODE_INDEX);
    if (sourceHandle == 0) {
        script_animTrees[slot][treeIndex] = 0;
        return;
    }

    uint16_t unresolvedRoot = FindObject(sourceHandle);
    script_animCurrentTreeRoot = Scr_AllocArray();

    if (Scr_LoadAnimTreeInternal(SL_ConvertToString(treeName),
                                 script_animCurrentTreeRoot,
                                 unresolvedRoot) == qfalse) {
        RemoveVariable(script_animTreeRoot, treeName);
        RemoveRefToObject(script_animCurrentTreeRoot);
        script_animCurrentTreeRoot = 0;
        script_animTrees[slot][treeIndex] = 0;
        return;
    }

    int32_t nodeCount = Scr_GetAnimTreeSize(script_animCurrentTreeRoot);
    const char *treeNameString = SL_ConvertToString(treeName);
    void *asset = FS_GetDataForFile("animtrees", treeNameString, ".atr");
    const char *name = asset == NULL ? "(savegame)" : *(const char **)asset;
    XAnim *tree = XAnimAllocTree(name, nodeCount, alloc);
    uint16_t rootName =
        SL_GetString_("root", SCRIPT_ANIM_STRING_USER_NONE,
                            SCRIPT_ANIM_NAME_TYPE);

    ConnectScriptToAnim(unresolvedRoot, SCRIPT_ANIM_TREE_ROOT_NODE_INDEX,
                        treeName, rootName, treeIndex);
    SL_RemoveRefToString(rootName);
    Scr_CreateAnimationTree(
        script_animCurrentTreeRoot, unresolvedRoot, tree,
        SCRIPT_ANIM_TREE_FIRST_CHILD_NODE_INDEX, "root",
        SCRIPT_ANIM_TREE_ROOT_NODE_INDEX, treeName, treeIndex);
    Scr_CheckAnimsDefined(unresolvedRoot, treeName);
    Scr_PrecacheAnimationTree(script_animCurrentTreeRoot);
    RemoveVariable(treeData, SCRIPT_ANIM_TREE_ROOT_NODE_INDEX);
    RemoveRefToObject(script_animCurrentTreeRoot);
    script_animCurrentTreeRoot = 0;

    VariableValue value;
    value.payload = (coduo_script_value_payload_t)tree;
    value.type = CODUO_SCRIPT_VAR_CODEPOS;
    loadedTreeHandle = GetVariable(
        treeData, SCRIPT_ANIM_TREE_FIRST_CHILD_NODE_INDEX);
    SetVariableValue(loadedTreeHandle, &value);
    XAnimSetupSyncNodes(tree);
    script_animTrees[slot][treeIndex] = tree;
}
