/**************************************************************************/
/*  aethex_vm.cpp                                                         */
/**************************************************************************/
/*                         This file is part of:                          */
/*                          AETHEX ENGINE                                 */
/*                     https://aethex.foundation                          */
/**************************************************************************/
/* Copyright (c) 2026-present AeThex Labs.                                */
/**************************************************************************/

#include "aethex_vm.h"
#include "core/io/file_access.h"
#include "core/os/os.h"

AeThexVM::AeThexVM() {
	reset_stack();
	register_builtin_functions();
}

AeThexVM::~AeThexVM() {
}

void AeThexVM::_bind_methods() {
	ClassDB::bind_method(D_METHOD("set_global", "name", "value"), &AeThexVM::set_global);
	ClassDB::bind_method(D_METHOD("get_global", "name"), &AeThexVM::get_global);
	ClassDB::bind_method(D_METHOD("has_global", "name"), &AeThexVM::has_global);
	ClassDB::bind_method(D_METHOD("get_error"), &AeThexVM::get_error);
	ClassDB::bind_method(D_METHOD("has_error"), &AeThexVM::has_error);
	ClassDB::bind_method(D_METHOD("set_debug_trace", "trace"), &AeThexVM::set_debug_trace);
	
	BIND_ENUM_CONSTANT(INTERPRET_OK);
	BIND_ENUM_CONSTANT(INTERPRET_COMPILE_ERROR);
	BIND_ENUM_CONSTANT(INTERPRET_RUNTIME_ERROR);
}

void AeThexVM::register_builtin_functions() {
	// Register standard math constants and basic functions via globals
	globals["PI"] = Math::PI;
	globals["TAU"] = Math::TAU;
	globals["INF"] = INFINITY;
	globals["E"] = Math::E;
	
	// Built-in types
	globals["String"] = Variant();
	globals["Array"] = Variant();
	globals["Dictionary"] = Variant();
	globals["Vector2"] = Variant();
	globals["Vector3"] = Variant();
	
	// Note: Native math functions like abs, floor, ceil, etc. are provided
	// via the AeThex script library or through Godot's expression evaluator
}

void AeThexVM::reset_stack() {
	stack_top = 0;
	frame_count = 0;
	runtime_error = "";
}

void AeThexVM::push(const Variant &value) {
	if (stack_top >= STACK_MAX) {
		runtime_error_at("Stack overflow");
		return;
	}
	stack[stack_top++] = value;
}

Variant AeThexVM::pop() {
	if (stack_top <= 0) {
		runtime_error_at("Stack underflow");
		return Variant();
	}
	return stack[--stack_top];
}

Variant AeThexVM::peek(int distance) const {
	if (stack_top - 1 - distance < 0) {
		return Variant();
	}
	return stack[stack_top - 1 - distance];
}

void AeThexVM::set_global(const String &name, const Variant &value) {
	globals[name] = value;
}

Variant AeThexVM::get_global(const String &name) const {
	if (globals.has(name)) {
		return globals[name];
	}
	return Variant();
}

bool AeThexVM::has_global(const String &name) const {
	return globals.has(name);
}

void AeThexVM::register_native_function(const String &name, const Callable &callable) {
	native_functions[name] = callable;
}

AeThexVM::InterpretResult AeThexVM::execute(const AeThexCompiler::CompiledScript *p_script) {
	if (!p_script) {
		runtime_error = "No script to execute";
		return INTERPRET_COMPILE_ERROR;
	}
	
	script = p_script;
	reset_stack();
	
	// Set up main frame
	frames[0].chunk = &script->main_chunk;
	frames[0].ip = 0;
	frames[0].stack_base = 0;
	frames[0].function_name = "main";
	frame_count = 1;
	
	return run();
}

AeThexVM::InterpretResult AeThexVM::call(const String &function_name, const Vector<Variant> &args) {
	if (!script) {
		runtime_error = "No script loaded";
		return INTERPRET_COMPILE_ERROR;
	}
	
	if (!script->functions.has(function_name)) {
		runtime_error = "Function not found: " + function_name;
		return INTERPRET_RUNTIME_ERROR;
	}
	
	// Push arguments
	for (const Variant &arg : args) {
		push(arg);
	}
	
	// Set up call frame
	CallFrame *frame = &frames[frame_count++];
	frame->chunk = &script->functions[function_name];
	frame->ip = 0;
	frame->stack_base = stack_top - args.size();
	frame->function_name = function_name;
	
	return run();
}

uint8_t AeThexVM::read_byte() {
	CallFrame *frame = &frames[frame_count - 1];
	return frame->chunk->code[frame->ip++];
}

uint16_t AeThexVM::read_short() {
	CallFrame *frame = &frames[frame_count - 1];
	uint16_t value = frame->chunk->code[frame->ip] |
					 (frame->chunk->code[frame->ip + 1] << 8);
	frame->ip += 2;
	return value;
}

int32_t AeThexVM::read_int() {
	CallFrame *frame = &frames[frame_count - 1];
	int32_t value = frame->chunk->code[frame->ip] |
					(frame->chunk->code[frame->ip + 1] << 8) |
					(frame->chunk->code[frame->ip + 2] << 16) |
					(frame->chunk->code[frame->ip + 3] << 24);
	frame->ip += 4;
	return value;
}

float AeThexVM::read_float() {
	union { float f; int32_t i; } u;
	u.i = read_int();
	return u.f;
}

const String &AeThexVM::read_string() {
	uint8_t idx = read_byte();
	CallFrame *frame = &frames[frame_count - 1];
	return frame->chunk->strings[idx];
}

Variant AeThexVM::read_constant() {
	uint8_t idx = read_byte();
	CallFrame *frame = &frames[frame_count - 1];
	return frame->chunk->constants[idx];
}

bool AeThexVM::call_function(const String &name, int arg_count) {
	if (script->functions.has(name)) {
		if (frame_count >= FRAMES_MAX) {
			runtime_error_at("Stack overflow (call frames)");
			return false;
		}
		
		CallFrame *frame = &frames[frame_count++];
		frame->chunk = &script->functions[name];
		frame->ip = 0;
		frame->stack_base = stack_top - arg_count;
		frame->function_name = name;
		return true;
	}
	
	return call_native(name, arg_count);
}

bool AeThexVM::call_native(const String &name, int arg_count) {
	if (native_functions.has(name)) {
		// Collect arguments
		Array args;
		for (int i = arg_count - 1; i >= 0; i--) {
			args.push_front(peek(i));
		}
		
		// Pop arguments
		for (int i = 0; i < arg_count; i++) {
			pop();
		}
		
		// Call native function
		Variant result;
		Callable::CallError error;
		const Variant *argv[16];
		for (int i = 0; i < MIN(arg_count, 16); i++) {
			argv[i] = &args[i];
		}
		
		native_functions[name].callp(argv, arg_count, result, error);
		
		if (error.error != Callable::CallError::CALL_OK) {
			runtime_error_at("Native function error: " + name);
			return false;
		}
		
		push(result);
		return true;
	}
	
	runtime_error_at("Undefined function: " + name);
	return false;
}

void AeThexVM::runtime_error_at(const String &message) {
	CallFrame *frame = &frames[frame_count - 1];
	runtime_error = message + " at " + frame->function_name + ":" + itos(frame->ip);
}

AeThexVM::InterpretResult AeThexVM::run() {
	CallFrame *frame = &frames[frame_count - 1];
	
	#define READ_BYTE() (frame->chunk->code[frame->ip++])
	#define READ_SHORT() (frame->ip += 2, (uint16_t)(frame->chunk->code[frame->ip - 2] | (frame->chunk->code[frame->ip - 1] << 8)))
	#define BINARY_OP(op) \
		do { \
			Variant b = pop(); \
			Variant a = pop(); \
			push(Variant::evaluate(Variant::op, a, b)); \
		} while (false)
	
	while (true) {
		if (frame->ip >= frame->chunk->code.size()) {
			break;
		}
		
		if (debug_trace) {
			print_stack();
		}
		
		uint8_t instruction = READ_BYTE();
		
		switch (instruction) {
			case AeThexCompiler::OP_PUSH_NULL:
				push(Variant());
				break;
				
			case AeThexCompiler::OP_PUSH_BOOL:
				push(READ_BYTE() != 0);
				break;
				
			case AeThexCompiler::OP_PUSH_INT: {
				int32_t value = frame->chunk->code[frame->ip] |
							   (frame->chunk->code[frame->ip + 1] << 8) |
							   (frame->chunk->code[frame->ip + 2] << 16) |
							   (frame->chunk->code[frame->ip + 3] << 24);
				frame->ip += 4;
				push(value);
				break;
			}
			
			case AeThexCompiler::OP_PUSH_FLOAT: {
				union { float f; int32_t i; } u;
				u.i = frame->chunk->code[frame->ip] |
					  (frame->chunk->code[frame->ip + 1] << 8) |
					  (frame->chunk->code[frame->ip + 2] << 16) |
					  (frame->chunk->code[frame->ip + 3] << 24);
				frame->ip += 4;
				push(u.f);
				break;
			}
			
			case AeThexCompiler::OP_PUSH_STRING: {
				uint8_t idx = READ_BYTE();
				if (idx < frame->chunk->strings.size()) {
					push(frame->chunk->strings[idx]);
				} else {
					push("");
				}
				break;
			}
			
			case AeThexCompiler::OP_POP:
				pop();
				break;
				
			case AeThexCompiler::OP_DUP:
				push(peek());
				break;
				
			case AeThexCompiler::OP_LOAD_LOCAL: {
				uint8_t slot = READ_BYTE();
				push(stack[frame->stack_base + slot]);
				break;
			}
			
			case AeThexCompiler::OP_STORE_LOCAL: {
				uint8_t slot = READ_BYTE();
				stack[frame->stack_base + slot] = peek();
				break;
			}
			
			case AeThexCompiler::OP_LOAD_GLOBAL: {
				uint8_t idx = READ_BYTE();
				String name = frame->chunk->strings[idx];
				if (globals.has(name)) {
					push(globals[name]);
				} else {
					push(Variant());
				}
				break;
			}
			
			case AeThexCompiler::OP_STORE_GLOBAL: {
				uint8_t idx = READ_BYTE();
				String name = frame->chunk->strings[idx];
				globals[name] = peek();
				break;
			}
			
			case AeThexCompiler::OP_ADD:
				BINARY_OP(OP_ADD);
				break;
				
			case AeThexCompiler::OP_SUB:
				BINARY_OP(OP_SUBTRACT);
				break;
				
			case AeThexCompiler::OP_MUL:
				BINARY_OP(OP_MULTIPLY);
				break;
				
			case AeThexCompiler::OP_DIV:
				BINARY_OP(OP_DIVIDE);
				break;
				
			case AeThexCompiler::OP_MOD:
				BINARY_OP(OP_MODULE);
				break;
				
			case AeThexCompiler::OP_NEG: {
				Variant a = pop();
				push(Variant::evaluate(Variant::OP_NEGATE, a, Variant()));
				break;
			}
			
			case AeThexCompiler::OP_EQ:
				BINARY_OP(OP_EQUAL);
				break;
				
			case AeThexCompiler::OP_NE:
				BINARY_OP(OP_NOT_EQUAL);
				break;
				
			case AeThexCompiler::OP_LT:
				BINARY_OP(OP_LESS);
				break;
				
			case AeThexCompiler::OP_LE:
				BINARY_OP(OP_LESS_EQUAL);
				break;
				
			case AeThexCompiler::OP_GT:
				BINARY_OP(OP_GREATER);
				break;
				
			case AeThexCompiler::OP_GE:
				BINARY_OP(OP_GREATER_EQUAL);
				break;
				
			case AeThexCompiler::OP_NOT: {
				Variant a = pop();
				push(Variant::evaluate(Variant::OP_NOT, a, Variant()));
				break;
			}
			
			case AeThexCompiler::OP_AND:
				BINARY_OP(OP_AND);
				break;
				
			case AeThexCompiler::OP_OR:
				BINARY_OP(OP_OR);
				break;
				
			case AeThexCompiler::OP_JUMP: {
				uint16_t offset = READ_SHORT();
				frame->ip += offset;
				break;
			}
			
			case AeThexCompiler::OP_JUMP_IF: {
				uint16_t offset = READ_SHORT();
				if (peek().booleanize()) {
					frame->ip += offset;
				}
				break;
			}
			
			case AeThexCompiler::OP_JUMP_IF_NOT: {
				uint16_t offset = READ_SHORT();
				if (!peek().booleanize()) {
					frame->ip += offset;
				}
				break;
			}
			
			case AeThexCompiler::OP_LOOP: {
				uint16_t offset = READ_SHORT();
				frame->ip -= offset;
				break;
			}
			
			case AeThexCompiler::OP_CALL: {
				uint8_t arg_count = READ_BYTE();
				Variant callee = peek(arg_count);
				
				if (callee.get_type() == Variant::STRING) {
					if (!call_function(callee, arg_count)) {
						return INTERPRET_RUNTIME_ERROR;
					}
					frame = &frames[frame_count - 1];
				} else if (callee.get_type() == Variant::CALLABLE) {
					// Direct callable
					Array args;
					for (int i = arg_count - 1; i >= 0; i--) {
						args.push_front(peek(i));
					}
					for (int i = 0; i < arg_count + 1; i++) {
						pop();
					}
					
					Variant result = callee.call("call_funcv", args);
					push(result);
				} else {
					runtime_error_at("Can only call functions");
					return INTERPRET_RUNTIME_ERROR;
				}
				break;
			}
			
			case AeThexCompiler::OP_RETURN: {
				Variant result = pop();
				
				// Pop locals
				stack_top = frame->stack_base;
				
				frame_count--;
				if (frame_count == 0) {
					push(result);
					return INTERPRET_OK;
				}
				
				push(result);
				frame = &frames[frame_count - 1];
				break;
			}
			
			case AeThexCompiler::OP_NEW_ARRAY: {
				uint8_t count = READ_BYTE();
				Array arr;
				for (int i = count - 1; i >= 0; i--) {
					arr.push_front(peek(i));
				}
				for (int i = 0; i < count; i++) {
					pop();
				}
				push(arr);
				break;
			}
			
			case AeThexCompiler::OP_NEW_DICT: {
				uint8_t count = READ_BYTE();
				Dictionary dict;
				for (int i = (count * 2) - 1; i >= 0; i -= 2) {
					dict[peek(i)] = peek(i - 1);
				}
				for (int i = 0; i < count * 2; i++) {
					pop();
				}
				push(dict);
				break;
			}
			
			case AeThexCompiler::OP_INDEX_GET: {
				Variant index = pop();
				Variant container = pop();
				bool valid = false;
				Variant result = container.get(index, &valid);
				if (!valid) {
					runtime_error_at("Invalid index access");
					return INTERPRET_RUNTIME_ERROR;
				}
				push(result);
				break;
			}
			
			case AeThexCompiler::OP_INDEX_SET: {
				Variant value = pop();
				Variant index = pop();
				Variant container = pop();
				bool valid = false;
				container.set(index, value, &valid);
				if (!valid) {
					runtime_error_at("Invalid index assignment");
					return INTERPRET_RUNTIME_ERROR;
				}
				push(value);
				break;
			}
			
			case AeThexCompiler::OP_SYNC: {
				// Cross-platform sync operation
				// In engine: just a barrier/wait point
				Variant data = pop();
				// TODO: Implement actual sync logic
				push(data);
				break;
			}
			
			case AeThexCompiler::OP_BEACON_EMIT: {
				uint8_t idx = READ_BYTE();
				String beacon_name = frame->chunk->strings[idx];
				// TODO: Emit beacon signal
				break;
			}
			
			case AeThexCompiler::OP_NOTIFY: {
				Variant message = pop();
				print_line("[AeThex] " + message.stringify());
				break;
			}
			
			case AeThexCompiler::OP_AWAIT: {
				// TODO: Implement async/await
				break;
			}
			
			default:
				runtime_error_at("Unknown opcode: " + itos(instruction));
				return INTERPRET_RUNTIME_ERROR;
		}
	}
	
	#undef READ_BYTE
	#undef READ_SHORT
	#undef BINARY_OP
	
	return INTERPRET_OK;
}

void AeThexVM::print_stack() const {
	print_line("          Stack: ");
	for (int i = 0; i < stack_top; i++) {
		print_line("          [ " + stack[i].stringify() + " ]");
	}
}

void AeThexVM::disassemble_chunk(const AeThexCompiler::Chunk &chunk, const String &name) const {
	print_line("== " + name + " ==");
	
	int offset = 0;
	while (offset < chunk.code.size()) {
		uint8_t instruction = chunk.code[offset];
		String line = vformat("%04d ", offset);
		line += AeThexCompiler::opcode_name((AeThexCompiler::Opcode)instruction);
		
		switch (instruction) {
			case AeThexCompiler::OP_PUSH_BOOL:
			case AeThexCompiler::OP_LOAD_LOCAL:
			case AeThexCompiler::OP_STORE_LOCAL:
			case AeThexCompiler::OP_LOAD_GLOBAL:
			case AeThexCompiler::OP_STORE_GLOBAL:
			case AeThexCompiler::OP_PUSH_STRING:
			case AeThexCompiler::OP_CALL:
				offset++;
				line += " " + itos(chunk.code[offset]);
				break;
				
			case AeThexCompiler::OP_PUSH_INT:
			case AeThexCompiler::OP_PUSH_FLOAT:
				offset += 4;
				break;
				
			case AeThexCompiler::OP_JUMP:
			case AeThexCompiler::OP_JUMP_IF:
			case AeThexCompiler::OP_JUMP_IF_NOT:
			case AeThexCompiler::OP_LOOP:
				offset += 2;
				break;
				
			default:
				break;
		}
		
		print_line(line);
		offset++;
	}
}
