// AVM ISA v1 — C++ Port (Strict Functional Execution)
#include <cstdint>
#include <vector>
#include <optional>
#include <variant>
#include <stdexcept>
#include <functional>

constexpr int32_t Q16_SCALE = 65536;

inline int32_t q16_mul(int32_t a, int32_t b) {
    return static_cast<int32_t>(
        (static_cast<int64_t>(a) * static_cast<int64_t>(b)) / Q16_SCALE);
}
inline int32_t q16_div(int32_t a, int32_t b) {
    if (b == 0) throw std::runtime_error("div by zero");
    return static_cast<int32_t>(
        (static_cast<int64_t>(a) * Q16_SCALE) / b);
}

// ── Value ─────────────────────────────────────────
struct AvmVal {
    enum Ty { Q0_16, Q16_16, Bool };
    Ty ty;
    int32_t raw;
    AvmVal(Ty t, int32_t r) : ty(t), raw(r) {}
    static AvmVal q16(int32_t x) { return {Q16_16, x}; }
    static AvmVal q0(int32_t x) {
        if (x < -32767) x = -32767;
        if (x > 32767) x = 32767;
        return {Q0_16, x};
    }
    static AvmVal boolean(bool x) { return {Bool, x ? 1 : 0}; }
};

// ── Primitives ─────────────────────────────────────
enum class Prim : int32_t {
    AddSatQ0, SubSatQ0, AddSatQ16, SubSatQ16, MulSatQ16, DivSatQ16,
    LtQ16, EqQ16, And, Or, Not
};

AvmVal exec_prim(Prim op, const AvmVal& a, const std::optional<AvmVal>& b) {
    auto bv = [&]() -> const AvmVal& { return b.value(); };
    switch (op) {
    case Prim::AddSatQ16: return AvmVal::q16(a.raw + bv().raw);
    case Prim::SubSatQ16: return AvmVal::q16(a.raw - bv().raw);
    case Prim::MulSatQ16: return AvmVal::q16(q16_mul(a.raw, bv().raw));
    case Prim::DivSatQ16: return AvmVal::q16(q16_div(a.raw, bv().raw));
    case Prim::LtQ16: return AvmVal::boolean(a.raw < bv().raw);
    case Prim::EqQ16: return AvmVal::boolean(a.raw == bv().raw);
    case Prim::And: return AvmVal::boolean(a.raw && bv().raw);
    case Prim::Or: return AvmVal::boolean(a.raw || bv().raw);
    case Prim::Not: return AvmVal::boolean(!a.raw);
    default: throw std::runtime_error("unknown prim");
    }
}

// ── Instruction ────────────────────────────────────
enum class InstrOp : int8_t {
    PushQ16, PushBool, Pop, Dup, Swap, Load, Store, Jump, JumpIf, Prim, Halt
};

struct Instr {
    InstrOp op;
    int32_t arg;
    bool arg2{false};
};

inline Instr push_q16(int32_t x) { return {InstrOp::PushQ16, x, false}; }
inline Instr push_bool(bool x) { return {InstrOp::PushBool, static_cast<int32_t>(x), true}; }
inline Instr pop() { return {InstrOp::Pop, 0, false}; }
inline Instr dup() { return {InstrOp::Dup, 0, false}; }
inline Instr swap() { return {InstrOp::Swap, 0, false}; }
inline Instr load(int i) { return {InstrOp::Load, i, false}; }
inline Instr store(int i) { return {InstrOp::Store, i, false}; }
inline Instr jump(int t) { return {InstrOp::Jump, t, false}; }
inline Instr jump_if(int t) { return {InstrOp::JumpIf, t, false}; }
inline Instr prim(Prim p) { return {InstrOp::Prim, static_cast<int32_t>(p), false}; }
inline Instr halt() { return {InstrOp::Halt, 0, false}; }

// ── State ──────────────────────────────────────────
struct State {
    int pc{0};
    std::vector<AvmVal> stack;
    std::vector<std::optional<AvmVal>> locals;
    bool halted{false};

    State() : pc(0), locals(16, std::nullopt) {}
    State(int pc_, std::vector<AvmVal> stack_, std::vector<std::optional<AvmVal>> locals_, bool halted_)
        : pc(pc_), stack(std::move(stack_)), locals(std::move(locals_)), halted(halted_) {}
};

// ── Step ───────────────────────────────────────────
State step(const State& s, const std::vector<Instr>& prog) {
    if (s.halted) throw std::runtime_error("halted");
    if (s.pc < 0 || s.pc >= (int)prog.size())
        return State{};

    auto instr = prog[s.pc];
    State ns{s.pc + 1, s.stack, s.locals, false};
    auto& st = ns.stack;

    switch (instr.op) {
    case InstrOp::PushQ16: st.push_back(AvmVal::q16(instr.arg)); break;
    case InstrOp::PushBool: st.push_back(AvmVal::boolean(instr.arg)); break;
    case InstrOp::Pop: st.pop_back(); break;
    case InstrOp::Dup: st.push_back(st.back()); break;
    case InstrOp::Swap: std::swap(st[st.size()-2], st[st.size()-1]); break;
    case InstrOp::Load: {
        auto v = ns.locals[instr.arg];
        if (!v.has_value()) throw std::runtime_error("missing local");
        st.push_back(v.value());
        break;
    }
    case InstrOp::Store: {
        ns.locals[instr.arg] = st.back(); st.pop_back();
        break;
    }
    case InstrOp::Jump: ns.pc = instr.arg; break;
    case InstrOp::JumpIf: {
        if (st.back().raw) ns.pc = instr.arg;
        st.pop_back();
        break;
    }
    case InstrOp::Prim: {
        auto p = static_cast<Prim>(instr.arg);
        int arity = (p == Prim::Not) ? 1 : 2;
        std::optional<AvmVal> b;
        if (arity == 2) { b = st.back(); st.pop_back(); }
        auto a = st.back(); st.pop_back();
        st.push_back(exec_prim(p, a, b));
        break;
    }
    case InstrOp::Halt: ns.halted = true; break;
    }
    return ns;
}

State run(const State& init, const std::vector<Instr>& prog, int fuel) {
    State s = init;
    for (int i = 0; i < fuel && !s.halted; i++)
        s = step(s, prog);
    return s;
}
