///|
/// Incremental framing for a single MRCL snapshot received in network chunks.
/// Failure poisons the reader until reset; trailing bytes are never ignored.
pub struct FrameReader {
  priv limits : DecodeLimits
  priv bytes : Array[Byte]
  priv mut expected : Int32?
  priv mut failed : Bool
  priv mut finished : Bool
}

///|
pub fn FrameReader::new(limits : DecodeLimits) -> FrameReader {
  { limits, bytes: [], expected: None, failed: false, finished: false, }
}

///|
pub fn FrameReader::buffered_bytes(self : FrameReader) -> Int32 {
  self.bytes.length()
}

///|
pub fn FrameReader::reset(self : FrameReader) -> Unit {
  self.bytes.clear()
  self.expected = None
  self.failed = false
  self.finished = false
}

///|
pub fn FrameReader::feed(
  self : FrameReader,
  chunk : Bytes,
) -> Snapshot? raise ReconcileError {
  if self.failed || self.finished {
    raise InvalidFrame("reader requires reset")
  }
  try {
    let maximum = 68 + self.limits.max_width * 60
    if chunk.length() > maximum - self.bytes.length() {
      raise LimitExceeded("stream frame size")
    }
    for byte in chunk {
      self.bytes.push(byte)
      // Validate length declarations as soon as the first 16 bytes arrive.
      if self.bytes.length() == 16 {
        let header = Bytes::from_array(self.bytes)
        if header[0] != 77 ||
          header[1] != 82 ||
          header[2] != 67 ||
          header[3] != 76 ||
          header[4] != 1 ||
          header[5] != 0 ||
          header[6] != 0 ||
          header[7] != 0 {
          raise InvalidFrame("invalid stream header")
        }
        let width = get_u32(header, 8)
        if width == 0U || width > self.limits.max_width.reinterpret_as_uint() {
          raise LimitExceeded("stream width")
        }
        if get_u32(header, 12) > self.limits.max_members.reinterpret_as_uint() {
          raise LimitExceeded("stream members")
        }
        self.expected = Some(68 + width.reinterpret_as_int() * 60)
      }
      if self.expected is Some(size) && self.bytes.length() > size {
        raise InvalidFrame("trailing stream bytes")
      }
    }
    if self.expected is Some(size) && self.bytes.length() == size {
      let snapshot = Snapshot::decode(
        Bytes::from_array(self.bytes),
        self.limits,
      )
      self.finished = true
      Some(snapshot)
    } else {
      None
    }
  } catch {
    (error : ReconcileError) => {
      self.failed = true
      self.bytes.clear()
      raise error
    }
  }
}

///|
/// Signal transport EOF; truncated input is an error, never an empty set.
pub fn FrameReader::finish(self : FrameReader) -> Unit raise ReconcileError {
  if !self.finished || self.failed {
    self.failed = true
    self.bytes.clear()
    raise InvalidFrame("incomplete stream")
  }
}