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

using namespace sliger;

void VM::run_until_done()
{
    while (!done()) {
        run_instruction();
    }
}

void VM::run_n_instructions(size_t amount)
{
    for (size_t i = 0; !done() and i < amount; ++i) {
        run_instruction();
    }
}

void VM::run_instruction()
{
    std::cout << "stack:\n";
    this->print_stack();
    std::cout << std::format("pc = {}\n", this->pc);
    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) {
                arguments.push_back(stack_pop());
            }
            stack_push(Ptr { .value = this->pc });
            stack_push(Ptr { .value = this->bp });
            for (size_t i = arguments.size(); i > 0; --i) {
                stack_push(std::move(arguments.at(i - 1)));
            }
            this->pc = fn_ptr.as_ptr().value;
            this->bp = static_cast<uint32_t>(this->stack.size());
            if (this->opts.flame_graph) {
                this->flame_graph.report_call(
                    fn_ptr.as_ptr().value, this->instruction_counter);
            }
            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;
            if (this->opts.flame_graph) {
                this->flame_graph.report_return(this->instruction_counter);
            }
            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 addr = stack_pop();
            auto cond = 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;
        }
        case Op::SourceMap: {
            assert_program_has(3);
            auto index = eat_int32();
            auto line = eat_int32();
            auto col = eat_int32();
            if (opts.code_coverage) {
                this->code_coverage.report_cover(this->current_pos);
            }
            this->current_pos = { index, line, col };
            break;
        }
    }
    this->instruction_counter += 1;
}