///|
/// Deterministic failures returned by frame parsing.
pub enum FrameError {
  NeedMore
  MalformedVarint
  FrameTooLarge(Int, Int)
  MissingChecksum
  ChecksumMismatch(UInt, UInt)
  BufferLimitExceeded(Int, Int)
}

///|
/// Prefix a payload with its unsigned LEB128 byte length.
pub fn encode_frame(payload : Bytes) -> Bytes {
  let header = encode_varint(payload.length())
  let output : Array[Byte] = []
  for byte in header {
    output.push(byte)
  }
  for byte in payload {
    output.push(byte)
  }
  Bytes::from_array(output)
}

///|
/// Encode several payloads as one concatenated framed stream.
pub fn encode_frames(payloads : ArrayView[Bytes]) -> Bytes {
  let output : Array[Byte] = []
  for payload in payloads {
    for byte in encode_frame(payload) {
      output.push(byte)
    }
  }
  Bytes::from_array(output)
}

///|
/// Decode one complete frame and report total bytes consumed.
pub fn decode_frame(
  data : Bytes,
  max_frame_size? : Int = 1048576,
) -> Result[(Bytes, Int), FrameError] {
  if data.length() >= 5 &&
    (data[0].to_int() & 0x80) != 0 &&
    (data[1].to_int() & 0x80) != 0 &&
    (data[2].to_int() & 0x80) != 0 &&
    (data[3].to_int() & 0x80) != 0 {
    let fifth = data[4].to_int()
    if (fifth & 0x80) != 0 || (fifth & 0x78) != 0 {
      return Err(MalformedVarint)
    }
  }
  let (length, header_size) = match decode_varint(data) {
    Some(parsed) => parsed
    None => {
      if data.length() >= 5 && (data[4].to_int() & 0x80) != 0 {
        return Err(MalformedVarint)
      }
      return Err(NeedMore)
    }
  }
  if length > max_frame_size {
    return Err(FrameTooLarge(length, max_frame_size))
  }
  let end = header_size + length
  if data.length() < end {
    return Err(NeedMore)
  }
  Ok((data[header_size:end].to_owned(), end))
}

///|
/// Decode an entire finite stream and reject trailing partial data.
pub fn decode_all(
  data : Bytes,
  max_frame_size? : Int = 1048576,
) -> Result[Array[Bytes], FrameError] {
  let decoder = Decoder::new(max_frame_size~)
  match decoder.feed(data) {
    Err(error) => Err(error)
    Ok(frames) =>
      match decoder.finish() {
        Ok(_) => Ok(frames)
        Err(error) => Err(error)
      }
  }
}