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

#include "coduo_engine_structs.h"
#include "script_memory_private.h"
#include "script_runtime_private.h"
#include "script_variable_private.h"

enum {
    SCRIPT_NOTIFY_WAIT_KEY = 0x20000,
    SCRIPT_NOTIFY_STACK_KEY = 0x20001,
    SCRIPT_NOTIFY_VARIABLE_TYPE_MASK = 0x1f,
    SCRIPT_NOTIFY_TIME_KEY_MASK = 0x00ffffff,
    SCRIPT_NOTIFY_MT_TAG = 1
};

extern uint16_t Scr_GetEntityId(
    int32_t entityNum, int32_t classNum);
extern uint16_t FindEntityId(
    int32_t entityNum, int32_t classNum);
extern void RuntimeError(coduo_script_codepos_t codePos,
                                               int32_t sourcePosOffset,
                                               const char *message,
                                               const char *detail);
extern const char *SL_ConvertToString(uint16_t stringValue);

extern uint8_t *script_codeBase;

void KillThread(uint16_t objectHandle);
void VM_Terminate(uint16_t objectHandle);
void VM_CancelNotify(uint16_t objectHandle,
                     uint16_t waitThread);
void VM_TerminateStack(uint16_t objectHandle);
void VM_TerminateNotifyList(uint16_t objectHandle);
void VM_TerminateTime(uint16_t listHandle);
extern uint16_t
VM_Execute(VariableValue *stackTop, uint8_t *codePos,
           uint16_t thread,
           uint16_t currentObject,
           VariableValue *stackBase);

static coduo_script_codepos_t
coduomp_script_notify_codepos_from_original(uint32_t codepos);
static uint32_t coduomp_script_notify_codepos_to_original(coduo_script_codepos_t codepos);

/* NOT_FROM_ORIGINAL_SOURCE: shared spelling for the original dead-thread write. */
static void coduomp_script_notify_mark_dead_thread(
    uint16_t objectHandle)
{
    script_variableNodes[objectHandle].packedTypeIndex &=
        ~(uint32_t)SCRIPT_NOTIFY_VARIABLE_TYPE_MASK;
    script_variableNodes[objectHandle].packedTypeIndex |=
        CODUO_SCRIPT_VAR_DEAD_THREAD;
}

/* NOT_FROM_ORIGINAL_SOURCE: reads one packed saved-stack value from a frame. */
static void coduomp_script_notify_read_frame_value(
    const VariableStackBufferValue *frameValue, VariableValue *value)
{
    coduo_script_value_payload_t payload;

    value->type = (coduo_script_variable_type_t)frameValue->type;
    memcpy(&payload, frameValue->payload, sizeof(payload));
    if (value->type == CODUO_SCRIPT_VAR_CODEPOS) {
        value->payload = (coduo_script_value_payload_t)
            coduomp_script_notify_codepos_from_original((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: writes one packed saved-stack value into a frame. */
static void coduomp_script_notify_write_frame_value(VariableStackBufferValue *frameValue,
                                         const VariableValue *value)
{
    coduo_script_value_payload_t payload;

    if (value->type == CODUO_SCRIPT_VAR_CODEPOS) {
        payload = (coduo_script_value_payload_t)coduomp_script_notify_codepos_to_original(
            (coduo_script_codepos_t)value->payload);
    } else if (value->type == CODUO_SCRIPT_VAR_INT) {
        payload = SCRIPT_VALUE_U32_PAYLOAD(value->payload);
    } else {
        payload = value->payload;
    }

    frameValue->type = (uint8_t)value->type;
    memcpy(frameValue->payload, &payload, sizeof(payload));
}

/* NOT_FROM_ORIGINAL_SOURCE: native runtime expansion for original 32-bit code offsets. */
static coduo_script_codepos_t
coduomp_script_notify_codepos_from_original(uint32_t codepos)
{
    return script_codeBase + codepos;
}

/* NOT_FROM_ORIGINAL_SOURCE: native runtime packing for original 32-bit code offsets. */
static uint32_t coduomp_script_notify_codepos_to_original(coduo_script_codepos_t codepos)
{
    return (uint32_t)(codepos - script_codeBase);
}

/* NOT_FROM_ORIGINAL_SOURCE: shared cleanup for original notify bucket removal. */
static void coduomp_script_notify_remove_waiter_from_notify_bucket(
    uint16_t objectHandle,
    uint16_t waitRoot,
    uint16_t notifyBucket,
    uint16_t notifyName,
    uint16_t waitThread)
{
    RemoveObjectVariable(notifyBucket, waitThread);
    if (GetArraySize(notifyBucket) != 0) {
        return;
    }

    RemoveVariable(waitRoot, notifyName);
    if (GetArraySize(waitRoot) == 0) {
        RemoveVariable(objectHandle, SCRIPT_NOTIFY_WAIT_KEY);
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: shared cleanup for original pause bucket removal. */
static void coduomp_script_notify_remove_waiter_from_pause_bucket(
    uint16_t parentHandle,
    uint16_t pauseBucket,
    uint16_t waitThread)
{
    RemoveObjectVariable(pauseBucket, waitThread);
    if (GetArraySize(pauseBucket) == 0) {
        RemoveObjectVariable(script_pauseArrayHandle, parentHandle);
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: frame size expression for packed notify frames. */
static int32_t coduomp_script_notify_frame_size(uint32_t valueCount)
{
    return (int32_t)(offsetof(VariableStackBuffer, buf) +
                     valueCount * sizeof(VariableStackBufferValue));
}

/* NOT_FROM_ORIGINAL_SOURCE: implements the original waittill/waittillmatch check. */
static qboolean coduomp_script_notify_frame_matches_args(
    VariableStackBuffer *frame,
    const VariableValue *argTop,
    qboolean *appendArgs)
{
    uint8_t *codePos = coduomp_script_notify_codepos_from_original(frame->pos);

    if (codePos[-1] != 'N') {
        *appendArgs = argTop->type != CODUO_SCRIPT_VAR_CODEPOS ? qtrue : qfalse;
        return qtrue;
    }

    /* ORIGINAL_BINARY_BUG: stock VM_Notify sign-extends this emitted byte.
     * Counts 128..255 therefore start beyond the packed saved frame and keep
     * traversing until the signed countdown wraps back to zero. */
    int32_t remaining = (int8_t)codePos[0];
    const int32_t initialCount = remaining;
    const VariableStackBufferValue *frameValue =
        frame->buf +
        (frame->size - remaining);
    const VariableValue *arg = argTop;

    while (remaining != 0) {
        if (arg->type == CODUO_SCRIPT_VAR_CODEPOS) {
            return qfalse;
        }

        remaining--;

        VariableValue savedValue;
        coduomp_script_notify_read_frame_value(frameValue, &savedValue);
        frameValue++;

        if (savedValue.type == CODUO_SCRIPT_VAR_CODEPOS) {
            frame->pos = coduomp_script_notify_codepos_to_original(codePos + 1);
            *appendArgs = qfalse;
            return qtrue;
        }

        ScriptValue_AddRefValue(&savedValue);
        VariableValue notifyValue = *arg;
        ScriptValue_AddRefValue(&notifyValue);

        if (CheckEquality(&savedValue, &notifyValue) == qfalse) {
            RuntimeError(
                coduomp_script_notify_codepos_from_original(frame->pos),
                initialCount - remaining + 2,
                script_errorMessage, script_errorSource);
            script_errorMessage = NULL;
            script_errorSource = NULL;
            return qfalse;
        }

        if (savedValue.payload == qfalse) {
            return qfalse;
        }

        arg--;
    }

    frame->pos = coduomp_script_notify_codepos_to_original(codePos + 1);
    *appendArgs = qfalse;
    return qtrue;
}

/* NOT_FROM_ORIGINAL_SOURCE: appends notify args into a suspended frame. */
static VariableStackBuffer *coduomp_script_notify_append_args_to_frame(
    VariableStackBuffer *frame, VariableValue *stackBase,
    VariableValue *argTop)
{
    uint32_t oldCount = frame->size;
    uint32_t appendCount = (uint32_t)(argTop - stackBase);
    uint32_t newCount = oldCount + appendCount;
    size_t oldSize = (size_t)coduomp_script_notify_frame_size(oldCount);
    size_t newSize = (size_t)coduomp_script_notify_frame_size(newCount);

    if (MT_Realloc(oldSize, newSize) == qfalse) {
        VariableStackBuffer *newFrame =
            MT_Alloc(newSize, SCRIPT_NOTIFY_MT_TAG);

        newFrame->time = frame->time;
        newFrame->pos = frame->pos;
        newFrame->size = frame->size;
        newFrame->localId = frame->localId;
        memcpy(newFrame->buf, frame->buf,
               oldCount * sizeof(frame->buf[0]));
        MT_Free(frame, oldSize);
        frame = newFrame;
    }

    frame->size = (uint16_t)newCount;
    VariableStackBufferValue *frameValue = frame->buf + oldCount;
    for (VariableValue *value = stackBase + 1; value <= argTop;
         ++value) {
        ScriptValue_AddRefValue(value);
        coduomp_script_notify_write_frame_value(frameValue, value);
        frameValue++;
    }

    return frame;
}

void VM_ArchiveStack(
    int32_t valueCount, coduo_script_codepos_t codepos,
    uint16_t threadHandle,
    uint16_t objectHandle,
    VariableValue *valueBeforeArgs, uint32_t resumeTimeKey)
{
    size_t frameSize = (size_t)coduomp_script_notify_frame_size((uint32_t)valueCount);
    VariableStackBuffer *frame =
        MT_Alloc(frameSize, SCRIPT_NOTIFY_MT_TAG);

    frame->time = resumeTimeKey;
    frame->pos = coduomp_script_notify_codepos_to_original(codepos);
    frame->size = (uint16_t)valueCount;
    frame->localId = threadHandle;
    AddRefToObject(objectHandle);

    VariableStackBufferValue *frameValue = frame->buf;
    script_callStackDepth--;
    VariableValue *value = valueBeforeArgs + 1;
    while (valueCount != 0) {
        valueCount--;
        if (value->type == CODUO_SCRIPT_VAR_CODEPOS) {
            script_callStackDepth--;
        }

        coduomp_script_notify_write_frame_value(frameValue, value);
        frameValue++;
        value++;
    }

    VariableValue stackValue;
    stackValue.type = CODUO_SCRIPT_VAR_STACK;
    stackValue.payload = (coduo_script_value_payload_t)frame;

    uint16_t stackSlot =
        GetVariable(objectHandle, SCRIPT_NOTIFY_STACK_KEY);
    SetNewVariableValue(stackSlot, &stackValue);
}

void VM_Notify(uint16_t objectHandle,
               uint16_t notifyName,
               VariableValue *argTop)
{
    uint16_t waitSlot =
        FindVariable(objectHandle, SCRIPT_NOTIFY_WAIT_KEY);
    if (waitSlot == 0) {
        return;
    }

    uint16_t waitRoot =
        FindObject(waitSlot);
    uint16_t notifySlot =
        FindVariable(waitRoot, notifyName);
    if (notifySlot == 0) {
        return;
    }

    uint16_t notifyBucket =
        FindObject(notifySlot);
    AddRefToObject(notifyBucket);

    for (uint16_t child =
             FindNextSibling(notifyBucket);
         child != 0; child = FindNextSibling(child)) {
        uint16_t waitThread =
            GetVariableKeyObject(child);
        uint16_t parentHandle =
            GetSelf(waitThread);
        uint16_t pauseSlot =
            FindObjectVariable(script_pauseArrayHandle,
                                           parentHandle);
        uint16_t pauseBucket =
            FindObject(pauseSlot);
        uint16_t stackSlot =
            FindVariable(waitThread, SCRIPT_NOTIFY_STACK_KEY);

        if (stackSlot == 0) {
            ClearThreadNotifyName(waitThread);
            AddRefToObject(waitThread);
            coduomp_script_notify_remove_waiter_from_notify_bucket(
                objectHandle, waitRoot, notifyBucket, notifyName, waitThread);
            coduomp_script_notify_remove_waiter_from_pause_bucket(parentHandle, pauseBucket,
                                                     waitThread);
            AddRefToObject(parentHandle);
            KillThread(waitThread);
            RemoveRefToObject(waitThread);
            /* 0x080ae735 invokes scheduler-aware termination for the parent;
             * only the no-stack waiter at 0x080ae71d uses KillThread. */
            VM_Terminate(parentHandle);
            RemoveRefToObject(parentHandle);
            child = notifyBucket;
            continue;
        }

        VariableValue *stackValue = GetVariableValueAddress(stackSlot);
        VariableStackBuffer *frame =
            (VariableStackBuffer *)stackValue->payload;
        qboolean appendArgs;

        if (coduomp_script_notify_frame_matches_args(
                frame, argTop, &appendArgs) ==
            qfalse) {
            continue;
        }

        ClearThreadNotifyName(waitThread);
        coduomp_script_notify_remove_waiter_from_notify_bucket(objectHandle, waitRoot,
                                                  notifyBucket, notifyName,
                                                  waitThread);

        frame->time = script_currentTimeKey;
        uint16_t timeSlot =
            GetVariable(script_timeArrayHandle,
                                    script_currentTimeKey);
        uint16_t timeBucket =
            GetArray(timeSlot);
        GetObjectVariable(timeBucket, waitThread);

        coduomp_script_notify_remove_waiter_from_pause_bucket(parentHandle, pauseBucket,
                                                 waitThread);

        if (appendArgs != qfalse) {
            VariableValue *stackBase = argTop;
            while (stackBase->type != CODUO_SCRIPT_VAR_CODEPOS) {
                stackBase--;
            }

            frame = coduomp_script_notify_append_args_to_frame(frame, stackBase, argTop);
            stackValue->payload = (coduo_script_value_payload_t)frame;
        }

        child = notifyBucket;
    }

    RemoveRefToObject(notifyBucket);
}

void Scr_NotifyId(uint16_t objectHandle,
                           uint16_t notifyName,
                           uint32_t paramCount)
{
    while (script_parameterCount != 0) {
        ScriptValue_ReleaseValue(script_valueStackTop);
        script_valueStackTop--;
        script_parameterCount--;
    }

    VariableValue *stackBase = &script_valueStackTop[-(int32_t)paramCount];
    uint32_t savedDepth =
        script_valueStackDepth - paramCount;
    coduo_script_variable_type_t savedType = stackBase->type;

    stackBase->type = CODUO_SCRIPT_VAR_CODEPOS;
    script_valueStackDepth = 0;
    VM_Notify(objectHandle, notifyName, script_valueStackTop);
    stackBase->type = savedType;

    while (script_valueStackTop != stackBase) {
        ScriptValue_ReleaseValue(script_valueStackTop);
        script_valueStackTop--;
    }

    script_valueStackDepth = savedDepth;
}

void Scr_NotifyNum(int32_t entityNum, int32_t classNum,
                            uint16_t notifyName,
                            uint32_t paramCount)
{
    while (script_parameterCount != 0) {
        ScriptValue_ReleaseValue(script_valueStackTop);
        script_valueStackTop--;
        script_parameterCount--;
    }

    VariableValue *stackBase = &script_valueStackTop[-(int32_t)paramCount];
    uint32_t savedDepth =
        script_valueStackDepth - paramCount;
    uint16_t objectHandle =
        FindEntityId(entityNum, classNum);

    if (objectHandle != 0) {
        coduo_script_variable_type_t savedType = stackBase->type;

        stackBase->type = CODUO_SCRIPT_VAR_CODEPOS;
        script_valueStackDepth = 0;
        VM_Notify(objectHandle, notifyName, script_valueStackTop);
        stackBase->type = savedType;
    }

    while (script_valueStackTop != stackBase) {
        ScriptValue_ReleaseValue(script_valueStackTop);
        script_valueStackTop--;
    }

    script_valueStackDepth = savedDepth;
}

void VM_Resume(uint16_t listHandle)
{
    Scr_ResetTimeout();
    AddRefToObject(listHandle);

    for (;;) {
        uint16_t child =
            FindNextSibling(listHandle);
        if (child == 0) {
            break;
        }

        uint16_t objectHandle =
            GetVariableKeyObject(child);
        RemoveObjectVariable(listHandle, objectHandle);

        uint16_t stackSlot =
            FindVariable(objectHandle, SCRIPT_NOTIFY_STACK_KEY);
        VariableValue *stackValue = GetVariableValueAddress(stackSlot);
        VariableStackBuffer *frame =
            (VariableStackBuffer *)stackValue->payload;

        RemoveVariable(objectHandle, SCRIPT_NOTIFY_STACK_KEY);

        uint32_t savedValueCount = frame->size;
        uint32_t valueCount = savedValueCount;
        coduo_script_codepos_t codepos =
            coduomp_script_notify_codepos_from_original(frame->pos);
        uint16_t threadHandle = frame->localId;
        const VariableStackBufferValue *frameValue = frame->buf;
        VariableValue *stackTop = &script_valueStack[0];

        while (valueCount != 0) {
            stackTop++;
            valueCount--;
            coduomp_script_notify_read_frame_value(frameValue, stackTop);
            frameValue++;

            if (stackTop->type == CODUO_SCRIPT_VAR_CODEPOS) {
                script_callStackCodepos[script_callStackDepth] =
                    (coduo_script_codepos_t)stackTop->payload;
                script_callStackDepth++;
            }
        }

        script_callStackCodepos[script_callStackDepth] = codepos;
        script_callStackDepth++;

        MT_Free(frame, coduomp_script_notify_frame_size(savedValueCount));

        uint16_t parentHandle =
            GetSelf(threadHandle);
        if (IsFieldObject(parentHandle) == qfalse) {
            for (;;) {
                KillThread(threadHandle);
                RemoveRefToObject(threadHandle);

                while (stackTop->type != CODUO_SCRIPT_VAR_CODEPOS) {
                    ScriptValue_ReleaseValue(stackTop);
                    stackTop--;
                }

                script_callStackDepth--;
                if (stackTop == &script_valueStack[0]) {
                    break;
                }

                threadHandle =
                    (uint16_t)stackTop[-1].payload;
                stackTop -= 2;
            }
        } else {
            uint16_t result =
                VM_Execute(stackTop, codepos, threadHandle, objectHandle,
                           &script_valueStack[0]);
            RemoveRefToObject(result);
            ScriptValue_ReleaseValue(&script_valueStack[1]);
        }
    }

    RemoveRefToObject(listHandle);
    ClearVariableValue(script_tempValueHandle);
}

void Scr_InitSystem(uint32_t unused, uint32_t time)
{
    (void)unused;

    script_timeArrayHandle = AllocObject();
    script_pauseArrayHandle = Scr_AllocArray();
    script_levelHandle = AllocObject();
    script_animHandle = AllocObject();
    script_currentTimeKey =
        (uint32_t)(time & SCRIPT_NOTIFY_TIME_KEY_MASK);
}

void Scr_ShutdownSystem(uint8_t unused)
{
    (void)unused;

    for (uint16_t child =
             FindNextSibling(script_timeArrayHandle);
         child != 0; child = FindNextSibling(child)) {
        uint16_t threadList =
            FindObject(child);
        VM_TerminateTime(threadList);
    }

    while (FindNextSibling(script_pauseArrayHandle) != 0) {
        uint16_t pauseSlot =
            FindNextSibling(script_pauseArrayHandle);
        uint16_t pauseBucket =
            FindObject(pauseSlot);
        uint16_t waiterSlot =
            FindNextSibling(pauseBucket);
        VariableValue *waiterValue = GetVariableValueAddress(waiterSlot);
        uint16_t objectHandle =
            (uint16_t)waiterValue->payload;

        AddRefToObject(objectHandle);
        VM_TerminateNotifyList(objectHandle);
        RemoveRefToObject(objectHandle);
    }

    ClearObject(script_levelHandle);
    RemoveRefToObject(script_levelHandle);
    script_levelHandle = 0;

    ClearObject(script_animHandle);
    RemoveRefToObject(script_animHandle);
    script_animHandle = 0;

    ClearObject(script_timeArrayHandle);
    RemoveRefToObject(script_timeArrayHandle);
    script_timeArrayHandle = 0;

    RemoveRefToObject(script_pauseArrayHandle);
    script_pauseArrayHandle = 0;
}

void VM_TerminateStackInternal(uint16_t objectHandle,
                               VariableStackBuffer *frame)
{
    RemoveVariable(objectHandle, SCRIPT_NOTIFY_STACK_KEY);
    RemoveRefToObject(objectHandle);
    KillThread(frame->localId);
    RemoveRefToObject(frame->localId);

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

    while (remaining != 0) {
        coduo_script_variable_type_t type =
            (coduo_script_variable_type_t)frameValue[-1].type;

        if (type == CODUO_SCRIPT_VAR_CODEPOS) {
            VariableValue priorValue;

            coduomp_script_notify_read_frame_value(
                frameValue - 2,
                &priorValue);
            frameValue -= 2;
            remaining -= 2;
            KillThread(
                (uint16_t)priorValue.payload);
            RemoveRefToObject(
                (uint16_t)priorValue.payload);
            continue;
        }

        VariableValue value;
        coduomp_script_notify_read_frame_value(frameValue - 1,
                                    &value);
        ScriptValue_ReleaseValue(&value);
        remaining--;
        frameValue--;
    }

    MT_Free(frame, (size_t)coduomp_script_notify_frame_size(frame->size));
}

void VM_TerminateStack(uint16_t objectHandle)
{
    uint16_t stackSlot =
        FindVariable(objectHandle, SCRIPT_NOTIFY_STACK_KEY);

    if (stackSlot == 0) {
        KillThread(objectHandle);
        RemoveRefToObject(objectHandle);
        return;
    }

    VariableValue *stackValue = GetVariableValueAddress(stackSlot);
    VM_TerminateStackInternal(
        objectHandle, (VariableStackBuffer *)stackValue->payload);
}

void VM_CancelNotify(uint16_t objectHandle,
                     uint16_t waitThread)
{
    uint16_t notifyKey =
        GetThreadNotifyName(waitThread);

    ClearThreadNotifyName(waitThread);

    uint16_t waitRoot =
        FindVariable(objectHandle, SCRIPT_NOTIFY_WAIT_KEY);
    waitRoot = FindObject(waitRoot);

    uint16_t notifyBucket =
        FindVariable(waitRoot, notifyKey);
    notifyBucket = FindObject(notifyBucket);

    RemoveObjectVariable(notifyBucket, waitThread);
    if (GetArraySize(notifyBucket) == 0) {
        RemoveVariable(waitRoot, notifyKey);
        if (GetArraySize(waitRoot) == 0) {
            RemoveVariable(objectHandle, SCRIPT_NOTIFY_WAIT_KEY);
        }
    }
}

void KillThread(uint16_t objectHandle)
{
    AddRefToObject(objectHandle);
    ClearObjectInternal(objectHandle);
    RemoveRefToObject(script_variableNodes[objectHandle]
                               .payload.halves.parentHandle);
    uint16_t parentHandle =
        GetSelf(objectHandle);

    uint16_t pauseSlot =
        FindObjectVariable(script_pauseArrayHandle, objectHandle);
    if (pauseSlot != 0) {
        uint16_t pauseBucket =
            FindObject(pauseSlot);

        for (;;) {
            uint16_t child =
                FindNextSibling(pauseBucket);
            if (child == 0) {
                break;
            }

            uint16_t waitingThread =
                GetVariableKeyObject(child);
            uint16_t waitingSlot =
                FindObjectVariable(pauseBucket, waitingThread);
            VariableValue *waitingValue =
                GetVariableValueAddress(waitingSlot);

            VM_CancelNotify(
                (uint16_t)waitingValue->payload,
                waitingThread);
            coduomp_script_notify_mark_dead_thread(waitingThread);
            RemoveObjectVariable(pauseBucket, waitingThread);
            RemoveRefToObject(objectHandle);
        }

        RemoveObjectVariable(script_pauseArrayHandle,
                                        objectHandle);
    }

    if (GetThreadNotifyName(objectHandle) != 0) {
        uint16_t parentPauseSlot =
            FindObjectVariable(script_pauseArrayHandle,
                                          parentHandle);
        uint16_t parentPauseBucket =
            FindObject(parentPauseSlot);
        uint16_t waitingSlot =
            FindObjectVariable(parentPauseBucket, objectHandle);
        VariableValue *waitingValue = GetVariableValueAddress(waitingSlot);

        VM_CancelNotify(
            (uint16_t)waitingValue->payload,
            objectHandle);
        RemoveObjectVariable(parentPauseBucket, objectHandle);
        if (GetArraySize(parentPauseBucket) == 0) {
            RemoveObjectVariable(script_pauseArrayHandle,
                                            parentHandle);
        }
    }

    coduomp_script_notify_mark_dead_thread(objectHandle);
    RemoveRefToObject(objectHandle);
}

void VM_Terminate(uint16_t objectHandle)
{
    uint16_t stackSlot =
        FindVariable(objectHandle, SCRIPT_NOTIFY_STACK_KEY);

    if (stackSlot == 0) {
        return;
    }

    VariableValue *stackValue = GetVariableValueAddress(stackSlot);
    VariableStackBuffer *frame =
        (VariableStackBuffer *)stackValue->payload;
    uint16_t parentHandle =
        GetSelf(objectHandle);
    uint16_t pauseSlot =
        FindObjectVariable(script_pauseArrayHandle, parentHandle);

    if (pauseSlot != 0) {
        uint16_t pauseBucket =
            FindObject(pauseSlot);
        uint16_t waitingSlot =
            FindObjectVariable(pauseBucket, objectHandle);

        if (waitingSlot != 0) {
            VariableValue *waitingValue =
                GetVariableValueAddress(waitingSlot);

            VM_CancelNotify(
                (uint16_t)waitingValue->payload,
                objectHandle);
            AddRefToObject(objectHandle);
            RemoveObjectVariable(pauseBucket, objectHandle);
            if (GetArraySize(pauseBucket) == 0) {
                RemoveObjectVariable(script_pauseArrayHandle,
                                     parentHandle);
            }

            VM_TerminateStackInternal(objectHandle, frame);
            return;
        }
    }

    uint32_t resumeTimeKey = frame->time;
    uint16_t timeSlot =
        FindVariable(script_timeArrayHandle, resumeTimeKey);
    uint16_t timeBucket =
        FindObject(timeSlot);

    AddRefToObject(objectHandle);
    RemoveObjectVariable(timeBucket, objectHandle);
    if (GetArraySize(timeBucket) == 0 &&
        resumeTimeKey != script_currentTimeKey) {
        RemoveVariable(script_timeArrayHandle, resumeTimeKey);
    }

    VM_TerminateStackInternal(objectHandle, frame);
}

void VM_TerminateNotifyList(uint16_t objectHandle)
{
    for (;;) {
        uint16_t waitSlot =
            FindVariable(objectHandle, SCRIPT_NOTIFY_WAIT_KEY);

        if (waitSlot == 0) {
            return;
        }

        uint16_t waitRoot =
            FindObject(waitSlot);
        uint16_t notifySlot =
            FindNextSibling(waitRoot);

        if (notifySlot == 0) {
            return;
        }

        uint16_t notifyBucket =
            FindObject(notifySlot);
        uint16_t waiterSlot =
            FindNextSibling(notifyBucket);

        if (waiterSlot == 0) {
            return;
        }

        uint16_t waiterObject =
            GetVariableKeyObject(waiterSlot);
        AddRefToObject(waiterObject);
        VM_TerminateStack(waiterObject);
    }
}

void VM_TerminateTime(uint16_t listHandle)
{
    AddRefToObject(listHandle);

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

        if (child == 0) {
            break;
        }

        uint16_t objectHandle =
            GetVariableKeyObject(child);
        AddRefToObject(objectHandle);
        RemoveObjectVariable(listHandle, objectHandle);
        VM_TerminateStack(objectHandle);
    }

    RemoveRefToObject(listHandle);
}
