// Copyright 2026 Leo Cheng
// SPDX-License-Identifier: Apache-2.0

///|
/// What the byte before a value can say about it.
///
/// The four flags are the specification's, and they are how a nullable field, a
/// shared value and a value that is simply there are told apart: one byte,
/// before the value, that says which.
pub(all) enum RefFlag {
  /// `0xFD`: there is no value. What a nullable field holds when it holds
  /// nothing.
  Null
  /// `0xFE`: the value is one that has already gone past, and the id says
  /// which one. A reference, and the reason this is not a tagged encoder.
  Ref(id~ : Int32)
  /// `0xFF`: there is a value here, and it is not tracked.
  NotNull
  /// `0x00`: there is a value here, and it may be pointed at; the id is the one
  /// it is given.
  RefValue(id~ : Int32)
} derive(Eq, Debug)

///|
pub extend RefFlag with Eq::{not_equal, equal}

///|
pub extend RefFlag with Debug::{to_repr}

///|
/// Where a value comes from: the bytes, the place in them, and what has been
/// read already.
///
/// The mirror of `Writer`. A reader keeps two things about references: where
/// each one started, so a value can be read again, and the container being
/// built for each one, so a cycle can point at a value that is not finished.
pub(all) struct Reader {
  /// The bytes being read.
  view : BytesView
  /// What this message is read under.
  config : Config
  /// Where the next byte is.
  mut at : Int32
  /// Where the value of each reference id starts.
  refs : Array[Int32]
  /// The container being built for each reference id, before it is filled.
  nodes : Map[Int32, Value]
  /// The structs this reader knows, by the user type ID they are registered
  /// under: same-schema mode is an agreement, and this is where it is kept.
  schemas : Map[Int32, StructMeta]
  /// How deep in a replay this read is: a value being read a second time for a
  /// reference does not get a reference id of its own.
  mut replay : Int32
  /// The offsets of the replays above this one, so a cycle is caught rather
  /// than read for ever.
  replaying : Array[Int32]
  /// How deep in the nesting this read is.
  mut depth : Int32
  /// How many bytes of variable-length payload have been declared so far.
  mut spent : Int32
}

///|
/// A reader at the start of `bytes`, knowing the structs named in `schemas`.
///
/// A struct arrives as a user type ID and nothing else, so the schema it stands
/// for has to be given to the reader before it is met.
pub fn Reader::new(
  bytes : Bytes,
  config~ : Config = Config::new(),
  schemas~ : Array[StructMeta] = [],
) -> Reader {
  let by_user : Map[Int32, StructMeta] = Map([])
  for meta in schemas {
    by_user.set(meta.user(), meta)
  }
  {
    view: bytes[:],
    config,
    at: 0,
    refs: [],
    nodes: Map([]),
    schemas: by_user,
    replay: 0,
    replaying: [],
    depth: 0,
    spent: 0,
  }
}

///|
/// The struct registered under a user type ID, when this reader knows it.
pub fn Reader::schema(self : Reader, user : Int32) -> StructMeta? {
  self.schemas.get(user)
}

///|
/// Where the next byte is.
pub fn Reader::offset(self : Reader) -> Int32 {
  self.at
}

///|
/// How many bytes are still unread.
pub fn Reader::remaining(self : Reader) -> Int32 {
  self.view.length() - self.at
}

///|
/// Whether every byte has been read.
pub fn Reader::is_done(self : Reader) -> Bool {
  self.at >= self.view.length()
}

///|
/// One byte.
pub fn Reader::read_byte(self : Reader) -> Int32 raise {
  if self.at >= self.view.length() {
    raise Refused::Truncated(at=self.at, want=1, got=0)
  }
  let v = self.view.at(self.at).to_int()
  self.at = self.at + 1
  v
}

///|
/// Eight bits, read as signed, which is how the reference flags are written.
pub fn Reader::read_i8(self : Reader) -> Int32 raise {
  let v = self.read_byte()
  if v >= 0x80 {
    v - 0x100
  } else {
    v
  }
}

///|
/// The header byte, checked against what this reader was asked for.
///
/// A message that does not carry the cross-language flag is a message written
/// by the native format, which is a different format.
pub fn Reader::read_head(self : Reader) -> Unit raise {
  let at = self.at
  let v = self.read_byte()
  if self.config.xlang && (v & 0x01) == 0 {
    raise Refused::BadHeader(at~, got=v)
  }
}

///|
/// A boolean: one byte, and everything but `1` is false.
pub fn Reader::read_bool(self : Reader) -> Bool raise {
  let at = self.at
  let v = self.read_byte()
  match v {
    0 => false
    1 => true
    _ => raise Refused::BadEncoding(at~, got=v)
  }
}

///|
/// Eight bits, unsigned.
pub fn Reader::read_u8(self : Reader) -> Int32 raise {
  self.read_byte()
}

///|
/// Sixteen bits, signed, little endian.
pub fn Reader::read_i16(self : Reader) -> Int32 raise {
  match @fixed.read_i16(self.view, at=self.at, order=Little) {
    None => raise Refused::Truncated(at=self.at, want=2, got=self.remaining())
    Some(v) => {
      self.at = self.at + 2
      v
    }
  }
}

///|
/// Sixteen bits, unsigned, little endian.
pub fn Reader::read_u16(self : Reader) -> Int32 raise {
  match @fixed.read_u16(self.view, at=self.at, order=Little) {
    None => raise Refused::Truncated(at=self.at, want=2, got=self.remaining())
    Some(v) => {
      self.at = self.at + 2
      v
    }
  }
}

///|
/// Thirty-two bits, signed, little endian.
pub fn Reader::read_i32(self : Reader) -> Int32 raise {
  match @fixed.read_i32(self.view, at=self.at, order=Little) {
    None => raise Refused::Truncated(at=self.at, want=4, got=self.remaining())
    Some(v) => {
      self.at = self.at + 4
      v
    }
  }
}

///|
/// Thirty-two bits, unsigned, little endian.
pub fn Reader::read_u32(self : Reader) -> UInt32 raise {
  match @fixed.read_u32(self.view, at=self.at, order=Little) {
    None => raise Refused::Truncated(at=self.at, want=4, got=self.remaining())
    Some(v) => {
      self.at = self.at + 4
      v
    }
  }
}

///|
/// Sixty-four bits, signed, little endian.
pub fn Reader::read_i64(self : Reader) -> Int64 raise {
  match @fixed.read_i64(self.view, at=self.at, order=Little) {
    None => raise Refused::Truncated(at=self.at, want=8, got=self.remaining())
    Some(v) => {
      self.at = self.at + 8
      v
    }
  }
}

///|
/// Sixty-four bits, unsigned, little endian.
pub fn Reader::read_u64(self : Reader) -> UInt64 raise {
  match @fixed.read_u64(self.view, at=self.at, order=Little) {
    None => raise Refused::Truncated(at=self.at, want=8, got=self.remaining())
    Some(v) => {
      self.at = self.at + 8
      v
    }
  }
}

///|
/// A single-precision float.
pub fn Reader::read_f32(self : Reader) -> Float raise {
  match @fixed.read_f32(self.view, at=self.at, order=Little) {
    None => raise Refused::Truncated(at=self.at, want=4, got=self.remaining())
    Some(v) => {
      self.at = self.at + 4
      v
    }
  }
}

///|
/// A double-precision float.
pub fn Reader::read_f64(self : Reader) -> Double raise {
  match @fixed.read_f64(self.view, at=self.at, order=Little) {
    None => raise Refused::Truncated(at=self.at, want=8, got=self.remaining())
    Some(v) => {
      self.at = self.at + 8
      v
    }
  }
}

///|
/// A base-128 varint.
pub fn Reader::read_var_u64(self : Reader) -> UInt64 raise {
  let at = self.at
  let (v, n) = @leb128.decode(self.view, at=self.at) catch {
    _ => raise Refused::Truncated(at~, want=1, got=self.remaining())
  }
  self.at = self.at + n
  v
}

///|
/// A base-128 varint of thirty-two bits.
pub fn Reader::read_var_u32(self : Reader) -> UInt32 raise {
  self.read_var_u64().to_uint()
}

///|
/// A zigzag-folded varint: the signed half of `write_var_i64`.
pub fn Reader::read_var_i64(self : Reader) -> Int64 raise {
  @leb128.unzigzag(self.read_var_u64())
}

///|
/// The same for thirty-two bits.
pub fn Reader::read_var_i32(self : Reader) -> Int32 raise {
  self.read_var_i64().to_int()
}

///|
/// A sixty-four-bit signed integer that is usually small: four bytes whose low
/// bit says whether eight more follow.
pub fn Reader::read_tagged_i64(self : Reader) -> Int64 raise {
  let first = self.read_i32()
  if (first & 1) == 1 {
    self.read_i64()
  } else {
    first.to_int64() >> 1
  }
}

///|
/// A sixty-four-bit unsigned integer that is usually small.
pub fn Reader::read_tagged_u64(self : Reader) -> UInt64 raise {
  let first = self.read_u32()
  if (first & 1U) == 1U {
    self.read_u64()
  } else {
    first.to_uint64() >> 1
  }
}

///|
/// The string header: one byte while it fits in seven bits, a varint when it
/// does not.
pub fn Reader::read_var_u36(self : Reader) -> UInt64 raise {
  let at = self.at
  let v = self.read_byte()
  if v < 0x80 {
    v.to_uint64()
  } else {
    // The byte is the first byte of the varint, so step back over it.
    self.at = at
    self.read_var_u64()
  }
}

///|
/// `n` bytes, as they are.
pub fn Reader::read_bytes(self : Reader, n : Int32) -> Bytes raise {
  if n < 0 {
    raise Refused::TooLarge(
      at=self.at,
      limit=self.config.graph.to_int64(),
      got=n.to_int64(),
    )
  }
  if self.remaining() < n {
    raise Refused::Truncated(at=self.at, want=n, got=self.remaining())
  }
  let out = self.view.exact_view(start=self.at, end=self.at + n).to_owned()
  self.at = self.at + n
  out
}

///|
/// A string: the header says how many bytes and which of the three encodings
/// they are.
pub fn Reader::read_string(self : Reader) -> String raise {
  let header = self.read_var_u36()
  let size = (header >> 2).to_int()
  let encoding = header & 3UL
  self.spend(size)
  let at = self.at
  let bytes = self.read_bytes(size)
  if encoding == 2UL {
    if self.config.check_string {
      @utf8.decode(bytes[:]) catch {
        _ => raise Refused::BadEncoding(at~, got=2)
      }
    } else {
      bytes.to_unchecked_string()
    }
  } else if encoding == 0UL {
    latin1(bytes)
  } else if encoding == 1UL {
    @utf16.decode(bytes[:], endianness=Little) catch {
      _ => raise Refused::BadEncoding(at~, got=1)
    }
  } else {
    raise Refused::BadEncoding(at=self.at, got=encoding.to_int())
  }
}

///|
/// LATIN1, which the specification allows a writer to choose: one byte, one
/// character, the first two hundred and fifty-six of Unicode.
///
/// Widening it is what it is: the bytes are the code points, so each one is
/// written as the UTF-8 it stands for.
fn latin1(bytes : Bytes) -> String {
  let out = StringBuilder()
  for i in 0.. Bytes raise {
  let n = self.read_var_u32().reinterpret_as_int()
  self.spend(n)
  self.read_bytes(n)
}

///|
/// A type ID byte.
pub fn Reader::read_type_id(self : Reader) -> TypeId raise {
  let at = self.at
  let v = self.read_byte()
  match TypeId::of(v) {
    Some(t) => t
    None => raise Refused::BadType(at~, got=v)
  }
}

///|
/// A type ID byte and the user type ID after it.
pub fn Reader::read_user_type(self : Reader) -> (TypeId, Int32) raise {
  let t = self.read_type_id()
  let user = self.read_var_u32().reinterpret_as_int()
  (t, user)
}

///|
/// The byte before a value, and what it says.
///
/// A reference to an id this message has not written is a refusal: there is
/// nothing to point at.
pub fn Reader::read_ref_flag(self : Reader) -> RefFlag raise {
  let at = self.at
  let v = self.read_i8()
  match v {
    -3 => Null
    -2 => {
      let id = self.read_var_u32().reinterpret_as_int()
      if id >= self.refs.length() {
        raise Refused::BadRef(at~, got=id)
      }
      Ref(id~)
    }
    -1 => NotNull
    0 => {
      let id = self.refs.length()
      // A value read a second time for a reference already has its id.
      if self.replay == 0 {
        self.refs.push(self.at)
      }
      RefValue(id~)
    }
    _ => raise Refused::BadEncoding(at~, got=v)
  }
}

///|
/// Where the value of a reference id starts, so it can be read again.
pub fn Reader::ref_offset(self : Reader, id : Int32) -> Int32 raise {
  if id < 0 || id >= self.refs.length() {
    raise Refused::BadRef(at=self.at, got=id)
  }
  self.refs[id]
}

///|
/// Put the reader back at `at`.
pub fn Reader::seek(self : Reader, at : Int32) -> Unit {
  self.at = at
}

///|
/// Go back to where the value of reference `id` was written and read it again,
/// which is what a reference asks for when the value was not a container put
/// aside. The answer is the offset to hand to `leave_ref` afterwards.
///
/// A reference that points into a value still being read is a cycle this
/// package cannot rebuild — a MoonBit value is finished before it is held — so
/// it is refused rather than read until the stack runs out.
pub fn Reader::enter_ref(self : Reader, id : Int32) -> Int32 raise {
  let offset = self.ref_offset(id)
  if self.replaying.contains(offset) {
    raise Refused::Cyclic(at=self.at, got=id)
  }
  self.replaying.push(offset)
  let saved = self.at
  self.at = offset
  self.replay = self.replay + 1
  saved
}

///|
/// Come back from a replay to where the read was.
pub fn Reader::leave_ref(self : Reader, saved : Int32) -> Unit {
  self.at = saved
  self.replay = self.replay - 1
  let _ = self.replaying.pop()
}

///|
/// The container being built for a reference id, when there is one.
///
/// A cycle points at a value that is not finished, so the container is put here
/// before its contents are read, and the reference finds it.
pub fn Reader::node(self : Reader, id : Int32) -> Value? {
  self.nodes.get(id)
}

///|
/// Remember the container being built for a reference id.
pub fn Reader::set_node(self : Reader, id : Int32, v : Value) -> Unit {
  self.nodes.set(id, v)
}

///|
/// Go one level deeper, refusing a message nested past what was configured.
pub fn Reader::enter(self : Reader) -> Unit raise {
  self.depth = self.depth + 1
  if self.depth > self.config.depth {
    raise Refused::TooDeep(at=self.at, limit=self.config.depth)
  }
}

///|
/// Come back one level.
pub fn Reader::leave(self : Reader) -> Unit {
  self.depth = self.depth - 1
}

///|
/// Count `n` bytes of declared payload against the budget for one message.
pub fn Reader::spend(self : Reader, n : Int32) -> Unit raise {
  self.spent = self.spent + n
  if self.spent > self.config.graph {
    raise Refused::TooLarge(
      at=self.at,
      limit=self.config.graph.to_int64(),
      got=self.spent.to_int64(),
    )
  }
}

///|
/// Refuse the bytes left over once a value has been read.
///
/// A message with two values in it is two messages, and reading one of them and
/// ignoring the rest is how a truncation goes unnoticed.
pub fn Reader::end(self : Reader) -> Unit raise {
  if self.at < self.view.length() {
    raise Refused::Trailing(at=self.at, got=self.view.length() - self.at)
  }
}