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

#include "coduo_engine_structs.h"
#include "script_compile_private.h"
#include "script_source_positions_private.h"

#define SCRIPT_FLOAT_SIGN_BIT 0x80000000U

enum {
    SCRIPT_OPCODE_VECTOR_FROM_LIST = 0x55,
    SCRIPT_OPCODE_STACK_POP = 0x1b,
    SCRIPT_OPCODE_SOURCE_MARKED_4D = 0x4d,
    SCRIPT_OPCODE_SHORT_CIRCUIT_37 = 0x37,
    SCRIPT_OPCODE_SHORT_CIRCUIT_38 = 0x38
};

void EmitPrimitiveExpression(coduo_scr_ast_node_t *node);
void EmitExpression(coduo_scr_ast_node_t *node);
void EmitExpressionFieldObject(coduo_scr_ast_node_t *node,
                               uint32_t sourcePos);
void EmitPrimitiveExpressionFieldObject(coduo_scr_ast_node_t *node,
                                        uint32_t sourcePos);
void EmitVariableExpressionRef(coduo_scr_ast_node_t *node);
void EmitArrayExpressionRef(coduo_scr_ast_node_t *node,
                            uint32_t sourcePos);

void EmitSize(coduo_scr_ast_node_t *node,
              uint32_t sourcePos)
{
    EmitPrimitiveExpression(node);
    EmitOpcode(SCRIPT_OPCODE_SOURCE_MARKED_4D, 0, 0);
    AddOpcodePos(sourcePos);
}

void EmitCallExpression(coduo_scr_ast_node_t *node, qboolean emitDropTop)
{
    if (node->kind == SCR_AST_KIND_FUNCTION_CALL_VALUE) {
        EmitCall(node->payload.functionCallValue.callee,
                 node->payload.functionCallValue.args, emitDropTop);
    } else if (node->kind == SCR_AST_KIND_METHOD_CALL_VALUE) {
        EmitMethod(node->payload.methodCallValue.objectNode,
                   node->payload.methodCallValue.callee,
                   node->payload.methodCallValue.args,
                   node->payload.methodCallValue.methodSourcePos,
                   emitDropTop);
    }
}

void EmitCallExpressionRef(coduo_scr_ast_node_t *node)
{
    if (node->kind == SCR_AST_KIND_FUNCTION_CALL_VALUE) {
        EmitCallRef(
            node->payload.functionCallValue.callee,
            node->payload.functionCallValue.args,
            node->payload.functionCallValue.sourcePos);
    } else if (node->kind == SCR_AST_KIND_METHOD_CALL_VALUE) {
        EmitMethodRef(
            node->payload.methodCallValue.objectNode,
            node->payload.methodCallValue.callee,
            node->payload.methodCallValue.args,
            node->payload.methodCallValue.methodSourcePos);
    }
}

void EmitCallExpressionFieldObject(coduo_scr_ast_node_t *node)
{
    if (node->kind == SCR_AST_KIND_FUNCTION_CALL_VALUE) {
        EmitCall(node->payload.functionCallValue.callee,
                 node->payload.functionCallValue.args, qfalse);
        EmitCastFieldObject(
            node->payload.functionCallValue.sourcePos);
    } else if (node->kind == SCR_AST_KIND_METHOD_CALL_VALUE) {
        EmitMethod(node->payload.methodCallValue.objectNode,
                   node->payload.methodCallValue.callee,
                   node->payload.methodCallValue.args,
                   node->payload.methodCallValue.methodSourcePos, qfalse);
        EmitCastFieldObject(node->payload.methodCallValue.objectSourcePos);
    }
}

void EmitPrimitiveExpressionList(coduo_scr_ast_list_t *list,
                                 uint32_t sourcePos)
{
    int32_t count = EmitExpressionList(list);

    if (count == 1) {
        return;
    }

    if (count == 3) {
        EmitOpcode(SCRIPT_OPCODE_VECTOR_FROM_LIST, -2, 0);
        AddExpressionListOpcodePos(list);
        return;
    }

    CompileError(sourcePos,
                          "expression list must have 1 or 3 parameters");
}

void EmitExpressionListFieldObject(coduo_scr_ast_list_t *list,
                                   uint32_t sourcePos)
{
    coduo_scr_ast_list_item_t *item = GetSingleParameter(list);

    if (item != NULL) {
        EmitExpressionFieldObject(item->entry->node,
                                  item->entry->sourcePos);
        return;
    }

    CompileError(sourcePos, "not an object");
    EmitExpressionList(list);
}

void EmitPrimitiveExpression(coduo_scr_ast_node_t *node)
{
    switch (node->kind) {
    case SCR_AST_KIND_INTEGER_LITERAL:
        EmitGetInteger((int32_t)node->payload.literal.rawValue);
        break;
    case SCR_AST_KIND_FLOAT_LITERAL: {
        float value;
        memcpy(&value, &node->payload.literal.rawValue, sizeof(value));
        EmitGetFloat(value);
        break;
    }
    case SCR_AST_KIND_NEGATED_INTEGER_LITERAL:
        EmitGetInteger(-(int32_t)node->payload.literal.rawValue);
        break;
    case SCR_AST_KIND_NEGATED_FLOAT_LITERAL: {
        const uint32_t negativeBits =
            node->payload.literal.rawValue ^ SCRIPT_FLOAT_SIGN_BIT;
        float negativeValue;
        memcpy(&negativeValue, &negativeBits, sizeof(negativeValue));
        EmitGetFloat(negativeValue);
        break;
    }
    case SCR_AST_KIND_STRING_USAGE_A:
        EmitGetString(
            SCR_AST_STRING_HANDLE(node->payload.sourceString.string));
        break;
    case SCR_AST_KIND_STRING_USAGE_B:
        EmitGetIString(
            SCR_AST_STRING_HANDLE(node->payload.sourceString.string));
        break;
    case SCR_AST_KIND_REFERENCE_EXPRESSION:
        EmitVariableExpression(node->payload.child.node);
        break;
    case SCR_AST_KIND_SCRIPT_FUNCTION_NAME:
        EmitGetFunction(node->payload.sourceChild.node,
                        node->payload.sourceChild.sourcePos);
        break;
    case SCR_AST_KIND_CALL_VALUE:
        EmitCallExpression(node->payload.child.node, qfalse);
        break;
    case SCR_AST_KIND_DEFERRED_CHECK:
        EmitIncTop();
        break;
    case SCR_AST_KIND_SELF_VALUE:
        EmitSelf();
        break;
    case SCR_AST_KIND_LEVEL_VALUE:
        EmitLevel();
        break;
    case SCR_AST_KIND_GAME_VALUE:
        EmitGame();
        break;
    case SCR_AST_KIND_ANIM_VALUE:
        EmitAnim();
        break;
    case SCR_AST_KIND_EXPRESSION_LIST_VALUE:
        EmitPrimitiveExpressionList(node->payload.expressionList.list,
                                    node->payload.expressionList.sourcePos);
        break;
    case SCR_AST_KIND_SOURCE_MARKED_4D:
        EmitSize(node->payload.sourceChild.node,
                 node->payload.sourceChild.sourcePos);
        break;
    case SCR_AST_KIND_OPCODE_17_VALUE:
        EmitEmptyArray();
        break;
    case SCR_AST_KIND_ANIM_STRING_VALUE:
        EmitAnimation(
            SCR_AST_STRING_HANDLE(node->payload.sourceString.string),
            node->payload.sourceString.sourcePos);
        break;
    case SCR_AST_KIND_LITERAL_ZERO:
        EmitFalse();
        break;
    case SCR_AST_KIND_LITERAL_ONE:
        EmitTrue();
        break;
    case SCR_AST_KIND_ANIMTREE_VALUE:
        EmitAnimTree(node->payload.sourceChild.sourcePos);
        break;
    }
}

void EmitBoolOrExpression(coduo_scr_ast_node_t *leftNode,
                          uint32_t leftSourcePos,
                          coduo_scr_ast_node_t *rightNode,
                          uint32_t rightSourcePos)
{
    EmitExpression(leftNode);
    EmitOpcode(SCRIPT_OPCODE_SHORT_CIRCUIT_38, -1, 0);
    AddOpcodePos(leftSourcePos);
    EmitInteger(0);

    char *patch = (char *)script_codeEmitCursor;
    EmitExpression(rightNode);
    EmitCastBool(rightSourcePos);

    char *end = (char *)TempMalloc(0);
    const int32_t offset =
        (int32_t)(end - patch) - (int32_t)sizeof(offset);
    memcpy(patch, &offset, sizeof(offset));
}

void EmitBoolAndExpression(coduo_scr_ast_node_t *leftNode,
                           uint32_t leftSourcePos,
                           coduo_scr_ast_node_t *rightNode,
                           uint32_t rightSourcePos)
{
    EmitExpression(leftNode);
    EmitOpcode(SCRIPT_OPCODE_SHORT_CIRCUIT_37, -1, 0);
    AddOpcodePos(leftSourcePos);
    EmitInteger(0);

    char *patch = (char *)script_codeEmitCursor;
    EmitExpression(rightNode);
    EmitCastBool(rightSourcePos);

    char *end = (char *)TempMalloc(0);
    const int32_t offset =
        (int32_t)(end - patch) - (int32_t)sizeof(offset);
    memcpy(patch, &offset, sizeof(offset));
}

void EmitBinaryOperatorExpression(coduo_scr_ast_node_t *leftNode,
                                  coduo_scr_ast_node_t *rightNode,
                                  uint8_t opcode,
                                  uint32_t sourcePos)
{
    EmitExpression(leftNode);
    EmitExpression(rightNode);
    EmitOpcode(opcode, -1, 0);
    AddOpcodePos(sourcePos);
}

void EmitBinaryEqualsOperatorExpression(coduo_scr_ast_node_t *refNode,
                                        coduo_scr_ast_node_t *valueNode,
                                        uint8_t opcode,
                                        uint32_t sourcePos)
{
    script_codeOwnsStrings = qtrue;
    EmitVariableExpression(refNode);
    script_codeOwnsStrings = qfalse;
    EmitExpression(valueNode);
    EmitOpcode(opcode, -1, 0);
    AddOpcodePos(sourcePos);
    EmitVariableExpressionRef(refNode);
    EmitSetVariableField(sourcePos);
}

void EmitExpression(coduo_scr_ast_node_t *node)
{
    switch (node->kind) {
    case SCR_AST_KIND_PRIMITIVE_EXPRESSION:
        EmitPrimitiveExpression(node->payload.child.node);
        break;
    case SCR_AST_KIND_SHORT_CIRCUIT_38:
        EmitBoolOrExpression(
            node->payload.shortCircuit.left,
            node->payload.shortCircuit.leftSourcePos,
            node->payload.shortCircuit.right,
            node->payload.shortCircuit.rightSourcePos);
        break;
    case SCR_AST_KIND_SHORT_CIRCUIT_37:
        EmitBoolAndExpression(
            node->payload.shortCircuit.left,
            node->payload.shortCircuit.leftSourcePos,
            node->payload.shortCircuit.right,
            node->payload.shortCircuit.rightSourcePos);
        break;
    case SCR_AST_KIND_BINARY_OPCODE:
        EmitBinaryOperatorExpression(node->payload.binaryOpcode.left,
                                     node->payload.binaryOpcode.right,
                                     node->payload.binaryOpcode.opcode,
                                     node->payload.binaryOpcode.sourcePos);
        break;
    case SCR_AST_KIND_SOURCE_MARKED_2F:
        EmitExpression(node->payload.sourceChild.node);
        EmitCastBool(
            node->payload.sourceChild.sourcePos);
        break;
    case SCR_AST_KIND_SOURCE_MARKED_30:
        EmitExpression(node->payload.sourceChild.node);
        EmitCastInt(
            node->payload.sourceChild.sourcePos);
        break;
    case SCR_AST_KIND_SOURCE_MARKED_31:
        EmitExpression(node->payload.sourceChild.node);
        EmitCastFloat(
            node->payload.sourceChild.sourcePos);
        break;
    case SCR_AST_KIND_SOURCE_MARKED_32:
        EmitExpression(node->payload.sourceChild.node);
        EmitCastString(
            node->payload.sourceChild.sourcePos);
        break;
    case SCR_AST_KIND_SOURCE_MARKED_33:
        EmitExpression(node->payload.sourceChild.node);
        EmitBoolNot(
            node->payload.sourceChild.sourcePos);
        break;
    case SCR_AST_KIND_SOURCE_MARKED_34:
        EmitExpression(node->payload.sourceChild.node);
        EmitBoolComplement(
            node->payload.sourceChild.sourcePos);
        break;
    }
}

void EmitPrimitiveExpressionFieldObject(coduo_scr_ast_node_t *node,
                                        uint32_t sourcePos)
{
    switch (node->kind) {
    case SCR_AST_KIND_REFERENCE_EXPRESSION:
        EmitVariableExpression(node->payload.child.node);
        EmitCastFieldObject(node->payload.sourceChild.sourcePos);
        break;
    case SCR_AST_KIND_CALL_VALUE:
        EmitCallExpressionFieldObject(node->payload.child.node);
        break;
    case SCR_AST_KIND_SELF_VALUE:
        EmitSelfObject();
        break;
    case SCR_AST_KIND_LEVEL_VALUE:
        EmitLevelObject();
        break;
    case SCR_AST_KIND_ANIM_VALUE:
        EmitAnimObject();
        break;
    case SCR_AST_KIND_EXPRESSION_LIST_VALUE:
        EmitExpressionListFieldObject(node->payload.expressionList.list,
                                      sourcePos);
        break;
    default:
        CompileError(sourcePos, "not an object");
        EmitPrimitiveExpression(node);
        break;
    }
}

void EmitExpressionFieldObject(coduo_scr_ast_node_t *node,
                               uint32_t sourcePos)
{
    if (node->kind == SCR_AST_KIND_PRIMITIVE_EXPRESSION) {
        EmitPrimitiveExpressionFieldObject(
            node->payload.child.node,
            node->payload.sourceChild.sourcePos);
        return;
    }

    CompileError(sourcePos, "not an object");
    EmitExpression(node);
}

void ConnectBreakStatements(void)
{
    char *current = (char *)TempMalloc(0);

    for (script_code_offset_patch_t *patch = script_breakPatchList;
         patch != NULL; patch = patch->next) {
        const int32_t offset = (int32_t)(current - patch->patch);
        memcpy(patch->patch, &offset, sizeof(offset));
    }
}

void ConnectContinueStatements(void)
{
    EmitNOP();
    char *current = (char *)TempMalloc(0);

    for (script_code_offset_patch_t *patch = script_continuePatchList;
         patch != NULL; patch = patch->next) {
        const int32_t offset = (int32_t)(current - patch->patch);
        memcpy(patch->patch, &offset, sizeof(offset));
    }
}

void EmitVariableExpressionRef(coduo_scr_ast_node_t *node)
{
    if (node->kind == SCR_AST_KIND_OBJECT_INDEX_OBJECT_REF) {
        EmitArrayVariableRef(
            node->payload.objectIndexObjectRef.objectNode,
            node->payload.objectIndexObjectRef.indexNode,
            node->payload.objectIndexObjectRef.objectSourcePos,
            node->payload.objectIndexObjectRef.indexSourcePos);
        return;
    }

    if (node->kind == SCR_AST_KIND_STRING_REF) {
        EmitLocalVariableRef(
            SCR_AST_STRING_HANDLE(node->payload.stringRef.string));
        return;
    }

    if (node->kind == SCR_AST_KIND_OBJECT_STRING_REF) {
        EmitFieldVariableRef(
            node->payload.objectStringRef.objectNode,
            SCR_AST_STRING_HANDLE(node->payload.objectStringRef.string),
            node->payload.objectStringRef.sourcePos);
    }
}

void EmitArrayPrimitiveExpressionRef(
    coduo_scr_ast_node_t *node, uint32_t sourcePos)
{
    if (node->kind == SCR_AST_KIND_PRIMITIVE_EXPRESSION) {
        EmitArrayExpressionRef(node->payload.sourceChild.node,
                               node->payload.sourceChild.sourcePos);
        return;
    }

    CompileError(sourcePos, "not an array, string, or vector");
    EmitExpression(node);
}

void EmitArrayExpressionListRef(coduo_scr_ast_list_t *list,
                                uint32_t sourcePos)
{
    coduo_scr_ast_list_item_t *item = GetSingleParameter(list);

    if (item != NULL) {
        EmitArrayPrimitiveExpressionRef(item->entry->node,
                                        item->entry->sourcePos);
        return;
    }

    int32_t count = EmitExpressionList(list);
    if (count == 3) {
        EmitOpcode(SCRIPT_OPCODE_VECTOR_FROM_LIST, -2, 0);
        AddExpressionListOpcodePos(list);
        EmitOpcode(SCRIPT_OPCODE_STACK_POP, -1, 0);
        return;
    }

    CompileError(sourcePos, "not an array, string, or vector");
}

void EmitArrayExpressionRef(coduo_scr_ast_node_t *node,
                            uint32_t sourcePos)
{
    if (node->kind == SCR_AST_KIND_CALL_VALUE) {
        EmitCallExpressionRef(node->payload.child.node);
        return;
    }

    if (node->kind == SCR_AST_KIND_REFERENCE_EXPRESSION) {
        EmitVariableExpressionRef(node->payload.child.node);
        return;
    }

    if (node->kind == SCR_AST_KIND_GAME_VALUE) {
        EmitGameRef();
        return;
    }

    if (node->kind == SCR_AST_KIND_EXPRESSION_LIST_VALUE) {
        EmitArrayExpressionListRef(
            node->payload.expressionList.list, sourcePos);
        return;
    }

    CompileError(sourcePos, "not an array, string, or vector");
    EmitPrimitiveExpression(node);
}
