///|
/// @types.Value stack for operands
struct Stack {
  values : Array[@types.Value]
  limit : Int32
  mut reserved : Int32
  mut sp : Int32 // stack pointer
} derive(Debug)

///|
pub fn Stack::Stack(max_values~ : Int32 = 10000) -> Stack {
  { values: [], sp: 0, limit: max_values, reserved: 0, }
}

///|
pub fn Stack::push(
  self : Stack,
  value : @types.Value,
) -> Unit raise RuntimeError {
  if self.sp >= self.limit - self.reserved {
    raise StackOverflow
  }
  if self.sp >= self.values.length() {
    self.values.push(value)
  } else {
    self.values[self.sp] = value
  }
  self.sp = self.sp + 1
}

///|
pub fn Stack::pop(self : Stack) -> @types.Value raise RuntimeError {
  if self.sp <= 0 {
    raise StackUnderflow
  }
  self.sp = self.sp - 1
  let value = self.values[self.sp]
  self.values[self.sp] = I32(0)
  value
}

///|
pub fn Stack::peek(self : Stack) -> @types.Value raise RuntimeError {
  if self.sp <= 0 {
    raise StackUnderflow
  }
  self.values[self.sp - 1]
}

///|
pub fn Stack::pop_i32(self : Stack) -> Int32 raise RuntimeError {
  match self.pop() {
    I32(n) => n
    v => {
      @logger.debug("type mismatch: expected i32, got \{v}")
      raise TypeMismatch
    }
  }
}

///|
pub fn Stack::pop_i64(self : Stack) -> Int64 raise RuntimeError {
  match self.pop() {
    I64(n) => n
    v => {
      @logger.debug("type mismatch: expected i64, got \{v}")
      raise TypeMismatch
    }
  }
}

///|
pub fn Stack::pop_f32(self : Stack) -> Float raise RuntimeError {
  match self.pop() {
    F32(f) => f
    v => {
      @logger.debug("type mismatch: expected f32, got \{v}")
      raise TypeMismatch
    }
  }
}

///|
pub fn Stack::pop_f64(self : Stack) -> Double raise RuntimeError {
  match self.pop() {
    F64(f) => f
    v => {
      @logger.debug("type mismatch: expected f64, got \{v}")
      raise TypeMismatch
    }
  }
}

///|
pub fn Stack::push_v128(self : Stack, v : Bytes) -> Unit raise RuntimeError {
  self.push(V128(v))
}

///|
pub fn Stack::pop_v128(self : Stack) -> Bytes raise RuntimeError {
  match self.pop() {
    V128(v) => v
    v => {
      @logger.debug("type mismatch: expected v128, got \{v}")
      raise TypeMismatch
    }
  }
}

///|
pub fn Stack::size(self : Stack) -> Int32 {
  self.sp
}

///|
/// Snapshot currently live stack values [0, sp)
pub fn Stack::active_values(self : Stack) -> Array[@types.Value] {
  let active : Array[@types.Value] = []
  for i in 0.. Unit {
  self.sp = 0
  self.reserved = 0
  self.values.clear()
}

///|
/// Pop n values from the stack, returning them in order (first popped is last in array)
pub fn Stack::pop_n(
  self : Stack,
  n : Int32,
) -> Array[@types.Value] raise RuntimeError {
  if n <= 0 {
    return []
  }
  if self.sp < n {
    raise StackUnderflow
  }
  let values : Array[@types.Value] = Array::make(n, I32(0))
  // Pop in reverse order so the array maintains stack order
  for i = n - 1; i >= 0; i = i - 1 {
    self.sp = self.sp - 1
    values[i] = self.values[self.sp]
    self.values[self.sp] = I32(0)
  }
  values
}

///|
/// Count frame locals against the same budget as live operand slots.
pub fn Stack::reserve(self : Stack, count : Int32) -> Unit raise RuntimeError {
  if count < 0 || count > self.limit - self.reserved - self.sp {
    raise StackOverflow
  }
  self.reserved += count
}

///|
pub fn Stack::release(self : Stack, count : Int32) -> Unit {
  self.reserved -= count
}