! AVM ISA v1 — Fortran Port (Strict Functional Execution)
module avm
  implicit none

  integer, parameter :: Q16_SCALE = 65536
  integer, parameter :: MAX_STACK = 1024
  integer, parameter :: MAX_LOCALS = 16
  integer, parameter :: MAX_PROG = 256

  ! Value type codes
  integer, parameter :: VAL_Q16 = 0, VAL_BOOL = 1

  type :: AvmVal
    integer :: ty = VAL_Q16
    integer :: val = 0
  end type

  ! Primitive codes
  integer, parameter :: PRIM_ADD = 0, PRIM_SUB = 1, PRIM_MUL = 2, PRIM_DIV = 3
  integer, parameter :: PRIM_LT = 4, PRIM_EQ = 5, PRIM_AND = 6, PRIM_OR = 7, PRIM_NOT = 8

  ! Instruction opcodes
  integer, parameter :: I_PUSH_Q16 = 0, I_PUSH_BOOL = 1, I_POP = 2, I_DUP = 3
  integer, parameter :: I_SWAP = 4, I_LOAD = 5, I_STORE = 6, I_JUMP = 7
  integer, parameter :: I_JUMP_IF = 8, I_PRIM = 9, I_HALT = 10

  type :: Instr
    integer :: op = 0
    integer :: arg = 0
  end type

  type :: State
    integer :: pc = 0
    type(AvmVal) :: stack(MAX_STACK)
    integer :: sp = 0  ! stack pointer
    type(AvmVal) :: locals(MAX_LOCALS)
    logical :: halted = .false.
  end type

contains

  function q16_mul(a, b) result(r)
    integer, intent(in) :: a, b
    integer :: r
    r = int((int(a, 8) * int(b, 8)) / Q16_SCALE)
  end function

  function q16_div(a, b) result(r)
    integer, intent(in) :: a, b
    integer :: r
    if (b == 0) then
      r = 2147483647
      return
    end if
    r = int((int(a, 8) * Q16_SCALE) / int(b, 8))
  end function

  function make_q16(x) result(v)
    integer, intent(in) :: x
    type(AvmVal) :: v
    v%ty = VAL_Q16; v%val = x
  end function

  function make_bool(x) result(v)
    logical, intent(in) :: x
    type(AvmVal) :: v
    v%ty = VAL_BOOL
    if (x) then; v%val = 1; else; v%val = 0; end if
  end function

  function make_instr(op, arg) result(i)
    integer, intent(in) :: op, arg
    type(Instr) :: i
    i%op = op; i%arg = arg
  end function

  subroutine push(s, v)
    type(State), intent(inout) :: s
    type(AvmVal), intent(in) :: v
    s%sp = s%sp + 1
    s%stack(s%sp) = v
  end subroutine

  function pop(s) result(v)
    type(State), intent(inout) :: s
    type(AvmVal) :: v
    v = s%stack(s%sp)
    s%sp = s%sp - 1
  end function

  function step(state, prog, prog_len) result(ns)
    type(State), intent(in) :: state
    type(Instr), intent(in) :: prog(MAX_PROG)
    integer, intent(in) :: prog_len
    type(State) :: ns
    type(AvmVal) :: a, b, result
    integer :: arity

    ns = state
    if (ns%halted) return
    if (ns%pc < 0 .or. ns%pc >= prog_len) then
      ns%halted = .true.; return
    end if

    select case (prog(ns%pc + 1)%op)  ! +1 for 1-indexed
    case (I_PUSH_Q16)
      call push(ns, make_q16(prog(ns%pc + 1)%arg))
    case (I_PUSH_BOOL)
      call push(ns, make_bool(prog(ns%pc + 1)%arg /= 0))
    case (I_POP)
      a = pop(ns)
    case (I_DUP)
      a = ns%stack(ns%sp)
      call push(ns, a)
    case (I_SWAP)
      a = pop(ns); b = pop(ns)
      call push(ns, a); call push(ns, b)
    case (I_LOAD)
      call push(ns, ns%locals(prog(ns%pc + 1)%arg + 1))
    case (I_STORE)
      ns%locals(prog(ns%pc + 1)%arg + 1) = pop(ns)
    case (I_JUMP)
      ns%pc = prog(ns%pc + 1)%arg - 1; return
    case (I_JUMP_IF)
      a = pop(ns)
      if (a%val /= 0) ns%pc = prog(ns%pc + 1)%arg - 1
    case (I_PRIM)
      arity = 2
      if (prog(ns%pc + 1)%arg == PRIM_NOT) arity = 1
      if (arity == 2) b = pop(ns)
      a = pop(ns)
      select case (prog(ns%pc + 1)%arg)
      case (PRIM_ADD); result = make_q16(a%val + b%val)
      case (PRIM_SUB); result = make_q16(a%val - b%val)
      case (PRIM_MUL); result = make_q16(q16_mul(a%val, b%val))
      case (PRIM_DIV); result = make_q16(q16_div(a%val, b%val))
      case (PRIM_LT);  result = make_bool(a%val < b%val)
      case (PRIM_EQ);  result = make_bool(a%val == b%val)
      case (PRIM_AND); result = make_bool(a%val /= 0 .and. b%val /= 0)
      case (PRIM_OR);  result = make_bool(a%val /= 0 .or. b%val /= 0)
      case (PRIM_NOT); result = make_bool(a%val == 0)
      end select
      call push(ns, result)
    case (I_HALT)
      ns%halted = .true.
    end select
    ns%pc = ns%pc + 1
  end function

  subroutine run(init, prog, prog_len, fuel, out_state)
    type(State), intent(in) :: init
    type(Instr), intent(in) :: prog(MAX_PROG)
    integer, intent(in) :: prog_len, fuel
    type(State), intent(out) :: out_state
    type(State) :: s
    integer :: i

    s = init
    do i = 1, fuel
      if (s%halted) exit
      s = step(s, prog, prog_len)
    end do
    out_state = s
  end subroutine

end module
