#include "vm.hpp"
#include "arch.hpp"
#include <cstdint>
#include <cstdlib>
#include <format>
#include <iostream>
#include <utility>
#include <vector>

using namespace sliger;

void VM::run()
{
    while (!done()) {
        auto op = eat_op();
        switch (op) {
            case Op::Nop:
                // nothing
                break;
            case Op::PushNull:
                this->stack.push_back(Null {});
                break;
            case Op::PushInt: {
                assert_program_has(1);
                auto value = eat_int32();
                this->stack.push_back(Int { value });
                break;
            }
            case Op::PushBool: {
                assert_program_has(1);
                auto value = eat_int32();
                this->stack.push_back(Bool { .value = value != 0 });
                break;
            }
            case Op::PushString: {
                assert_program_has(1);
                auto string_length = eat_uint32();
                assert_program_has(string_length);
                auto value = std::string();
                for (uint32_t i = 0; i < string_length; ++i) {
                    auto ch = eat_uint32();
                    value.push_back(static_cast<char>(ch));
                }
                stack_push(String { .value = std::move(value) });
                break;
            }
            case Op::PushPtr: {
                assert_program_has(1);
                auto value = eat_uint32();
                this->stack.push_back(Ptr { value });
                break;
            }
            case Op::Pop: {
                assert_stack_has(1);
                this->stack.pop_back();
                break;
            }
            case Op::LoadLocal: {
                auto loc = eat_uint32();
                assert_fn_stack_has(loc);
                auto value = fn_stack_at(loc);
                stack_push(value);
                break;
            }
            case Op::StoreLocal: {
                auto loc = eat_uint32();
                assert_fn_stack_has(loc + 1);
                auto value = stack_pop();
                fn_stack_at(loc) = value;
                break;
            }
            case Op::Call: {
                assert_program_has(1);
                auto arg_count = eat_uint32();
                assert_stack_has(arg_count + 1);
                auto fn_ptr = stack_pop();
                auto arguments = std::vector<Value>();
                for (uint32_t i = 0; i < arg_count; ++i) {
                    auto argument = stack_pop();
                    arguments.push_back(argument);
                }
                stack_push(Ptr { .value = this->pc });
                stack_push(Ptr { .value = this->bp });
                for (size_t i = 0; i < arguments.size(); ++i) {
                    auto argument
                        = std::move(arguments.at(arguments.size() - 1 - i));
                    arguments.pop_back();
                    stack_push(argument);
                }
                this->pc = fn_ptr.as_ptr().value;
                break;
            }
            case Op::Return: {
                assert_stack_has(3);
                auto ret_val = stack_pop();
                auto bp_val = stack_pop();
                auto pc_val = stack_pop();
                this->bp = bp_val.as_ptr().value;
                stack_push(ret_val);
                this->pc = pc_val.as_ptr().value;
                break;
            }
            case Op::Jump: {
                assert_stack_has(1);
                auto addr = stack_pop();
                this->pc = addr.as_ptr().value;
                break;
            }
            case Op::JumpIfFalse: {
                assert_stack_has(2);
                auto cond = stack_pop();
                auto addr = stack_pop();
                if (cond.as_bool().value) {
                    this->pc = addr.as_ptr().value;
                }
                break;
            }
            case Op::Add: {
                assert_stack_has(2);
                auto right = stack_pop().as_int().value;
                auto left = stack_pop().as_int().value;
                auto value = left + right;
                stack_push(Int { .value = value });
                break;
            }
            case Op::Subtract: {
                assert_stack_has(2);
                auto right = stack_pop().as_int().value;
                auto left = stack_pop().as_int().value;
                auto value = left - right;
                stack_push(Int { .value = value });
                break;
            }
            case Op::Multiply: {
                assert_stack_has(2);
                auto right = stack_pop().as_int().value;
                auto left = stack_pop().as_int().value;
                auto value = left * right;
                stack_push(Int { .value = value });
                break;
            }
            case Op::Divide: {
                assert_stack_has(2);
                auto right = stack_pop().as_int().value;
                auto left = stack_pop().as_int().value;
                auto value = left / right;
                stack_push(Int { .value = value });
                break;
            }
            case Op::Remainder: {
                assert_stack_has(2);
                auto right = stack_pop().as_int().value;
                auto left = stack_pop().as_int().value;
                auto value = left % right;
                stack_push(Int { .value = value });
                break;
            }
            case Op::Equal: {
                assert_stack_has(2);
                auto right = stack_pop().as_int().value;
                auto left = stack_pop().as_int().value;
                auto value = left == right;
                stack_push(Bool { .value = value });
                break;
            }
            case Op::LessThan: {
                assert_stack_has(2);
                auto right = stack_pop().as_int().value;
                auto left = stack_pop().as_int().value;
                auto value = left < right;
                stack_push(Bool { .value = value });
                break;
            }
            case Op::And: {
                assert_stack_has(2);
                auto right = stack_pop().as_bool().value;
                auto left = stack_pop().as_bool().value;
                auto value = left && right;
                stack_push(Bool { .value = value });
                break;
            }
            case Op::Or: {
                assert_stack_has(2);
                auto right = stack_pop().as_bool().value;
                auto left = stack_pop().as_bool().value;
                auto value = left || right;
                stack_push(Bool { .value = value });
                break;
            }
            case Op::Xor: {
                assert_stack_has(2);
                auto right = stack_pop().as_bool().value;
                auto left = stack_pop().as_bool().value;
                auto value = (left || !right) || (!left && right);
                stack_push(Bool { .value = value });
                break;
            }
            case Op::Not:
                assert_stack_has(1);
                auto value = !stack_pop().as_bool().value;
                stack_push(Bool { .value = value });
                break;
        }
    }
}