#include <stdint.h>
#include <stddef.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"

enum {
    SCRIPT_CODEGEN_MODE_INTERN_STRINGS = 0,
    SCRIPT_CODEGEN_MODE_RECORD_STRING_FIXUPS = 1,
    SCRIPT_CODEGEN_MODE_RELEASE_STRINGS = 2,
    SCRIPT_OPCODE_DEVELOPER_COMMAND = 0x57,
    SCRIPT_OPCODE_DEFERRED_DEVELOPER_CHECK = 0x58,
    SCRIPT_DEVELOPER_OP_BUFFER_SIZE = 0x100000,
    SCRIPT_DEVELOPER_OPCODE_PATCH_INITIAL_CAPACITY = 0x10000,
    SCRIPT_COM_ERROR_DROP = 1
};

void EmitStatementList(coduo_scr_ast_statement_block_t *block)
{
    for (coduo_scr_ast_statement_item_t *item = block->list->head;
         item != NULL; item = item->next) {
        EmitStatement(item->node);
    }
}

void Scr_TransferToDeveloperBuffer(void)
{
    uint8_t *tempEnd = TempMalloc(0);
    int32_t byteCount = (int32_t)(tempEnd - script_codeRelocationStart);
    int32_t usedBytes =
        (int32_t)(script_codeRelocationEnd - script_developerOpBuffer);

    if (SCRIPT_DEVELOPER_OP_BUFFER_SIZE < usedBytes + byteCount) {
        Com_Error(
            SCRIPT_COM_ERROR_DROP,
            "max developer script size exceeded - increase DEV_OP_BUF_SIZE");
    }

    Com_Memcpy(script_codeRelocationEnd, script_codeRelocationStart,
                 (size_t)byteCount);
    script_codeRelocationEnd += byteCount;
}

void Scr_TransferStatementListToDeveloperBuffer(void)
{
    uint32_t savedChecksum = script_codeChecksum;

    EmitOpcode(SCRIPT_OPCODE_DEFERRED_DEVELOPER_CHECK, 0, 0);

    int32_t codeOffset =
        (int32_t)(script_codeRelocationStart - script_codeBase);
    script_codeChecksum = savedChecksum;

    if (script_developerOpcodePatchCapacity <= codeOffset) {
        int32_t oldCapacity = script_developerOpcodePatchCapacity;
        int32_t newCapacity = oldCapacity * 2;
        if (newCapacity <= codeOffset) {
            newCapacity = codeOffset * 2;
        }

        uint8_t **newTable =
            Z_Malloc((size_t)newCapacity * sizeof(newTable[0]));
        Com_Memcpy(newTable, script_developerOpcodePatchTable,
                     (size_t)oldCapacity *
                         sizeof(script_developerOpcodePatchTable[0]));
        Com_Memset(&newTable[oldCapacity], 0,
                   (size_t)(newCapacity - oldCapacity) *
                       sizeof(newTable[0]));

        script_developerOpcodePatchCapacity = newCapacity;
        Z_Free(script_developerOpcodePatchTable);
        script_developerOpcodePatchTable = newTable;
    }

    script_developerOpcodePatchTable[codeOffset] = script_codeRelocationEnd;
    Scr_TransferToDeveloperBuffer();
    TempMemorySetPos(script_codeRelocationStart);
}

void EmitDeveloperStatementList(coduo_scr_ast_statement_block_t *block,
                                uint32_t sourcePos)
{
    if (script_codegenMode != SCRIPT_CODEGEN_MODE_INTERN_STRINGS) {
        CompileError(sourcePos, "cannot recurse /#");
    }

    uint32_t savedChecksum = script_codeChecksum;
    if (script_runtimeDeveloperScriptFlag == 0) {
        uint8_t *tempMark = TempMalloc(0);
        script_codegenMode = SCRIPT_CODEGEN_MODE_RELEASE_STRINGS;
        EmitStatementList(block);
        TempMemorySetPos(tempMark);
    } else {
        if (script_codeNeedsDeferredCheck == qfalse) {
            EmitOpcode(SCRIPT_OPCODE_DEVELOPER_COMMAND, 0, 0);
            script_codeRelocationStart = script_codeEmitCursor;
        }
        script_codegenMode = SCRIPT_CODEGEN_MODE_RECORD_STRING_FIXUPS;
        EmitStatementList(block);
        script_codeNeedsDeferredCheck = qtrue;
    }

    script_codegenMode = SCRIPT_CODEGEN_MODE_INTERN_STRINGS;
    script_codeChecksum = savedChecksum;
}

void Scr_InitDeveloperOpcodes(void)
{
    if (script_runtimeDeveloperScriptFlag == 0) {
        return;
    }

    script_developerOpBuffer = Z_Malloc(SCRIPT_DEVELOPER_OP_BUFFER_SIZE);
    script_developerOpcodePatchCapacity =
        SCRIPT_DEVELOPER_OPCODE_PATCH_INITIAL_CAPACITY;
    script_developerOpcodePatchTable = Z_Malloc(
        (script_developerOpcodePatchCapacity *
                  sizeof(script_developerOpcodePatchTable[0])));
    Com_Memset(script_developerOpcodePatchTable, 0,
               (script_developerOpcodePatchCapacity *
                  sizeof(script_developerOpcodePatchTable[0])));
    script_codeRelocationEnd = script_developerOpBuffer;
    Com_Memset(&script_frameBackupCodepos, 0,
               sizeof(script_frameBackupCodepos));
    script_codeStringFixups = NULL;
}

void Scr_InsertDeveloperOpcodes(void)
{
    if (script_runtimeDeveloperScriptFlag == 0) {
        return;
    }

    for (int32_t codeOffset = 0;
         codeOffset < script_developerOpcodePatchCapacity; ++codeOffset) {
        uint8_t *patch = script_developerOpcodePatchTable[codeOffset];
        if (patch != NULL) {
            *patch = script_codeBase[codeOffset];
            script_codeBase[codeOffset] = SCRIPT_OPCODE_DEVELOPER_COMMAND;
        }
    }

    while (script_codeStringFixups != NULL) {
        script_code_string_fixup_t *fixup = script_codeStringFixups;
        uint16_t string;
        memcpy(&string, fixup->codepos, sizeof(string));
        if (string != 0) {
            string = SL_TransferToCanonicalString(string);
            memcpy(fixup->codepos, &string, sizeof(string));
        }
        script_codeStringFixups = fixup->next;
        Z_Free(fixup);
    }
}

void Scr_ShutdownDeveloperOpcodes(void)
{
    if (script_runtimeDeveloperScriptFlag == 0) {
        return;
    }

    if (script_developerOpBuffer != NULL) {
        Z_Free(script_developerOpBuffer);
        script_developerOpBuffer = NULL;
    }
    if (script_developerOpcodePatchTable != NULL) {
        Z_Free(script_developerOpcodePatchTable);
        script_developerOpcodePatchTable = NULL;
    }
}
