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

#include "../core_memory/core_memory_private.h"
#include "coduo_engine_structs.h"
#include "script_compile_private.h"
#include "script_memory_private.h"
#include "script_source_positions_private.h"

enum {
    SCRIPT_CODEGEN_MODE_INTERN_STRINGS = 0,
    SCRIPT_CODEGEN_MODE_RECORD_STRING_FIXUPS = 1,
    SCRIPT_CODEGEN_MODE_RELEASE_STRINGS = 2,
    SCRIPT_CODE_OPCODE_HASH_MULTIPLIER = 31,
    SCRIPT_CODE_OPCODE_LITERAL_INT = 0x03,
    SCRIPT_CODE_OPCODE_LITERAL_FLOAT = 0x04,
    SCRIPT_CODE_OPCODE_GET_STRING = 0x05,
    SCRIPT_CODE_OPCODE_GET_ISTRING = 0x06,
    SCRIPT_CODE_OPCODE_SELF_OBJECT = 0x07,
    SCRIPT_CODE_OPCODE_LEVEL_OBJECT = 0x08,
    SCRIPT_CODE_OPCODE_ANIM_OBJECT = 0x09,
    SCRIPT_CODE_OPCODE_SELF = 0x0a,
    SCRIPT_CODE_OPCODE_LEVEL = 0x0b,
    SCRIPT_CODE_OPCODE_GAME = 0x0c,
    SCRIPT_CODE_OPCODE_ANIM = 0x0d,
    SCRIPT_CODE_OPCODE_ANIMATION = 0x0e,
    SCRIPT_CODE_OPCODE_GAME_REF = 0x0f,
    SCRIPT_CODE_OPCODE_LOCAL_VARIABLE = 0x11,
    SCRIPT_CODE_OPCODE_LOCAL_VARIABLE_REF = 0x12,
    SCRIPT_CODE_OPCODE_CLEAR_LOCAL_VARIABLE = 0x13,
    SCRIPT_CODE_OPCODE_EVAL_ARRAY = 0x14,
    SCRIPT_CODE_OPCODE_EVAL_ARRAY_REF = 0x15,
    SCRIPT_CODE_OPCODE_CLEAR_ARRAY = 0x16,
    SCRIPT_CODE_OPCODE_EMPTY_ARRAY = 0x17,
    SCRIPT_CODE_OPCODE_FIELD_VARIABLE = 0x18,
    SCRIPT_CODE_OPCODE_FIELD_VARIABLE_REF = 0x19,
    SCRIPT_CODE_OPCODE_CLEAR_FIELD_VARIABLE = 0x1a,
    SCRIPT_CODE_OPCODE_STACK_POP = 0x1b,
    SCRIPT_CODE_OPCODE_SAFE_SET_VARIABLE_FIELD = 0x1c,
    SCRIPT_CODE_OPCODE_SAFE_SET_WAITTILL_VARIABLE_FIELD = 0x1d,
    SCRIPT_CODE_OPCODE_SET_VARIABLE_FIELD = 0x20,
    SCRIPT_CODE_OPCODE_DROP_TOP = 0x2d,
    SCRIPT_CODE_OPCODE_SOURCE_MARKED_2E = 0x2e,
    SCRIPT_CODE_OPCODE_CAST_BOOL = 0x2f,
    SCRIPT_CODE_OPCODE_CAST_INT = 0x30,
    SCRIPT_CODE_OPCODE_CAST_FLOAT = 0x31,
    SCRIPT_CODE_OPCODE_CAST_STRING = 0x32,
    SCRIPT_CODE_OPCODE_BOOL_NOT = 0x33,
    SCRIPT_CODE_OPCODE_BOOL_COMPLEMENT = 0x34,
    SCRIPT_CODE_OPCODE_NOP = 0x56,
    SCRIPT_CODE_OPCODE_UNDEFINED_VALUE = 0x02
};

#define SCRIPT_CODE_UNRESOLVED_DWORD UINT32_MAX

void TempMemoryReset(void)
{
    script_codeTempSize = 0;
}

uint8_t *TempMalloc(size_t size)
{
    size_t oldSize = script_codeTempSize;

    script_codeTempSize += size;
    return (uint8_t *)Hunk_ReallocateTempMemory(script_codeTempSize) + oldSize;
}

void TempMemorySetPos(uint8_t *pos)
{
    uint8_t *end = TempMalloc(0);

    script_codeTempSize -= (size_t)(end - pos);
    Hunk_ReallocateTempMemory(script_codeTempSize);
}

void CompileRemoveRefToString(uint16_t string)
{
    if (script_codeOwnsStrings == qfalse) {
        SL_RemoveRefToString(string);
    }
}

void EmitCanonicalString(uint16_t string)
{
    script_codeEmitCursor = TempMalloc(sizeof(uint16_t));
    char *out = (char *)script_codeEmitCursor;

    if (script_codegenMode == SCRIPT_CODEGEN_MODE_RELEASE_STRINGS) {
        CompileRemoveRefToString(string);
        return;
    }

    if (script_codeOwnsStrings != qfalse) {
        SL_AddRefToString(string);
    }

    if (script_codegenMode == SCRIPT_CODEGEN_MODE_INTERN_STRINGS) {
        const uint16_t canonical = SL_TransferToCanonicalString(string);
        memcpy(out, &canonical, sizeof(canonical));
        return;
    }

    memcpy(out, &string, sizeof(string));

    script_code_string_fixup_t *fixup = Z_Malloc(sizeof(*fixup));
    fixup->codepos =
        (char *)(script_codeEmitCursor +
                 (script_codeRelocationEnd - script_codeRelocationStart));
    const uint16_t emptyString = 0;
    memcpy(fixup->codepos, &emptyString, sizeof(emptyString));
    fixup->next = script_codeStringFixups;
    script_codeStringFixups = fixup;
}

void CompileTransferRefToString(uint16_t string,
                                uint8_t usage)
{
    if (script_codegenMode == SCRIPT_CODEGEN_MODE_RELEASE_STRINGS) {
        CompileRemoveRefToString(string);
        return;
    }

    if (script_codeOwnsStrings != qfalse) {
        SL_AddRefToString(string);
    }

    SL_TransferRefToString(string, usage);
}

void EmitOpcode(uint8_t opcode, int32_t stackDelta,
                int32_t localDepthMode)
{
    if (script_codeNeedsDeferredCheck != qfalse &&
        script_codegenMode == SCRIPT_CODEGEN_MODE_INTERN_STRINGS) {
        script_codeNeedsDeferredCheck = qfalse;
        Scr_TransferStatementListToDeveloperBuffer();
    }

    script_codeStackDepth += stackDelta;
    if (script_codeMaxStackDepth < script_codeStackDepth) {
        script_codeMaxStackDepth = script_codeStackDepth;
    }

    if (localDepthMode != 0 &&
        script_codeMaxLocalDepth < script_codeStackDepth) {
        script_codeMaxLocalDepth = script_codeStackDepth;
        if (localDepthMode == 3) {
            script_codeMaxLocalDepth = script_codeStackDepth + 1;
        }
    }

    script_codeEmitCursor = TempMalloc(sizeof(opcode));
    script_codeLastOpcodePos = script_codeEmitCursor;
    *script_codeEmitCursor = opcode;
    script_codeChecksum =
        script_codeChecksum * SCRIPT_CODE_OPCODE_HASH_MULTIPLIER + opcode;
}

void EmitNOP(void)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_NOP, 0, 0);
}

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

    script_codeEmitCursor = (uint8_t *)out;
    memcpy(out, &value, sizeof(value));
}

void EmitShort(int16_t value)
{
    char *out = (char *)TempMalloc(sizeof(value));

    script_codeEmitCursor = (uint8_t *)out;
    memcpy(out, &value, sizeof(value));
}

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

    script_codeEmitCursor = out;
    *out = value;
}

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

    script_codeEmitCursor = (uint8_t *)out;
    memcpy(out, &value, sizeof(value));
}

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

    script_codeEmitCursor = (uint8_t *)out;
    memcpy(out, &value, sizeof(value));
}

void EmitBlankPos(void)
{
    const uint32_t value = 0;
    char *out = (char *)TempMalloc(sizeof(value));

    script_codeEmitCursor = (uint8_t *)out;
    memcpy(out, &value, sizeof(value));
}

/* NOT_FROM_ORIGINAL_SOURCE: emits host-width script value payload operands. */
void coduomp_emit_blank_value_payload(void)
{
    const coduo_script_value_payload_t value = 0;
    char *out = (char *)TempMalloc(sizeof(value));

    script_codeEmitCursor = (uint8_t *)out;
    memcpy(out, &value, sizeof(value));
}

void EmitCodepos(uint32_t i386CodePos)
{
    char *out = (char *)TempMalloc(sizeof(i386CodePos));

    script_codeEmitCursor = (uint8_t *)out;
    memcpy(out, &i386CodePos, sizeof(i386CodePos));
}

/* NOT_FROM_ORIGINAL_SOURCE: emits host-width script value payload operands. */
void coduomp_emit_value_payload(coduo_script_value_payload_t value)
{
    char *out = (char *)TempMalloc(sizeof(value));

    script_codeEmitCursor = (uint8_t *)out;
    memcpy(out, &value, sizeof(value));
}

void EmitBuiltinFunction(uint32_t i386FunctionBits)
{
    char *out = (char *)TempMalloc(sizeof(i386FunctionBits));

    script_codeEmitCursor = (uint8_t *)out;
    memcpy(out, &i386FunctionBits, sizeof(i386FunctionBits));
}

void EmitBuiltinMethod(uint32_t i386MethodBits)
{
    char *out = (char *)TempMalloc(sizeof(i386MethodBits));

    script_codeEmitCursor = (uint8_t *)out;
    memcpy(out, &i386MethodBits, sizeof(i386MethodBits));
}

/* NOT_FROM_ORIGINAL_SOURCE: emits host-width native callback operands. */
void coduomp_emit_builtin_function_payload(
    coduo_script_function_callback_fn function)
{
    char *out = (char *)TempMalloc(sizeof(function));

    script_codeEmitCursor = (uint8_t *)out;
    memcpy(out, &function, sizeof(function));
}

/* NOT_FROM_ORIGINAL_SOURCE: emits host-width native callback operands. */
void coduomp_emit_builtin_method_payload(
    coduo_script_method_callback_fn method)
{
    char *out = (char *)TempMalloc(sizeof(method));

    script_codeEmitCursor = (uint8_t *)out;
    memcpy(out, &method, sizeof(method));
}

void EmitIncTop(void)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_UNDEFINED_VALUE, 1, 0);
}

void EmitGetInteger(int32_t value)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_LITERAL_INT, 1, 0);
    EmitInteger(value);
}

void EmitGetFloat(float value)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_LITERAL_FLOAT, 1, 0);
    EmitFloat(value);
}

void EmitFalse(void)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_LITERAL_INT, 1, 0);
    EmitInteger(0);
}

void EmitTrue(void)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_LITERAL_INT, 1, 0);
    EmitInteger(1);
}

void EmitAnimTree(uint32_t sourcePos)
{
    if (script_activeAnimTreeHandle == 0) {
        CompileError(sourcePos, "#using_animtree was not specified");
        return;
    }

    EmitOpcode(SCRIPT_CODE_OPCODE_LITERAL_INT, 1, 0);
    EmitInteger((int32_t)script_activeAnimTreeHandle);
}

void EmitSetVariableField(uint32_t sourcePos)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_SET_VARIABLE_FIELD, -1, 0);
    AddOpcodePos(sourcePos);
}

void EmitSafeSetVariableField(uint16_t string,
                              uint32_t sourcePos)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_SAFE_SET_VARIABLE_FIELD, 0, 0);
    AddOpcodePos(sourcePos);
    EmitCanonicalString(string);
}

void EmitSafeSetWaittillVariableField(uint16_t string,
                                      uint32_t sourcePos)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_SAFE_SET_WAITTILL_VARIABLE_FIELD, 0, 0);
    AddOpcodePos(sourcePos);
    EmitCanonicalString(string);
}

void EmitGetString(uint16_t string)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_GET_STRING, 1, 0);
    EmitString(string);
    CompileTransferRefToString(string, 1);
}

void EmitGetIString(uint16_t string)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_GET_ISTRING, 1, 0);
    EmitString(string);
    CompileTransferRefToString(string, 1);
}

void EmitCastBool(uint32_t sourcePos)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_CAST_BOOL, 0, 0);
    AddOpcodePos(sourcePos);
}

void EmitCastInt(uint32_t sourcePos)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_CAST_INT, 0, 0);
    AddOpcodePos(sourcePos);
}

void EmitCastFloat(uint32_t sourcePos)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_CAST_FLOAT, 0, 0);
    AddOpcodePos(sourcePos);
}

void EmitCastString(uint32_t sourcePos)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_CAST_STRING, 0, 0);
    AddOpcodePos(sourcePos);
}

void EmitBoolNot(uint32_t sourcePos)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_BOOL_NOT, 0, 0);
    AddOpcodePos(sourcePos);
}

void EmitBoolComplement(uint32_t sourcePos)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_BOOL_COMPLEMENT, 0, 0);
    AddOpcodePos(sourcePos);
}

void EmitSelf(void)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_SELF, 1, 0);
}

void EmitLevel(void)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_LEVEL, 1, 0);
}

void EmitGame(void)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_GAME, 1, 0);
}

void EmitAnim(void)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_ANIM, 1, 0);
}

void EmitSelfObject(void)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_SELF_OBJECT, 0, 0);
}

void EmitLevelObject(void)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_LEVEL_OBJECT, 0, 0);
}

void EmitAnimObject(void)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_ANIM_OBJECT, 0, 0);
}

void EmitLocalVariable(uint16_t string)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_LOCAL_VARIABLE, 1, 0);
    EmitCanonicalString(string);
}

void EmitLocalVariableRef(uint16_t string)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_LOCAL_VARIABLE_REF, 0, 0);
    EmitCanonicalString(string);
}

void EmitGameRef(void)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_GAME_REF, 0, 0);
}

void EmitClearLocalVariable(uint16_t string)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_CLEAR_LOCAL_VARIABLE, 0, 0);
    EmitCanonicalString(string);
}

void EmitEvalArray(uint32_t firstSourcePos,
                   uint32_t secondSourcePos)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_EVAL_ARRAY, -1, 0);
    AddOpcodePos(secondSourcePos);
    AddOpcodePos(firstSourcePos);
}

void EmitEvalArrayRef(uint32_t firstSourcePos,
                      uint32_t secondSourcePos)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_EVAL_ARRAY_REF, -1, 0);
    AddOpcodePos(secondSourcePos);
    AddOpcodePos(firstSourcePos);
}

void EmitClearArray(uint32_t firstSourcePos,
                    uint32_t secondSourcePos)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_CLEAR_ARRAY, -1, 0);
    AddOpcodePos(secondSourcePos);
    AddOpcodePos(firstSourcePos);
}

void EmitEmptyArray(void)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_EMPTY_ARRAY, 1, 0);
}

void EmitAnimation(uint16_t string,
                   uint32_t sourcePos)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_ANIMATION, 1, 0);
    EmitInteger((int32_t)SCRIPT_CODE_UNRESOLVED_DWORD);
    AddOpcodePos(sourcePos);
    Scr_EmitAnimation((char *)script_codeEmitCursor, string, sourcePos);
    CompileRemoveRefToString(string);
}

void EmitFieldVariable(coduo_scr_ast_node_t *objectNode,
                       uint16_t string,
                       uint32_t sourcePos)
{
    EmitPrimitiveExpressionFieldObject(objectNode, sourcePos);
    EmitOpcode(SCRIPT_CODE_OPCODE_FIELD_VARIABLE, 1, 0);
    AddOpcodePos(sourcePos);
    EmitCanonicalString(string);
}

void EmitFieldVariableRef(coduo_scr_ast_node_t *objectNode,
                          uint16_t string,
                          uint32_t sourcePos)
{
    EmitPrimitiveExpressionFieldObject(objectNode, sourcePos);
    EmitOpcode(SCRIPT_CODE_OPCODE_FIELD_VARIABLE_REF, 0, 0);
    EmitCanonicalString(string);
}

void EmitClearFieldVariable(coduo_scr_ast_node_t *objectNode,
                            uint16_t string,
                            uint32_t objectSourcePos,
                            uint32_t opcodeSourcePos)
{
    EmitPrimitiveExpressionFieldObject(objectNode, objectSourcePos);
    EmitOpcode(SCRIPT_CODE_OPCODE_CLEAR_FIELD_VARIABLE, 0, 0);
    AddOpcodePos(opcodeSourcePos);
    EmitCanonicalString(string);
}

void EmitCallRef(coduo_scr_ast_node_t *nameNode,
                 coduo_scr_ast_list_t *argsNode,
                 uint32_t sourcePos)
{
    /* Stock passes the call node's source position but does not read it. */
    (void)sourcePos;

    EmitCall(nameNode, argsNode, qfalse);
    EmitOpcode(SCRIPT_CODE_OPCODE_STACK_POP, -1, 0);
}

void EmitMethodRef(coduo_scr_ast_node_t *objectNode,
                   coduo_scr_ast_node_t *nameNode,
                   coduo_scr_ast_list_t *argsNode,
                   uint32_t sourcePos)
{
    EmitMethod(objectNode, nameNode, argsNode, sourcePos, qfalse);
    EmitOpcode(SCRIPT_CODE_OPCODE_STACK_POP, -1, 0);
}

void EmitDecTop(void)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_DROP_TOP, -1, 0);
}

void EmitCastFieldObject(uint32_t sourcePos)
{
    EmitOpcode(SCRIPT_CODE_OPCODE_SOURCE_MARKED_2E, -1, 0);
    AddOpcodePos(sourcePos);
}
