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

#include "../core_runtime/core_runtime_private.h"
#include "coduo_engine_structs.h"
#include "script_compile_private.h"
#include "script_source_positions_private.h"

enum {
    SCRIPT_CODEGEN_MODE_RECORD_STRING_FIXUPS = 1,
    SCRIPT_CODEGEN_MODE_RELEASE_STRINGS = 2,
    SCRIPT_STRING_USAGE_CASE = 1,
    SCRIPT_OPCODE_RETURN_VALUE = 0x01,
    SCRIPT_OPCODE_WAITTILL_END = 0x1e,
    SCRIPT_OPCODE_WAIT = 0x23,
    SCRIPT_OPCODE_JUMP_ON_FALSE = 0x35,
    SCRIPT_OPCODE_JUMP_ON_TRUE_BACK = 0x36,
    SCRIPT_OPCODE_JUMP_FORWARD = 0x39,
    SCRIPT_OPCODE_JUMP_BACK = 0x3a,
    SCRIPT_OPCODE_INC = 0x3b,
    SCRIPT_OPCODE_DEC = 0x3c,
    SCRIPT_OPCODE_WAITTILLMATCH = 0x4e,
    SCRIPT_OPCODE_WAITTILL = 0x4f,
    SCRIPT_OPCODE_NOTIFY = 0x50,
    SCRIPT_OPCODE_ENDON = 0x51,
    SCRIPT_OPCODE_PUSH_CODEPOS = 0x52,
    SCRIPT_OPCODE_SWITCH_JUMP = 0x53,
    SCRIPT_OPCODE_SWITCH_TABLE = 0x54,
    SCRIPT_CASE_INTEGER_TEST_BIAS = 0x7e0000U,
    SCRIPT_CASE_INTEGER_TEST_LIMIT = 0xfe0000U,
    SCRIPT_CASE_INTEGER_ENCODE_BIAS = 0x800000U,
    SCRIPT_CASE_INTEGER_ENCODE_MASK = 0xffffffU
};

typedef struct script_loop_patch_state_s {
    uint8_t breakAllowed;
    uint8_t breakAllowedInDeveloperBlock;
    script_code_offset_patch_t *breakPatchList;
    uint8_t continueAllowed;
    uint8_t continueAllowedInDeveloperBlock;
    script_code_offset_patch_t *continuePatchList;
} script_loop_patch_state_t;

/* The stock i386 bytecode table consists of packed 8-byte rows: a four-byte
 * case value followed immediately by a four-byte code reference. Native
 * builds widen only the code-reference payload, so the row remains packed as
 * 4 + sizeof(coduo_script_value_payload_t). Payload bytes are emitted with
 * memcpy and are never dereferenced through an unaligned pointer. */
#pragma pack(push, 1)
typedef struct script_switch_case_table_entry_s {
    uint32_t value;
    coduo_script_value_payload_t codeOffset;
} script_switch_case_table_entry_t;
#pragma pack(pop)

typedef struct script_switch_patch_state_s {
    uint8_t caseAllowed;
    uint8_t caseAllowedInDeveloperBlock;
    script_switch_case_record_t *caseList;
    uint8_t breakAllowed;
    uint8_t breakAllowedInDeveloperBlock;
    script_code_offset_patch_t *breakPatchList;
} script_switch_patch_state_t;

void EmitStatement(coduo_scr_ast_node_t *node);

qboolean IsUndefinedPrimitiveExpression(coduo_scr_ast_node_t *node)
{
    return node->kind == SCR_AST_KIND_DEFERRED_CHECK ? qtrue : qfalse;
}

qboolean IsUndefinedExpression(coduo_scr_ast_node_t *node)
{
    if (node->kind == SCR_AST_KIND_PRIMITIVE_EXPRESSION) {
        return IsUndefinedPrimitiveExpression(node->payload.child.node);
    }

    return qfalse;
}

void EmitClearVariableExpression(coduo_scr_ast_node_t *node)
{
    switch (node->kind) {
    case SCR_AST_KIND_STRING_REF:
        EmitClearLocalVariable(
            SCR_AST_STRING_HANDLE(node->payload.stringRef.string));
        break;
    case SCR_AST_KIND_OBJECT_INDEX_OBJECT_REF:
        EmitClearArrayVariable(
            node->payload.objectIndexObjectRef.objectNode,
            node->payload.objectIndexObjectRef.indexNode,
            node->payload.objectIndexObjectRef.objectSourcePos,
            node->payload.objectIndexObjectRef.indexSourcePos);
        break;
    case SCR_AST_KIND_OBJECT_STRING_REF:
        EmitClearFieldVariable(
            node->payload.objectStringRef.objectNode,
            SCR_AST_STRING_HANDLE(node->payload.objectStringRef.string),
            node->payload.objectStringRef.sourcePos,
            node->payload.objectStringRef.opcodeSourcePos);
        break;
    }
}

void EmitAssignmentStatement(coduo_scr_ast_node_t *refNode,
                             coduo_scr_ast_node_t *valueNode,
                             uint32_t sourcePos)
{
    if (IsUndefinedExpression(valueNode) == qfalse) {
        EmitExpression(valueNode);
        EmitVariableExpressionRef(refNode);
        EmitSetVariableField(sourcePos);
        return;
    }

    EmitClearVariableExpression(refNode);
}

void EmitExpressionStatement(coduo_scr_ast_node_t *node)
{
    EmitCallExpression(node, qtrue);
}

void EmitReturnStatement(coduo_scr_ast_node_t *node)
{
    EmitExpression(node);
    EmitOpcode(SCRIPT_OPCODE_RETURN_VALUE, -1, 0);
}

void EmitEndStatement(void)
{
    EmitOpcode(0, 0, 0);
}

void EmitWaitStatement(coduo_scr_ast_node_t *timeNode,
                       uint32_t timeSourcePos,
                       uint32_t opcodeSourcePos)
{
    if (script_codegenMode != 0) {
        CompileError(opcodeSourcePos,
                            "wait not allowed in /# ... #/ comment");
    }

    EmitExpression(timeNode);
    EmitOpcode(SCRIPT_OPCODE_WAIT, -1, 0);
    AddOpcodePos(opcodeSourcePos);
    AddOpcodePos(timeSourcePos);
}

/* NOT_FROM_ORIGINAL_SOURCE: local factoring for repeated jump placeholder code. */
static char *coduomp_script_emit_forward_jump_patch(
    uint8_t opcode, int32_t stackDelta, uint32_t sourcePos)
{
    EmitOpcode(opcode, stackDelta, 0);
    AddOpcodePos(sourcePos);
    EmitInteger(0);

    return (char *)script_codeEmitCursor;
}

/* NOT_FROM_ORIGINAL_SOURCE: local factoring for bytecode jump fixups. */
static void coduomp_script_patch_jump_from_dword(char *patch)
{
    char *current = (char *)TempMalloc(0);
    const int32_t offset =
        (int32_t)(current - patch) - (int32_t)sizeof(offset);

    memcpy(patch, &offset, sizeof(offset));
}

void EmitIfStatement(coduo_scr_ast_node_t *conditionNode,
                     coduo_scr_ast_node_t *bodyNode,
                     uint32_t sourcePos)
{
    EmitExpression(conditionNode);
    char *falsePatch = coduomp_script_emit_forward_jump_patch(
        SCRIPT_OPCODE_JUMP_ON_FALSE, -1, sourcePos);

    EmitStatement(bodyNode);
    EmitNOP();
    coduomp_script_patch_jump_from_dword(falsePatch);
}

void EmitIfElseStatement(coduo_scr_ast_node_t *conditionNode,
                         coduo_scr_ast_node_t *thenNode,
                         coduo_scr_ast_node_t *elseNode,
                         uint32_t sourcePos)
{
    EmitExpression(conditionNode);
    char *falsePatch = coduomp_script_emit_forward_jump_patch(
        SCRIPT_OPCODE_JUMP_ON_FALSE, -1, sourcePos);

    EmitStatement(thenNode);
    EmitOpcode(SCRIPT_OPCODE_JUMP_FORWARD, 0, 0);
    EmitInteger(0);
    char *endPatch = (char *)script_codeEmitCursor;

    coduomp_script_patch_jump_from_dword(falsePatch);
    EmitStatement(elseNode);
    EmitNOP();

    char *current = (char *)TempMalloc(0);
    const int32_t endOffset = (int32_t)(current - endPatch);
    memcpy(endPatch, &endOffset, sizeof(endOffset));
}

/* NOT_FROM_ORIGINAL_SOURCE: preserves nested loop break/continue state. */
static void coduomp_script_save_loop_patch_state(
    script_loop_patch_state_t *state)
{
    state->breakAllowed = script_breakAllowed;
    state->breakAllowedInDeveloperBlock = script_breakAllowedInDeveloperBlock;
    state->breakPatchList = script_breakPatchList;
    state->continueAllowed = script_continueAllowed;
    state->continueAllowedInDeveloperBlock =
        script_continueAllowedInDeveloperBlock;
    state->continuePatchList = script_continuePatchList;
}

/* NOT_FROM_ORIGINAL_SOURCE: preserves machine-code loop state sequencing. */
static void coduomp_script_clear_loop_patch_state(void)
{
    script_breakAllowed = qfalse;
    script_breakAllowedInDeveloperBlock = qfalse;
    script_continueAllowed = qfalse;
    script_continueAllowedInDeveloperBlock = qfalse;
}

/* NOT_FROM_ORIGINAL_SOURCE: preserves machine-code loop state sequencing. */
static void coduomp_script_enable_loop_patch_state(void)
{
    script_breakAllowed = qtrue;
    script_breakAllowedInDeveloperBlock =
        script_codegenMode != 0 ? qtrue : qfalse;
    script_breakPatchList = NULL;
    script_continueAllowed = qtrue;
    script_continueAllowedInDeveloperBlock =
        script_codegenMode != 0 ? qtrue : qfalse;
    script_continuePatchList = NULL;
}

/* NOT_FROM_ORIGINAL_SOURCE: preserves nested loop break/continue state. */
static void coduomp_script_restore_loop_patch_state(
    const script_loop_patch_state_t *state)
{
    script_breakAllowed = state->breakAllowed;
    script_breakAllowedInDeveloperBlock = state->breakAllowedInDeveloperBlock;
    script_breakPatchList = state->breakPatchList;
    script_continueAllowed = state->continueAllowed;
    script_continueAllowedInDeveloperBlock =
        state->continueAllowedInDeveloperBlock;
    script_continuePatchList = state->continuePatchList;
}

/* NOT_FROM_ORIGINAL_SOURCE: shared setup for loop bodies with break/continue. */
static void coduomp_script_enable_loop_body_patches(void)
{
    coduomp_script_enable_loop_patch_state();
}

/* NOT_FROM_ORIGINAL_SOURCE: shared teardown for loop bodies with break/continue. */
static void coduomp_script_disable_loop_body_patches(void)
{
    coduomp_script_clear_loop_patch_state();
}

void EmitWhileStatement(coduo_scr_ast_node_t *conditionNode,
                        coduo_scr_ast_node_t *bodyNode,
                        uint32_t conditionSourcePos,
                        uint32_t loopSourcePos)
{
    EmitNOP();

    script_loop_patch_state_t loopState;
    coduomp_script_save_loop_patch_state(&loopState);
    coduomp_script_clear_loop_patch_state();

    uint8_t *loopStart = TempMalloc(0);
    EmitExpression(conditionNode);
    char *falsePatch = coduomp_script_emit_forward_jump_patch(
        SCRIPT_OPCODE_JUMP_ON_FALSE, -1, conditionSourcePos);

    coduomp_script_enable_loop_patch_state();
    EmitStatement(bodyNode);
    coduomp_script_clear_loop_patch_state();
    ConnectContinueStatements();

    EmitOpcode(SCRIPT_OPCODE_JUMP_BACK, 0, 0);
    AddOpcodePos(loopSourcePos);
    uint8_t *jumpPos = TempMalloc(0);
    EmitInteger((int32_t)(loopStart - jumpPos));

    coduomp_script_patch_jump_from_dword(falsePatch);
    ConnectBreakStatements();
    coduomp_script_restore_loop_patch_state(&loopState);
}

void EmitDoWhileStatement(coduo_scr_ast_node_t *bodyNode,
                          coduo_scr_ast_node_t *conditionNode,
                          uint32_t conditionSourcePos,
                          uint32_t loopSourcePos)
{
    EmitNOP();

    script_loop_patch_state_t loopState;
    coduomp_script_save_loop_patch_state(&loopState);
    coduomp_script_enable_loop_body_patches();

    uint8_t *loopStart = TempMalloc(0);
    EmitStatement(bodyNode);
    coduomp_script_disable_loop_body_patches();
    ConnectContinueStatements();
    EmitExpression(conditionNode);
    EmitOpcode(SCRIPT_OPCODE_JUMP_ON_TRUE_BACK, -1, 0);
    AddOpcodePos(loopSourcePos);
    AddOpcodePos(conditionSourcePos);
    uint8_t *jumpPos = TempMalloc(0);
    EmitInteger((int32_t)(loopStart - jumpPos));
    ConnectBreakStatements();
    coduomp_script_restore_loop_patch_state(&loopState);
}

void EmitForStatement(coduo_scr_ast_node_t *initNode,
                      coduo_scr_ast_node_t *conditionNode,
                      coduo_scr_ast_node_t *incrementNode,
                      coduo_scr_ast_node_t *bodyNode,
                      uint32_t conditionSourcePos,
                      uint32_t loopSourcePos)
{
    script_loop_patch_state_t loopState;
    coduomp_script_save_loop_patch_state(&loopState);
    coduomp_script_clear_loop_patch_state();

    EmitStatement(initNode);
    EmitNOP();
    uint8_t *loopStart = TempMalloc(0);

    char *falsePatch = NULL;
    if (conditionNode->kind == SCR_AST_KIND_FOR_CONDITION) {
        EmitExpression(conditionNode->payload.child.node);
        falsePatch = coduomp_script_emit_forward_jump_patch(
            SCRIPT_OPCODE_JUMP_ON_FALSE, -1, conditionSourcePos);
    }

    coduomp_script_enable_loop_body_patches();
    EmitStatement(bodyNode);
    coduomp_script_disable_loop_body_patches();
    ConnectContinueStatements();
    EmitStatement(incrementNode);
    EmitOpcode(SCRIPT_OPCODE_JUMP_BACK, 0, 0);
    AddOpcodePos(conditionSourcePos);
    AddOpcodePos(loopSourcePos);
    uint8_t *jumpPos = TempMalloc(0);
    EmitInteger((int32_t)(loopStart - jumpPos));

    if (falsePatch != NULL) {
        coduomp_script_patch_jump_from_dword(falsePatch);
    }

    ConnectBreakStatements();
    coduomp_script_restore_loop_patch_state(&loopState);
}

void EmitIncStatement(coduo_scr_ast_node_t *refNode,
                      uint32_t sourcePos)
{
    EmitVariableExpressionRef(refNode);
    EmitOpcode(SCRIPT_OPCODE_INC, 1, 0);
    AddOpcodePos(sourcePos);
    EmitSetVariableField(sourcePos);
}

void EmitDecStatement(coduo_scr_ast_node_t *refNode,
                      uint32_t sourcePos)
{
    EmitVariableExpressionRef(refNode);
    EmitOpcode(SCRIPT_OPCODE_DEC, 1, 0);
    AddOpcodePos(sourcePos);
    EmitSetVariableField(sourcePos);
}

void EmitFormalParameterListRefInternal(coduo_scr_ast_list_item_t *item)
{
    for (item = item->next; item != NULL; item = item->next) {
        EmitSafeSetVariableField(item->stringEntry->string,
                                 item->stringEntry->sourcePos);
    }
}

void EmitFormalWaittillParameterListRefInternal(
    coduo_scr_ast_list_item_t *item)
{
    for (item = item->next; item != NULL; item = item->next) {
        EmitSafeSetWaittillVariableField(item->stringEntry->string,
                                         item->stringEntry->sourcePos);
    }
}

void EmitWaittillStatement(coduo_scr_ast_node_t *objectNode,
                           coduo_scr_ast_list_t *list,
                           uint32_t objectSourcePos,
                           uint32_t opcodeSourcePos)
{
    if (script_codegenMode != 0) {
        CompileError(opcodeSourcePos,
                            "waittill not allowed in developer script");
    }

    coduo_scr_ast_list_item_t *eventItem = list->head->next;
    EmitExpression(eventItem->entry->node);
    EmitPrimitiveExpression(objectNode);
    EmitOpcode(SCRIPT_OPCODE_WAITTILL, -2, 0);
    AddOpcodePos(opcodeSourcePos);
    AddOpcodePos(eventItem->entry->sourcePos);
    AddOpcodePos(objectSourcePos);
    EmitFormalWaittillParameterListRefInternal(eventItem);
    EmitOpcode(SCRIPT_OPCODE_WAITTILL_END, 0, 0);
}

void EmitWaittillmatchStatement(coduo_scr_ast_node_t *objectNode,
                                coduo_scr_ast_list_t *list,
                                uint32_t objectSourcePos,
                                uint32_t opcodeSourcePos)
{
    if (script_codegenMode != 0) {
        CompileError(opcodeSourcePos,
                            "waittillmatch not allowed in developer script");
    }

    coduo_scr_ast_list_item_t *eventItem = list->head->next;
    uint32_t matchCount = 0;
    for (coduo_scr_ast_list_item_t *item = eventItem->next; item != NULL;
         item = item->next) {
        EmitExpression(item->entry->node);
        matchCount++;
    }

    EmitExpression(eventItem->entry->node);
    EmitPrimitiveExpression(objectNode);
    EmitOpcode(SCRIPT_OPCODE_WAITTILLMATCH,
               -2 - (int32_t)matchCount, 0);
    AddOpcodePos(opcodeSourcePos);
    AddOpcodePos(eventItem->entry->sourcePos);
    AddOpcodePos(objectSourcePos);
    for (coduo_scr_ast_list_item_t *item = eventItem->next; item != NULL;
         item = item->next) {
        AddOpcodePos(item->entry->sourcePos);
    }
    /* ORIGINAL_BINARY_BUG: the full unchecked count narrows to one byte;
     * VM_Notify later sign-extends it, so counts 128..255 go negative. */
    EmitByte((uint8_t)matchCount);
    EmitOpcode(SCRIPT_OPCODE_WAITTILL_END, 0, 0);
}

void EmitNotifyStatement(coduo_scr_ast_node_t *objectNode,
                         coduo_scr_ast_list_t *list,
                         uint32_t objectSourcePos,
                         uint32_t opcodeSourcePos)
{
    EmitOpcode(SCRIPT_OPCODE_PUSH_CODEPOS, 1, 0);

    int32_t argumentCount = 0;
    coduo_scr_ast_list_item_t *lastItem = NULL;
    for (coduo_scr_ast_list_item_t *item = list->head; item != NULL;
         item = item->next) {
        lastItem = item;
        EmitExpression(item->entry->node);
        argumentCount++;
    }

    EmitPrimitiveExpression(objectNode);
    EmitOpcode(SCRIPT_OPCODE_NOTIFY, -2 - argumentCount, 0);
    AddOpcodePos(opcodeSourcePos);
    AddOpcodePos(lastItem->entry->sourcePos);
    AddOpcodePos(objectSourcePos);
}

void EmitEndOnStatement(coduo_scr_ast_node_t *objectNode,
                        coduo_scr_ast_node_t *eventNode,
                        uint32_t objectSourcePos,
                        uint32_t opcodeSourcePos)
{
    EmitExpression(eventNode);
    EmitPrimitiveExpression(objectNode);
    EmitOpcode(SCRIPT_OPCODE_ENDON, -2, 0);
    AddOpcodePos(opcodeSourcePos);
    AddOpcodePos(objectSourcePos);
}

/* NOT_FROM_ORIGINAL_SOURCE: preserves nested switch/case and break state. */
static void coduomp_script_save_switch_patch_state(
    script_switch_patch_state_t *state)
{
    state->caseAllowed = script_caseAllowed;
    state->caseAllowedInDeveloperBlock = script_caseAllowedInDeveloperBlock;
    state->caseList = script_caseRecordList;
    state->breakAllowed = script_breakAllowed;
    state->breakAllowedInDeveloperBlock = script_breakAllowedInDeveloperBlock;
    state->breakPatchList = script_breakPatchList;
}

/* NOT_FROM_ORIGINAL_SOURCE: preserves machine-code switch state sequencing. */
static void coduomp_script_clear_switch_patch_state(void)
{
    script_caseAllowed = qfalse;
    script_caseAllowedInDeveloperBlock = qfalse;
    script_breakAllowed = qfalse;
    script_breakAllowedInDeveloperBlock = qfalse;
}

/* NOT_FROM_ORIGINAL_SOURCE: preserves machine-code switch state sequencing. */
static void coduomp_script_enable_switch_patch_state(void)
{
    script_caseAllowed = qtrue;
    script_caseAllowedInDeveloperBlock =
        script_codegenMode != 0 ? qtrue : qfalse;
    script_caseRecordList = NULL;
    script_breakAllowed = qtrue;
    script_breakAllowedInDeveloperBlock =
        script_codegenMode != 0 ? qtrue : qfalse;
    script_breakPatchList = NULL;
}

/* NOT_FROM_ORIGINAL_SOURCE: preserves nested switch/case and break state. */
static void coduomp_script_restore_switch_patch_state(
    const script_switch_patch_state_t *state)
{
    script_caseAllowed = state->caseAllowed;
    script_caseAllowedInDeveloperBlock = state->caseAllowedInDeveloperBlock;
    script_caseRecordList = state->caseList;
    script_breakAllowed = state->breakAllowed;
    script_breakAllowedInDeveloperBlock = state->breakAllowedInDeveloperBlock;
    script_breakPatchList = state->breakPatchList;
}

int CompareCaseInfo(const void *left, const void *right)
{
    const script_switch_case_table_entry_t *leftCase = left;
    const script_switch_case_table_entry_t *rightCase = right;

    if (rightCase->value < leftCase->value) {
        return -1;
    }
    if (leftCase->value < rightCase->value) {
        return 1;
    }
    return 0;
}

void EmitCaseStatementInfo(uint32_t value, uint32_t sourcePos)
{
    if (script_codegenMode == SCRIPT_CODEGEN_MODE_RELEASE_STRINGS) {
        return;
    }

    script_switch_case_record_t *record =
        Hunk_AllocateTempMemoryHighInternal((size_t)sizeof(*record));
    record->value = value;
    record->codeOffset =
        (coduo_script_value_payload_t)TempMalloc(0);
    if (script_codegenMode == SCRIPT_CODEGEN_MODE_RECORD_STRING_FIXUPS) {
        record->codeOffset +=
            (coduo_script_value_payload_t)(script_codeRelocationEnd -
                                           script_codeRelocationStart);
    }
    record->sourcePos = sourcePos;
    record->next = script_caseRecordList;
    script_caseRecordList = record;
}

void EmitSwitchStatement(coduo_scr_ast_node_t *valueNode,
                         coduo_scr_ast_node_t *bodyNode,
                         uint32_t sourcePos)
{
    script_switch_patch_state_t state;
    coduomp_script_save_switch_patch_state(&state);
    coduomp_script_clear_switch_patch_state();

    EmitExpression(valueNode);
    EmitOpcode(SCRIPT_OPCODE_SWITCH_JUMP, -1, 0);
    EmitInteger(0);
    char *tablePatch = (char *)script_codeEmitCursor;

    coduomp_script_enable_switch_patch_state();
    EmitStatement(bodyNode);
    coduomp_script_clear_switch_patch_state();

    EmitOpcode(SCRIPT_OPCODE_SWITCH_TABLE, 0, 0);
    AddOpcodePos(sourcePos);
    EmitShort(0);
    script_switch_case_table_entry_t *table =
        (script_switch_case_table_entry_t *)TempMalloc(0);
    const int32_t tableOffset = (int32_t)((char *)table - tablePatch);
    memcpy(tablePatch, &tableOffset, sizeof(tableOffset));

    uint32_t caseCount = 0;
    for (script_switch_case_record_t *record = script_caseRecordList;
         record != NULL; record = record->next) {
        EmitInteger((int32_t)record->value);
        coduomp_emit_value_payload(record->codeOffset);
        caseCount++;
    }

    uint8_t *tableBytes = (uint8_t *)table;
    /* ORIGINAL_BINARY_BUG: the unchecked full count narrows to a word which
     * both VM_Execute switch consumers later sign-extend. */
    const uint16_t emittedCaseCount = (uint16_t)caseCount;
    memcpy(tableBytes - sizeof(emittedCaseCount), &emittedCaseCount,
           sizeof(emittedCaseCount));
    qsort(table, caseCount, sizeof(*table), CompareCaseInfo);

    for (uint32_t index = 1; index < caseCount; index++) {
        if (table[index - 1].value != table[index].value) {
            continue;
        }

        for (script_switch_case_record_t *record = script_caseRecordList;
             record != NULL; record = record->next) {
            if (record->value == table[index - 1].value) {
                CompileError(record->sourcePos,
                                    "duplicate case expression");
            }
        }
    }

    ConnectBreakStatements();
    coduomp_script_restore_switch_patch_state(&state);
}

void EmitCaseStatement(coduo_scr_ast_node_t *valueNode,
                       uint32_t sourcePos)
{
    uint32_t caseValue;

    if (valueNode->kind == SCR_AST_KIND_INTEGER_LITERAL) {
        int32_t integerValue =
            (int32_t)valueNode->payload.literal.rawValue;
        if (integerValue + SCRIPT_CASE_INTEGER_TEST_BIAS >=
            SCRIPT_CASE_INTEGER_TEST_LIMIT) {
            CompileError(
                sourcePos, va("case index %d out of range", integerValue));
            return;
        }
        caseValue = ((uint32_t)integerValue + SCRIPT_CASE_INTEGER_ENCODE_BIAS) &
                    SCRIPT_CASE_INTEGER_ENCODE_MASK;
    } else {
        if (valueNode->kind != SCR_AST_KIND_STRING_USAGE_A) {
            CompileError(sourcePos,
                                "case expression must be an int or string");
            EmitPrimitiveExpression(valueNode);
            return;
        }

        caseValue = SCR_AST_STRING_HANDLE(valueNode->payload.sourceString.string);
        CompileTransferRefToString((uint16_t)caseValue,
                                   SCRIPT_STRING_USAGE_CASE);
    }

    if (script_caseAllowed == qfalse) {
        CompileError(sourcePos, "illegal case statement");
    } else if (script_caseAllowedInDeveloperBlock == qfalse &&
               script_codegenMode != 0) {
        CompileError(
            sourcePos,
            "cannot use /# ... #/ comments directly around a case statement");
    } else {
        EmitCaseStatementInfo(caseValue, sourcePos);
    }
}

void EmitDefaultStatement(uint32_t sourcePos)
{
    if (script_caseAllowed == qfalse) {
        CompileError(sourcePos, "illegal default statement");
    } else {
        EmitCaseStatementInfo(0, sourcePos);
    }
}

void EmitBreakStatement(uint32_t sourcePos)
{
    if (script_breakAllowed == qfalse) {
        CompileError(sourcePos, "illegal break statement");
        return;
    }
    if (script_breakAllowedInDeveloperBlock == qfalse &&
        script_codegenMode != 0) {
        CompileError(
            sourcePos,
            "cannot use /# ... #/ comments directly around a break statement");
        return;
    }

    EmitOpcode(SCRIPT_OPCODE_JUMP_FORWARD, 0, 0);
    EmitInteger(0);

    script_code_offset_patch_t *patch =
        Hunk_AllocateTempMemoryHighInternal((size_t)sizeof(*patch));
    patch->patch = (char *)script_codeEmitCursor;
    patch->next = script_breakPatchList;
    script_breakPatchList = patch;
}

void EmitContinueStatement(uint32_t sourcePos)
{
    if (script_continueAllowed == qfalse) {
        CompileError(sourcePos, "illegal continue statement");
        return;
    }
    if (script_continueAllowedInDeveloperBlock == qfalse &&
        script_codegenMode != 0) {
        CompileError(sourcePos,
                            "cannot use /# ... #/ comments directly around a "
                            "continue statement");
        return;
    }

    EmitOpcode(SCRIPT_OPCODE_JUMP_FORWARD, 0, 0);
    EmitInteger(0);

    script_code_offset_patch_t *patch =
        Hunk_AllocateTempMemoryHighInternal((size_t)sizeof(*patch));
    patch->patch = (char *)script_codeEmitCursor;
    patch->next = script_continuePatchList;
    script_continuePatchList = patch;
}

void EmitStatement(coduo_scr_ast_node_t *node)
{
    switch (node->kind) {
    case SCR_AST_KIND_ASSIGNMENT_STATEMENT:
        EmitAssignmentStatement(
            node->payload.assignmentStatement.refNode,
            node->payload.assignmentStatement.valueNode,
            node->payload.assignmentStatement.sourcePos);
        break;
    case SCR_AST_KIND_CALL_STATEMENT:
        EmitExpressionStatement(node->payload.child.node);
        break;
    case SCR_AST_KIND_RETURN_VALUE_STATEMENT:
        EmitReturnStatement(
            node->payload.returnValueStatement.valueNode);
        break;
    case SCR_AST_KIND_RETURN_STATEMENT:
        EmitEndStatement();
        break;
    case SCR_AST_KIND_WAIT_STATEMENT:
        EmitWaitStatement(node->payload.waitStatement.timeNode,
                          node->payload.waitStatement.timeSourcePos,
                          node->payload.waitStatement.opcodeSourcePos);
        break;
    case SCR_AST_KIND_IF_STATEMENT:
        EmitIfStatement(node->payload.ifStatement.conditionNode,
                        node->payload.ifStatement.bodyNode,
                        node->payload.ifStatement.sourcePos);
        break;
    case SCR_AST_KIND_IF_ELSE_STATEMENT:
        EmitIfElseStatement(node->payload.ifElseStatement.conditionNode,
                            node->payload.ifElseStatement.thenNode,
                            node->payload.ifElseStatement.elseNode,
                            node->payload.ifElseStatement.sourcePos);
        break;
    case SCR_AST_KIND_WHILE_STATEMENT:
        EmitWhileStatement(node->payload.whileStatement.conditionNode,
                           node->payload.whileStatement.bodyNode,
                           node->payload.whileStatement.conditionSourcePos,
                           node->payload.whileStatement.loopSourcePos);
        break;
    case SCR_AST_KIND_DO_WHILE_STATEMENT:
        EmitDoWhileStatement(
            node->payload.doWhileStatement.bodyNode,
            node->payload.doWhileStatement.conditionNode,
            node->payload.doWhileStatement.conditionSourcePos,
            node->payload.doWhileStatement.loopSourcePos);
        break;
    case SCR_AST_KIND_FOR_STATEMENT:
        EmitForStatement(node->payload.forStatement.initNode,
                         node->payload.forStatement.conditionNode,
                         node->payload.forStatement.incrementNode,
                         node->payload.forStatement.bodyNode,
                         node->payload.forStatement.conditionSourcePos,
                         node->payload.forStatement.loopSourcePos);
        break;
    case SCR_AST_KIND_INC_STATEMENT:
        EmitIncStatement(node->payload.incDecStatement.refNode,
                         node->payload.incDecStatement.sourcePos);
        break;
    case SCR_AST_KIND_DEC_STATEMENT:
        EmitDecStatement(node->payload.incDecStatement.refNode,
                         node->payload.incDecStatement.sourcePos);
        break;
    case SCR_AST_KIND_REF_ASSIGNMENT_STATEMENT:
        EmitBinaryEqualsOperatorExpression(
            node->payload.assignmentStatement.refNode,
            node->payload.assignmentStatement.valueNode,
            (uint8_t)node->payload.assignmentStatement.opcode,
            node->payload.assignmentStatement.sourcePos);
        break;
    case SCR_AST_KIND_STATEMENT_BLOCK:
        EmitStatementList(node->payload.statementBlock.block);
        break;
    case SCR_AST_KIND_DEVELOPER_STATEMENT_BLOCK:
        EmitDeveloperStatementList(
            node->payload.developerStatementBlock.block,
            node->payload.developerStatementBlock.sourcePos);
        break;
    case SCR_AST_KIND_WAITTILL_STATEMENT:
        EmitWaittillStatement(
            node->payload.waittillStatement.objectNode,
            node->payload.waittillStatement.list,
            node->payload.waittillStatement.objectSourcePos,
            node->payload.waittillStatement.opcodeSourcePos);
        break;
    case SCR_AST_KIND_WAITTILLMATCH_STATEMENT:
        EmitWaittillmatchStatement(
            node->payload.waittillStatement.objectNode,
            node->payload.waittillStatement.list,
            node->payload.waittillStatement.objectSourcePos,
            node->payload.waittillStatement.opcodeSourcePos);
        break;
    case SCR_AST_KIND_NOTIFY_STATEMENT:
        EmitNotifyStatement(
            node->payload.notifyStatement.objectNode,
            node->payload.notifyStatement.list,
            node->payload.notifyStatement.objectSourcePos,
            node->payload.notifyStatement.opcodeSourcePos);
        break;
    case SCR_AST_KIND_ENDON_STATEMENT:
        EmitEndOnStatement(node->payload.endOnStatement.objectNode,
                           node->payload.endOnStatement.eventNode,
                           node->payload.endOnStatement.objectSourcePos,
                           node->payload.endOnStatement.opcodeSourcePos);
        break;
    case SCR_AST_KIND_SWITCH_STATEMENT:
        EmitSwitchStatement(node->payload.switchStatement.valueNode,
                            node->payload.switchStatement.bodyNode,
                            node->payload.switchStatement.sourcePos);
        break;
    case SCR_AST_KIND_CASE_STATEMENT:
        EmitCaseStatement(node->payload.caseStatement.valueNode,
                          node->payload.caseStatement.sourcePos);
        break;
    case SCR_AST_KIND_DEFAULT_STATEMENT:
        EmitDefaultStatement(
            node->payload.sourceOnlyStatement.sourcePos);
        break;
    case SCR_AST_KIND_BREAK_STATEMENT:
        EmitBreakStatement(node->payload.sourceOnlyStatement.sourcePos);
        break;
    case SCR_AST_KIND_CONTINUE_STATEMENT:
        EmitContinueStatement(
            node->payload.sourceOnlyStatement.sourcePos);
        break;
    }
}
