// AVM ISA v1 — Scala Port (Strict Functional Execution)
package silversight.avm

object AVM {
  // ── Constants ──────────────────────────────────────────────────
  val AVMClampMin = -2147483647
  val AVMClampMax = 2147483647
  val AVMQ0Min = -32767
  val AVMQ0Max = 32767
  val Q16Scale = 65536
  val AVMMaxStack = 1024

  def avmClamp(x: Long): Int = math.min(AVMClampMax.toLong, math.max(AVMClampMin.toLong, x)).toInt
  def avmQ0Clamp(x: Long): Int = math.min(AVMQ0Max.toLong, math.max(AVMQ0Min.toLong, x)).toInt

  def floorDiv(a: Long, b: Long): Int = {
    if (b == 0) throw new ArithmeticException("division by zero")
    val q = a / b; val r = a % b
    (if (r != 0 && ((a ^ b) < 0)) q - 1 else q).toInt
  }

  def ltQ16V6(a: Int, b: Int): Boolean = {
    val sa = a < 0; val sb = b < 0
    if (sa != sb) sa else a < b
  }

  // ── Types ────────────────────────────────────────────────────
  sealed trait Ty
  case object Q0_16 extends Ty; case object Q16_16 extends Ty; case object BoolTy extends Ty

  case class Val(ty: Ty, i: Int = 0, b: Boolean = false)
  object Val { def q16(x: Int) = Val(Q16_16, i = avmClamp(x))
    def q0(x: Int) = Val(Q0_16, i = avmQ0Clamp(x))
    def bool(x: Boolean) = Val(BoolTy, b = x) }

  // ── Primitives ──────────────────────────────────────────────
  sealed trait Prim
  case object AddSatQ0 extends Prim; case object SubSatQ0 extends Prim
  case object AddSatQ16 extends Prim; case object SubSatQ16 extends Prim
  case object MulSatQ16 extends Prim; case object DivSatQ16 extends Prim
  case object LtQ16 extends Prim; case object EqQ16 extends Prim
  case object And extends Prim; case object Or extends Prim; case object Not extends Prim

  def primArity(p: Prim): Int = if (p == Not) 1 else 2

  def execPrim(p: Prim, a: Val, b: Val): Val = (p, a.ty, b.ty) match {
    case (AddSatQ0, Q0_16, Q0_16) => Val.q0(avmQ0Clamp(a.i.toLong + b.i))
    case (SubSatQ0, Q0_16, Q0_16) => Val.q0(avmQ0Clamp(a.i.toLong - b.i))
    case (AddSatQ16, Q16_16, Q16_16) => Val.q16(avmClamp(a.i.toLong + b.i))
    case (SubSatQ16, Q16_16, Q16_16) => Val.q16(avmClamp(a.i.toLong - b.i))
    case (MulSatQ16, Q16_16, Q16_16) => Val.q16(avmClamp(floorDiv(a.i.toLong * b.i, Q16Scale)))
    case (DivSatQ16, Q16_16, Q16_16) => Val.q16(avmClamp(floorDiv(a.i.toLong * Q16Scale, b.i)))
    case (LtQ16, Q16_16, Q16_16) => Val.bool(ltQ16V6(a.i, b.i))
    case (EqQ16, Q16_16, Q16_16) => Val.bool(a.i == b.i)
    case (And, BoolTy, BoolTy) => Val.bool(a.b && b.b)
    case (Or, BoolTy, BoolTy) => Val.bool(a.b || b.b)
    case (Not, BoolTy, _) => Val.bool(!a.b)
    case _ => throw new RuntimeException("type mismatch")
  }

  // ── Instructions ────────────────────────────────────────────
  sealed trait Op
  case class PushQ16(x: Int) extends Op; case class PushBool(b: Boolean) extends Op
  case class PushQ0(x: Int) extends Op; case object Pop extends Op
  case object Dup extends Op; case object Swap extends Op
  case class Load(i: Int) extends Op; case class Store(i: Int) extends Op
  case class Jump(t: Int) extends Op; case class JumpIf(t: Int) extends Op
  case class Primitive(p: Prim) extends Op; case object Halt extends Op

  // ── State ───────────────────────────────────────────────────
  case class State(pc: Int, stack: List[Val], locals: Vector[Option[Val]], halted: Boolean)
  def initState(nLocals: Int = 0): State = State(0, Nil, Vector.fill(nLocals)(None), false)

  // ── Step ────────────────────────────────────────────────────
  def step(s: State, prog: Vector[Op]): Option[State] = {
    if (s.halted) return None
    if (s.pc < 0 || s.pc >= prog.length) return Some(State(s.pc, s.stack, s.locals, true))

    val instr = prog(s.pc); var stack = s.stack; val npc = s.pc + 1

    val growing = instr match {
      case _: PushQ16 | _: PushBool | _: PushQ0 | Dup | Load => true; case _ => false
    }
    if (growing && stack.length >= AVMMaxStack) return None

    instr match {
      case PushQ16(x) => stack = Val.q16(x) :: stack
      case PushBool(b) => stack = Val.bool(b) :: stack
      case PushQ0(x) => stack = Val.q0(x) :: stack
      case Pop => stack match {
        case Nil => return None; case _ :: xs => stack = xs}
      case Dup => stack match {
        case Nil => return None; case x :: xs => stack = x :: x :: xs}
      case Swap => stack match {
        case a :: b :: xs => stack = b :: a :: xs; case _ => return None}
      case Load(i) =>
        if (i >= s.locals.length || s.locals(i).isEmpty) return None
        stack = s.locals(i).get :: stack
      case Store(i) => stack match {
        case Nil => return None
        case v :: xs =>
          if (i >= s.locals.length) return None
          val newLocals = s.locals.updated(i, Some(v))
          return Some(State(npc, xs, newLocals, false))
      }
      case Jump(t) => if (t < 0 || t >= prog.length) return None
        else return Some(State(t, stack, s.locals, false))
      case JumpIf(t) => stack match {
        case Val(BoolTy, _, true) :: xs =>
          if (t < 0 || t >= prog.length) return None
          return Some(State(t, xs, s.locals, false))
        case Val(BoolTy, _, false) :: xs => stack = xs
        case _ => return None
      }
      case Primitive(p) =>
        val arity = primArity(p)
        if (stack.length < arity) return None
        val b = if (arity >= 2) { val (v, rest) = (stack.head, stack.tail); stack = rest; v } else Val.bool(false)
        val a = stack.head; stack = stack.tail
        try { stack = execPrim(p, a, b) :: stack } catch { case _: Throwable => return None }
      case Halt => return Some(State(s.pc, stack, s.locals, true))
    }
    Some(State(npc, stack, s.locals, false))
  }

  // ── Run (fuel-bounded) ─────────────────────────────────────
  @annotation.tailrec
  def run(s: State, prog: Vector[Op], fuel: Int = 10000): Option[State] = {
    if (fuel <= 0 || s.halted) return Some(s)
    step(s, prog) match {
      case None => None
      case Some(next) => run(next, prog, fuel - 1)
    }
  }
}
