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

#include "../core_memory/core_memory_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_serialization_private.h"
#include "script_variable_private.h"

enum {
    SCRIPT_SERIALIZATION_STRING_TYPE = 0x0e,
    SCRIPT_SERIALIZATION_NULL_CODEPOS_OFFSET = -1,
    SCRIPT_SERIALIZATION_MT_PERMANENT = 1,
    SCRIPT_SERIALIZATION_FULL_OBJECT_ID_MAP_BYTES =
        CODUO_SCRIPT_OBJECT_ID_MAP_COUNT * sizeof(uint16_t),
    SCRIPT_SERIALIZATION_VALUE_TYPE_MASK =
        CODUO_SCRIPT_VARIABLE_NODE_TYPE_MASK,
    SCRIPT_SERIALIZATION_PERSISTENT_TYPE_MASK = 0x9f,
    SCRIPT_SERIALIZATION_NAME_SHIFT =
        CODUO_SCRIPT_VARIABLE_NODE_PACKED_INDEX_SHIFT,
    SCRIPT_SERIALIZATION_SHORT_NAME_LIMIT = 0xffff,
    SCRIPT_SERIALIZATION_OBJECT_NAME_LIMIT = 0x1ffff,
    SCRIPT_SERIALIZATION_OBJECT_NAME_BASE = 0x10000,
    SCRIPT_SERIALIZATION_CHILD_SHORT_NAME = 0,
    SCRIPT_SERIALIZATION_CHILD_INT_NAME = 1,
    SCRIPT_SERIALIZATION_CHILD_STRING_NAME = 2,
    SCRIPT_SERIALIZATION_CHILD_OBJECT_NAME = 3
};

extern void *Scr_LoadRead(uint32_t size);
void WriteId(uint16_t object);
uint16_t ReadId(void);
void WriteFloat(float value);
void WriteStack(VariableStackBuffer *frame);
VariableStackBuffer *ReadStack(void);
void ScriptSave_PrepareStack(VariableStackBuffer *frame);
void ScriptSave_PrepareValue(coduo_script_variable_type_t type,
                             coduo_script_value_payload_t payload);
void ScriptSave_PrepareValueObjectRefs(VariableValue *value);
void ScriptSave_WriteValue(coduo_script_variable_type_t type,
                           coduo_script_value_payload_t payload);
void ScriptSave_WriteChildValue(VariableValue *value, uint32_t name,
                                qboolean parentIsArray);
void ScriptLoad_ReadValue(VariableValue *value);
uint32_t ScriptLoad_ReadChildValue(VariableValue *value);
void ScriptSave_PrepareObject(uint16_t object);
void ScriptSave_WriteObjectRecord(uint16_t object);
void ScriptLoad_ReadObjectRecord(uint16_t object);
void ScriptSave_WriteRootValue(void);
void ScriptLoad_ReadRootValue(void);

/* NOT_FROM_ORIGINAL_SOURCE: converts the portable frame's base-relative dword
 * to the original behavior's live code pointer. */
static coduo_script_codepos_t
coduomp_script_stack_codepos_from_original(uint32_t codePos)
{
    return script_codeBase + codePos;
}

/* NOT_FROM_ORIGINAL_SOURCE: packs a live code pointer into the portable
 * frame's base-relative dword carrier. */
static uint32_t
coduomp_script_stack_codepos_to_original(coduo_script_codepos_t codePos)
{
    return (uint32_t)(codePos - script_codeBase);
}

/* NOT_FROM_ORIGINAL_SOURCE: packed script stack entries are byte records. */
static void coduomp_script_stack_read_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)
            coduomp_script_stack_codepos_from_original(payload);
    } else if (value->type == CODUO_SCRIPT_VAR_INT) {
        value->payload = SCRIPT_VALUE_U32_PAYLOAD(payload);
    } else {
        value->payload = (coduo_script_value_payload_t)payload;
    }
}

/* NOT_FROM_ORIGINAL_SOURCE: packed script stack entries are byte records. */
static void coduomp_script_stack_write_entry_value(
    VariableStackBufferValue *entry, const VariableValue *value)
{
    coduo_script_value_payload_t payload;

    if (value->type == CODUO_SCRIPT_VAR_CODEPOS) {
        payload = (coduo_script_value_payload_t)
            coduomp_script_stack_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;
    }

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

/* NOT_FROM_ORIGINAL_SOURCE: bit pattern write for float script values. */
static void
coduomp_script_save_write_float_bits(coduo_script_value_payload_t payload)
{
    float value;

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

void WriteByte(uint8_t value)
{
    uint8_t *out = TempMalloc(sizeof(*out));

    *out = value;
}

uint8_t ReadByte(void)
{
    /* ORIGINAL_BINARY_BUG: stock 0x080a8dda retains no stream end and reads
     * one byte from the serialization cursor without a bounds check. */
    uint8_t value = *script_serializationCursor;

    script_serializationCursor += sizeof(value);
    return value;
}

void WriteShort(uint16_t value)
{
    char *out = (char *)TempMalloc(sizeof(value));

    memcpy(out, &value, sizeof(value));
}

uint16_t ReadShort(void)
{
    uint16_t value;

    /* ORIGINAL_BINARY_BUG: stock 0x080a8e1c reads two bytes through the sole
     * serialization cursor without retaining or checking a stream end. */
    memcpy(&value, script_serializationCursor, sizeof(value));
    script_serializationCursor += sizeof(value);
    return value;
}

void WriteString(uint16_t string)
{
    const char *text = SL_ConvertToString(string);
    size_t size = strlen(text) + 1;
    char *out = (char *)TempMalloc(size);

    strcpy(out, text);
}

uint16_t ReadString(void)
{
    /* ORIGINAL_BINARY_BUG: stock 0x080a8ebc calls strlen on the cursor before
     * any bound or remaining-length check. */
    const char *text = (const char *)script_serializationCursor;
    size_t size = strlen(text) + 1;
    uint16_t string =
        SL_GetStringOfLen(text, 0, size,
                                 SCRIPT_SERIALIZATION_STRING_TYPE);

    script_serializationCursor += size;
    return string;
}

void WriteFloat(float value)
{
    char *out = (char *)TempMalloc(sizeof(value));

    /* Stock VA 0x080a8f32 writes the four argument bytes directly into the
     * packed serialization stream, whose preceding record can be one byte. */
    memcpy(out, &value, sizeof(value));
}

long double ReadFloat(void)
{
    float value;

    /* ORIGINAL_BINARY_BUG: stock 0x080a8f4e loads four bytes without a
     * remaining-length check. */
    memcpy(&value, script_serializationCursor, sizeof(value));
    script_serializationCursor += sizeof(value);
    return (long double)value;
}

void ScriptSave_WriteData(const void *data, size_t length)
{
    void *out = TempMalloc(length);

    Com_Memcpy(out, data, length);
}

void ScriptLoad_ReadData(void *data, size_t length)
{
    /* ORIGINAL_BINARY_BUG: stock 0x080a90f4 retains no stream end before
     * copying a caller-selected dword extent from the cursor. */
    Com_Memcpy(data, script_serializationCursor, length);
    script_serializationCursor += length;
}

void SafeWriteString(uint16_t string)
{
    if (string == 0) {
        WriteByte(qfalse);
        return;
    }

    WriteByte(qtrue);
    WriteString(string);
}

uint16_t SafeReadString(void)
{
    if (ReadByte() == qfalse) {
        return 0;
    }

    return ReadString();
}

void WriteVector(const vec3_t vector)
{
    WriteFloat(vector[0]);
    WriteFloat(vector[1]);
    WriteFloat(vector[2]);
}

coduo_script_value_payload_t ReadVector(void)
{
    vec3_t vector;

    vector[0] = (float)ReadFloat();
    vector[1] = (float)ReadFloat();
    vector[2] = (float)ReadFloat();
    return AllocVectorCopy(vector);
}

void WriteInt(int32_t value)
{
    char *out = (char *)TempMalloc(sizeof(value));

    /* Stock VA 0x080a8fd2 likewise emits an unaligned four-byte cell. */
    memcpy(out, &value, sizeof(value));
}

int32_t ReadInt(void)
{
    int32_t value;

    /* ORIGINAL_BINARY_BUG: stock 0x080a8fee consumes a dword without a
     * remaining-length check. */
    ScriptLoad_ReadData(&value, sizeof(value));
    return value;
}

void WriteCodepos(coduo_script_codepos_t codePos)
{
    int32_t offset = SCRIPT_SERIALIZATION_NULL_CODEPOS_OFFSET;

    if (codePos != NULL) {
        offset = (int32_t)(codePos - script_codeBase);
    }

    WriteInt(offset);
}

coduo_script_codepos_t ReadCodepos(void)
{
    int32_t offset = ReadInt();

    /* ORIGINAL_BINARY_BUG: stock 0x080a903a accepts every nonnegative offset
     * without checking the script-code allocation extent. */
    if (offset < 0) {
        return NULL;
    }

    return script_codeBase + offset;
}

void WriteId(uint16_t object)
{
    WriteShort(script_variableToObjectId[object]);
}

uint16_t ReadId(void)
{
    /* ORIGINAL_BINARY_BUG: stock 0x080a9098 indexes the object-id map with
     * the serialized word without comparing it to the saved object count. */
    uint16_t objectId = ReadShort();
    uint16_t object =
        script_objectIdToVariable[objectId];

    AddRefToObject(object);
    return object;
}

uint16_t
Scr_ConvertThreadToSave(uint16_t thread)
{
    if (thread == 0) {
        return 0;
    }

    ScriptSave_PrepareObject(thread);
    return script_variableToObjectId[thread];
}

uint16_t
Scr_ConvertThreadFromLoad(uint16_t objectId)
{
    if (objectId == 0) {
        return 0;
    }

    uint16_t thread = script_objectIdToVariable[objectId];

    AddRefToObject(thread);
    return thread;
}

void WriteStack(VariableStackBuffer *frame)
{
    WriteShort(frame->size);
    WriteInt((int32_t)frame->time);
    WriteCodepos(
        coduomp_script_stack_codepos_from_original(frame->pos));
    WriteId(frame->localId);

    VariableStackBufferValue *entry = frame->buf;
    for (uint16_t count = frame->size; count != 0; --count) {
        VariableValue value;

        coduomp_script_stack_read_entry_value(entry, &value);
        ScriptSave_WriteValue(value.type, value.payload);
        entry++;
    }
}

VariableStackBuffer *ReadStack(void)
{
    /* ORIGINAL_BINARY_BUG: stock 0x080a9249 trusts the serialized entry count
     * and retains no stream end for the following value loop. */
    uint16_t entryCount = ReadShort();
    int32_t frameSize =
        (int32_t)offsetof(VariableStackBuffer, buf) +
        (int32_t)entryCount * (int32_t)sizeof(VariableStackBufferValue);
    VariableStackBuffer *frame =
        MT_Alloc(frameSize, SCRIPT_SERIALIZATION_MT_PERMANENT);

    frame->size = entryCount;
    frame->time = (uint32_t)ReadInt();
    frame->pos =
        coduomp_script_stack_codepos_to_original(ReadCodepos());
    frame->localId = ReadId();

    VariableStackBufferValue *entry = frame->buf;
    for (uint16_t count = entryCount; count != 0; --count) {
        VariableValue value;

        ScriptLoad_ReadValue(&value);
        coduomp_script_stack_write_entry_value(entry, &value);
        entry++;
    }

    return frame;
}

void ScriptSave_PrepareStack(VariableStackBuffer *frame)
{
    ScriptSave_PrepareObject(frame->localId);

    VariableStackBufferValue *entry = frame->buf;
    for (uint16_t count = frame->size; count != 0; --count) {
        VariableValue value;

        coduomp_script_stack_read_entry_value(entry, &value);
        ScriptSave_PrepareValue(value.type, value.payload);
        entry++;
    }
}

void ScriptSave_PrepareValue(coduo_script_variable_type_t type,
                             coduo_script_value_payload_t payload)
{
    if (type == CODUO_SCRIPT_VAR_OBJECT) {
        ScriptSave_PrepareObject((uint16_t)payload);
        return;
    }

    if (type == CODUO_SCRIPT_VAR_STACK) {
        ScriptSave_PrepareStack(
            (VariableStackBuffer *)payload);
    }
}

void ScriptSave_PrepareValueObjectRefs(VariableValue *value)
{
    ScriptSave_PrepareValue(
        (coduo_script_variable_type_t)(value->type &
                                       SCRIPT_SERIALIZATION_VALUE_TYPE_MASK),
        value->payload);
}

void ScriptSave_WriteValue(coduo_script_variable_type_t type,
                           coduo_script_value_payload_t payload)
{
    WriteByte((uint8_t)type);

    switch (type & SCRIPT_SERIALIZATION_VALUE_TYPE_MASK) {
    case CODUO_SCRIPT_VAR_STRING:
    case CODUO_SCRIPT_VAR_LOCALIZED_STRING:
        WriteString((uint16_t)payload);
        break;
    case CODUO_SCRIPT_VAR_VECTOR:
        WriteVector((const float *)payload);
        break;
    case CODUO_SCRIPT_VAR_FLOAT:
        coduomp_script_save_write_float_bits(payload);
        break;
    case CODUO_SCRIPT_VAR_INT:
        WriteInt((int32_t)payload);
        break;
    case CODUO_SCRIPT_VAR_FUNCTION:
    case CODUO_SCRIPT_VAR_CODEPOS:
        WriteCodepos((coduo_script_codepos_t)payload);
        break;
    case CODUO_SCRIPT_VAR_ANIMATION:
        WriteInt((int32_t)payload);
        break;
    case CODUO_SCRIPT_VAR_OBJECT:
        WriteId((uint16_t)payload);
        break;
    case CODUO_SCRIPT_VAR_STACK:
        WriteStack((VariableStackBuffer *)payload);
        break;
    default:
        break;
    }
}

void ScriptSave_WriteChildValue(VariableValue *value, uint32_t name,
                                qboolean parentIsArray)
{
    ScriptSave_WriteValue(value->type, value->payload);

    if (parentIsArray != qfalse &&
        name <= SCRIPT_SERIALIZATION_OBJECT_NAME_LIMIT) {
        if (name <= SCRIPT_SERIALIZATION_SHORT_NAME_LIMIT) {
            WriteByte(SCRIPT_SERIALIZATION_CHILD_STRING_NAME);
            WriteString((uint16_t)name);
        } else {
            WriteByte(SCRIPT_SERIALIZATION_CHILD_OBJECT_NAME);
            WriteId((uint16_t)name);
        }
        return;
    }

    if (name <= SCRIPT_SERIALIZATION_SHORT_NAME_LIMIT) {
        WriteByte(SCRIPT_SERIALIZATION_CHILD_SHORT_NAME);
        WriteShort((uint16_t)name);
        return;
    }

    WriteByte(SCRIPT_SERIALIZATION_CHILD_INT_NAME);
    WriteInt((int32_t)name);
}

void ScriptLoad_ReadValue(VariableValue *value)
{
    value->type = (coduo_script_variable_type_t)ReadByte();

    switch (value->type & SCRIPT_SERIALIZATION_VALUE_TYPE_MASK) {
    case CODUO_SCRIPT_VAR_STRING:
    case CODUO_SCRIPT_VAR_LOCALIZED_STRING:
        value->payload = ReadString();
        break;
    case CODUO_SCRIPT_VAR_VECTOR:
        value->payload = ReadVector();
        break;
    case CODUO_SCRIPT_VAR_FLOAT: {
        float floatValue = (float)ReadFloat();

        value->payload = 0;
        memcpy(&value->payload, &floatValue, sizeof(floatValue));
        break;
    }
    case CODUO_SCRIPT_VAR_INT:
        value->payload = SCRIPT_VALUE_INT_PAYLOAD(ReadInt());
        break;
    case CODUO_SCRIPT_VAR_FUNCTION:
    case CODUO_SCRIPT_VAR_CODEPOS:
        value->payload = (coduo_script_value_payload_t)ReadCodepos();
        break;
    case CODUO_SCRIPT_VAR_ANIMATION:
        value->payload = SCRIPT_VALUE_U32_PAYLOAD(ReadInt());
        break;
    case CODUO_SCRIPT_VAR_OBJECT:
        value->payload = ReadId();
        break;
    case CODUO_SCRIPT_VAR_STACK:
        value->payload = (coduo_script_value_payload_t)ReadStack();
        break;
    default:
        /* ORIGINAL_BINARY_BUG: stock 0x080a95c5..0x080a9637 accepts unknown
         * type tags while leaving the destination payload untouched. */
        break;
    }
}

uint32_t ScriptLoad_ReadChildValue(VariableValue *value)
{
    ScriptLoad_ReadValue(value);

    switch ((int32_t)ReadByte()) {
    case SCRIPT_SERIALIZATION_CHILD_SHORT_NAME:
        return ReadShort();
    case SCRIPT_SERIALIZATION_CHILD_INT_NAME:
        return (uint32_t)ReadInt();
    case SCRIPT_SERIALIZATION_CHILD_STRING_NAME:
        return ReadString();
    case SCRIPT_SERIALIZATION_CHILD_OBJECT_NAME:
        return SCRIPT_SERIALIZATION_OBJECT_NAME_BASE +
               ReadId();
    default:
        return 0;
    }
}

void ScriptSave_PrepareObject(uint16_t object)
{
    if (script_variableToObjectId[object] != 0) {
        return;
    }

    script_savedObjectCount++;
    script_variableToObjectId[object] = script_savedObjectCount;
    script_objectIdToVariable[script_savedObjectCount] = object;

    coduo_script_variable_node_t *objectNode = &script_variableNodes[object];
    uint32_t packedTypeIndex = objectNode->packedTypeIndex;
    qboolean parentIsArray =
        ((packedTypeIndex & SCRIPT_SERIALIZATION_VALUE_TYPE_MASK) ==
         CODUO_SCRIPT_VAR_ARRAY)
            ? qtrue
            : qfalse;

    for (uint16_t child =
             FindNextSibling(object);
         child != 0; child = FindNextSibling(child)) {
        coduo_script_variable_node_t *childNode = &script_variableNodes[child];

        if (parentIsArray != qfalse) {
            uint32_t name =
                childNode->packedTypeIndex >> SCRIPT_SERIALIZATION_NAME_SHIFT;
            if (name > SCRIPT_SERIALIZATION_SHORT_NAME_LIMIT &&
                name <= SCRIPT_SERIALIZATION_OBJECT_NAME_LIMIT) {
                ScriptSave_PrepareObject((uint16_t)name);
            }
        }

        VariableValue childValue;

        childValue.payload = childNode->payload.valuePayload;
        childValue.type = (coduo_script_variable_type_t)
            childNode->packedTypeIndex;
        ScriptSave_PrepareValueObjectRefs(&childValue);
    }

    if ((packedTypeIndex & SCRIPT_SERIALIZATION_VALUE_TYPE_MASK) ==
        CODUO_SCRIPT_VAR_THREAD) {
        ScriptSave_PrepareObject(objectNode->payload.halves.parentHandle);
    }
}

void ScriptSave_WriteObjectRecord(uint16_t object)
{
    coduo_script_variable_node_t *objectNode = &script_variableNodes[object];
    coduo_script_variable_type_t savedType =
        (coduo_script_variable_type_t)(objectNode->packedTypeIndex &
                                       SCRIPT_SERIALIZATION_PERSISTENT_TYPE_MASK);

    WriteByte((uint8_t)savedType);

    if ((savedType & SCRIPT_SERIALIZATION_VALUE_TYPE_MASK) ==
        CODUO_SCRIPT_VAR_THREAD) {
        WriteId(objectNode->payload.halves.parentHandle);
        SafeWriteString(
            (uint16_t)(
                objectNode->packedTypeIndex >> SCRIPT_SERIALIZATION_NAME_SHIFT));
    } else if ((savedType & SCRIPT_SERIALIZATION_VALUE_TYPE_MASK) ==
                   CODUO_SCRIPT_VAR_ENTITY ||
               (savedType & SCRIPT_SERIALIZATION_VALUE_TYPE_MASK) ==
                   CODUO_SCRIPT_VAR_DEAD_ENTITY) {
        WriteShort(objectNode->payload.halves.parentHandle);
        WriteShort((uint16_t)(objectNode->packedTypeIndex >>
                                         SCRIPT_SERIALIZATION_NAME_SHIFT));
    }

    qboolean parentIsArray =
        ((objectNode->packedTypeIndex & SCRIPT_SERIALIZATION_VALUE_TYPE_MASK) ==
         CODUO_SCRIPT_VAR_ARRAY)
            ? qtrue
            : qfalse;
    uint16_t childCount = 0;
    for (uint16_t child =
             FindNextSibling(object);
         child != 0; child = FindNextSibling(child)) {
        childCount++;
    }

    WriteShort(childCount);

    for (uint16_t child =
             FindNextSibling(object);
         child != 0; child = FindNextSibling(child)) {
        coduo_script_variable_node_t *childNode = &script_variableNodes[child];
        VariableValue value;

        value.payload = childNode->payload.valuePayload;
        value.type = (coduo_script_variable_type_t)(
            childNode->packedTypeIndex &
            SCRIPT_SERIALIZATION_PERSISTENT_TYPE_MASK);
        ScriptSave_WriteChildValue(
            &value,
            childNode->packedTypeIndex >> SCRIPT_SERIALIZATION_NAME_SHIFT,
            parentIsArray);
    }
}

void ScriptLoad_ReadObjectRecord(uint16_t object)
{
    coduo_script_variable_node_t *objectNode = &script_variableNodes[object];
    coduo_script_variable_type_t savedType =
        (coduo_script_variable_type_t)ReadByte();

    objectNode->packedTypeIndex &=
        ~(uint32_t)SCRIPT_SERIALIZATION_VALUE_TYPE_MASK;
    objectNode->packedTypeIndex |= savedType;

    if ((savedType & SCRIPT_SERIALIZATION_VALUE_TYPE_MASK) ==
        CODUO_SCRIPT_VAR_THREAD) {
        objectNode->payload.halves.parentHandle = ReadId();
        objectNode->packedTypeIndex |=
            (uint32_t)SafeReadString()
            << SCRIPT_SERIALIZATION_NAME_SHIFT;
    } else if ((savedType & SCRIPT_SERIALIZATION_VALUE_TYPE_MASK) ==
                   CODUO_SCRIPT_VAR_ENTITY ||
               (savedType & SCRIPT_SERIALIZATION_VALUE_TYPE_MASK) ==
                   CODUO_SCRIPT_VAR_DEAD_ENTITY) {
        objectNode->payload.halves.parentHandle = ReadShort();
        objectNode->packedTypeIndex |=
            (uint32_t)ReadShort()
            << SCRIPT_SERIALIZATION_NAME_SHIFT;
    } else if ((savedType & SCRIPT_SERIALIZATION_VALUE_TYPE_MASK) ==
               CODUO_SCRIPT_VAR_ARRAY) {
        objectNode->payload.halves.parentHandle = 0;
    }

    qboolean parentIsArray =
        ((savedType & SCRIPT_SERIALIZATION_VALUE_TYPE_MASK) ==
         CODUO_SCRIPT_VAR_ARRAY)
            ? qtrue
            : qfalse;
    uint16_t childCount = ReadShort();
    for (uint16_t index = 0; index < childCount; ++index) {
        VariableValue value;
        uint32_t name = ScriptLoad_ReadChildValue(&value);
        uint16_t childIndirection =
            GetVariableIndexInternal(object, name);
        uint16_t child =
            script_variableIndirections[childIndirection].valueIndex;
        coduo_script_variable_node_t *childNode =
            &script_variableNodes[child];

        if (parentIsArray != qfalse) {
            if (name <= SCRIPT_SERIALIZATION_SHORT_NAME_LIMIT) {
                SL_RemoveRefToString((uint16_t)name);
            } else if (name <= SCRIPT_SERIALIZATION_OBJECT_NAME_LIMIT) {
                RemoveRefToObject((uint16_t)name);
            }
        }

        childNode->packedTypeIndex |= value.type;
        childNode->payload.valuePayload = value.payload;
    }
}

void Scr_SavePre(void)
{
    script_variableToObjectId =
        Hunk_AllocateTempMemoryHighInternal(
            SCRIPT_SERIALIZATION_FULL_OBJECT_ID_MAP_BYTES);
    script_objectIdToVariable =
        Hunk_AllocateTempMemoryHighInternal(
            SCRIPT_SERIALIZATION_FULL_OBJECT_ID_MAP_BYTES);
    Com_Memset(script_variableToObjectId, 0,
               SCRIPT_SERIALIZATION_FULL_OBJECT_ID_MAP_BYTES);
    script_savedObjectCount = 0;

    ScriptSave_PrepareObject(script_levelHandle);
    ScriptSave_PrepareObject(script_animHandle);
    ScriptSave_PrepareObject(script_timeArrayHandle);
    ScriptSave_PrepareObject(script_pauseArrayHandle);

    for (uint32_t index = 0; index < script_entityTypeUsageCount; ++index) {
        uint16_t entityType =
            FindObject(
                FindVariable(script_entityTypeClassMapRoot, index));
        ScriptSave_PrepareObject(entityType);

        uint16_t classType =
            FindObject(
                FindVariable(script_classMapRoot, index));
        ScriptSave_PrepareObject(classType);
    }

    coduo_script_variable_node_t *notifyNode =
        &script_variableNodes[script_gameHandle];
    VariableValue notifyValue;

    notifyValue.payload = notifyNode->payload.valuePayload;
    notifyValue.type =
        (coduo_script_variable_type_t)notifyNode->packedTypeIndex;
    ScriptSave_PrepareValueObjectRefs(&notifyValue);
}

void ScriptSave_WriteRootValue(void)
{
    coduo_script_variable_node_t *node =
        &script_variableNodes[script_gameHandle];

    ScriptSave_WriteValue(
        (coduo_script_variable_type_t)(node->packedTypeIndex &
                                       SCRIPT_SERIALIZATION_PERSISTENT_TYPE_MASK),
        node->payload.valuePayload);
}

void ScriptLoad_ReadRootValue(void)
{
    script_gameHandle = Scr_AllocArray();

    VariableValue value;
    ScriptLoad_ReadValue(&value);

    script_variableNodes[script_gameHandle].packedTypeIndex |=
        value.type;
    script_variableNodes[script_gameHandle].payload.valuePayload =
        value.payload;
}

void Scr_SavePost(void)
{
    WriteInt((int32_t)script_currentTimeKey);
    WriteShort(script_savedObjectCount);

    for (uint32_t objectId = 1; objectId <= script_savedObjectCount;
         ++objectId) {
        ScriptSave_WriteObjectRecord(script_objectIdToVariable[objectId]);
    }

    ScriptSave_WriteRootValue();
    WriteId(script_levelHandle);
    WriteId(script_animHandle);
    WriteId(script_timeArrayHandle);
    WriteId(script_pauseArrayHandle);

    for (uint32_t index = 0; index < script_entityTypeUsageCount; ++index) {
        uint16_t entityType =
            FindObject(
                FindVariable(script_entityTypeClassMapRoot, index));
        WriteId(entityType);
    }
}

void Scr_SaveShutdown(void)
{
    Hunk_ClearTempMemoryHigh();
}

void Scr_LoadPre(void)
{
    script_serializationCursor = Scr_LoadRead(sizeof(uint32_t));
    uint32_t saveDataSize;

    memcpy(&saveDataSize, script_serializationCursor, sizeof(saveDataSize));
    script_serializationCursor = Scr_LoadRead(saveDataSize);

    script_currentTimeKey =
        (uint32_t)ReadInt();
    script_savedObjectCount = ReadShort();
    script_variableToObjectId =
        Hunk_AllocateTempMemory(
            SCRIPT_SERIALIZATION_FULL_OBJECT_ID_MAP_BYTES);
    script_objectIdToVariable =
        Hunk_AllocateTempMemory(
            ((size_t)script_savedObjectCount + 1) *
            (size_t)sizeof(script_objectIdToVariable[0]));

    for (uint32_t objectId = 1; objectId <= script_savedObjectCount;
         ++objectId) {
        uint16_t object = AllocObject();

        script_objectIdToVariable[objectId] = object;
        script_variableToObjectId[object] = (uint16_t)objectId;
    }

    for (uint32_t objectId = 1; objectId <= script_savedObjectCount;
         ++objectId) {
        ScriptLoad_ReadObjectRecord(script_objectIdToVariable[objectId]);
    }

    ScriptLoad_ReadRootValue();
    script_levelHandle = ReadId();
    script_animHandle = ReadId();
    script_timeArrayHandle = ReadId();
    script_pauseArrayHandle = ReadId();

    for (uint32_t index = 0; index < script_entityTypeUsageCount; ++index) {
        VariableValue value;

        value.type = CODUO_SCRIPT_VAR_OBJECT;
        value.payload = ReadId();

        uint16_t classEntry =
            FindVariable(script_entityTypeClassMapRoot, index);
        SetVariableValue(classEntry, &value);
    }
}

void Scr_LoadShutdown(void)
{
    for (uint32_t objectId = 1; objectId <= script_savedObjectCount;
         ++objectId) {
        RemoveRefToObject(script_objectIdToVariable[objectId]);
    }

    Hunk_FreeTempMemory(script_objectIdToVariable);
    Hunk_FreeTempMemory(script_variableToObjectId);
}
