#include <stdint.h>

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

enum {
    SCRIPT_OPCODE_FUNCTION_REFERENCE = 0x10,
    SCRIPT_OPCODE_THREAD_MARKER = 0x24,
    SCRIPT_OPCODE_CALL_FUNCTION = 0x25,
    SCRIPT_OPCODE_CALL_FUNCTION_METHOD_CONTEXT = 0x27,
    SCRIPT_OPCODE_CALL_POINTER = 0x26,
    SCRIPT_OPCODE_CALL_POINTER_METHOD_CONTEXT = 0x28,
    SCRIPT_OPCODE_THREAD_FUNCTION = 0x29,
    SCRIPT_OPCODE_THREAD_FUNCTION_METHOD_CONTEXT = 0x2b,
    SCRIPT_OPCODE_THREAD_POINTER = 0x2a,
    SCRIPT_OPCODE_THREAD_POINTER_METHOD_CONTEXT = 0x2c,
    SCRIPT_CALL_LOCAL_DEPTH_STACK = 3,
    SCRIPT_THREAD_LOCAL_DEPTH_STACK = 2
};

void EmitGetFunction(coduo_scr_ast_node_t *nameNode,
                     uint32_t sourcePos)
{
    EmitOpcode(SCRIPT_OPCODE_FUNCTION_REFERENCE, 1, 0);
    EmitFunction(nameNode, sourcePos);
}

void EmitPostScriptFunction(coduo_scr_ast_node_t *nameNode,
                            int32_t argumentCount,
                            qboolean hasMethodContext,
                            uint32_t sourcePos)
{
    if (hasMethodContext == qfalse) {
        EmitOpcode(SCRIPT_OPCODE_CALL_FUNCTION,
                   -1 - argumentCount,
                   SCRIPT_CALL_LOCAL_DEPTH_STACK);
    } else {
        EmitOpcode(SCRIPT_OPCODE_CALL_FUNCTION_METHOD_CONTEXT,
                   -2 - argumentCount,
                   SCRIPT_CALL_LOCAL_DEPTH_STACK);
    }

    EmitFunction(nameNode, sourcePos);
    EmitInteger(argumentCount);
}

void EmitPostScriptFunctionPointer(coduo_scr_ast_node_t *objectNode,
                                   int32_t argumentCount,
                                   qboolean hasMethodContext,
                                   uint32_t callSourcePos,
                                   uint32_t objectSourcePos)
{
    EmitExpression(objectNode);
    if (hasMethodContext == qfalse) {
        EmitOpcode(SCRIPT_OPCODE_CALL_POINTER,
                   -2 - argumentCount,
                   SCRIPT_CALL_LOCAL_DEPTH_STACK);
    } else {
        EmitOpcode(SCRIPT_OPCODE_CALL_POINTER_METHOD_CONTEXT,
                   -3 - argumentCount,
                   SCRIPT_CALL_LOCAL_DEPTH_STACK);
    }

    AddOpcodePos(objectSourcePos);
    AddOpcodePos(callSourcePos);
    EmitInteger(argumentCount);
}

void EmitPostScriptThread(coduo_scr_ast_node_t *nameNode,
                          int32_t argumentCount,
                          qboolean hasMethodContext,
                          uint32_t sourcePos)
{
    if (hasMethodContext == qfalse) {
        EmitOpcode(SCRIPT_OPCODE_THREAD_FUNCTION,
                   1 - argumentCount,
                   SCRIPT_THREAD_LOCAL_DEPTH_STACK);
    } else {
        EmitOpcode(SCRIPT_OPCODE_THREAD_FUNCTION_METHOD_CONTEXT,
                   -argumentCount,
                   SCRIPT_THREAD_LOCAL_DEPTH_STACK);
    }

    EmitFunction(nameNode, sourcePos);
    EmitInteger(argumentCount);
}

void EmitPostScriptThreadPointer(coduo_scr_ast_node_t *objectNode,
                                 int32_t argumentCount,
                                 qboolean hasMethodContext,
                                 uint32_t sourcePos)
{
    EmitExpression(objectNode);
    if (hasMethodContext == qfalse) {
        EmitOpcode(SCRIPT_OPCODE_THREAD_POINTER, -argumentCount,
                   SCRIPT_THREAD_LOCAL_DEPTH_STACK);
    } else {
        EmitOpcode(SCRIPT_OPCODE_THREAD_POINTER_METHOD_CONTEXT,
                   -1 - argumentCount,
                   SCRIPT_THREAD_LOCAL_DEPTH_STACK);
    }

    AddOpcodePos(sourcePos);
    EmitInteger(argumentCount);
}

void EmitPostScriptFunctionCall(coduo_scr_ast_node_t *calleeNode,
                                int32_t argumentCount,
                                qboolean hasMethodContext,
                                uint32_t callSourcePos)
{
    if (calleeNode->kind == SCR_AST_KIND_SCRIPT_FUNCTION_NAME) {
        EmitPostScriptFunction(calleeNode->payload.namedCall.nameNode,
                               argumentCount, hasMethodContext,
                               callSourcePos);
    } else if (calleeNode->kind == SCR_AST_KIND_FUNCTION_POINTER_CALL) {
        EmitPostScriptFunctionPointer(
            calleeNode->payload.pointerCall.objectNode, argumentCount,
            hasMethodContext, callSourcePos,
            calleeNode->payload.pointerCall.sourcePos);
    }
}

void EmitPostScriptThreadCall(coduo_scr_ast_node_t *calleeNode,
                              int32_t argumentCount,
                              qboolean hasMethodContext,
                              uint32_t callSourcePos,
                              uint32_t methodSourcePos)
{
    if (calleeNode->kind == SCR_AST_KIND_SCRIPT_FUNCTION_NAME) {
        EmitPostScriptThread(calleeNode->payload.namedCall.nameNode,
                             argumentCount, hasMethodContext,
                             methodSourcePos);
    } else if (calleeNode->kind == SCR_AST_KIND_FUNCTION_POINTER_CALL) {
        EmitPostScriptThreadPointer(
            calleeNode->payload.pointerCall.objectNode, argumentCount,
            hasMethodContext, calleeNode->payload.pointerCall.sourcePos);
    }

    AddOpcodePos(callSourcePos);
}

void EmitPreFunctionCall(coduo_scr_ast_node_t *callNode)
{
    if (callNode->kind == SCR_AST_KIND_FUNCTION_CALL) {
        EmitOpcode(SCRIPT_OPCODE_THREAD_MARKER, 2, 0);
    }
}

void EmitPostFunctionCall(coduo_scr_ast_node_t *callNode,
                          int32_t argumentCount,
                          qboolean hasMethodContext)
{
    if (callNode->kind == SCR_AST_KIND_FUNCTION_CALL) {
        EmitPostScriptFunctionCall(callNode->payload.call.callee,
                                   argumentCount, hasMethodContext,
                                   callNode->payload.call.sourcePos);
    } else if (callNode->kind == SCR_AST_KIND_METHOD_CALL) {
        EmitPostScriptThreadCall(callNode->payload.call.callee, argumentCount,
                                 hasMethodContext,
                                 callNode->payload.call.sourcePos,
                                 callNode->payload.call.methodSourcePos);
    }
}
