///|
/// A borrowed BSON document view backed by an existing `Bytes` allocation.
///
/// Construction and element iteration keep the original bytes as `BytesView`;
/// no document-wide copy or value allocation is performed until a value is
/// decoded explicitly.
pub struct RawDocumentView {
  bytes : BytesView
} derive(Eq, Debug)

///|
pub fn RawDocumentView::from_bytes(
  bytes : BytesView,
) -> RawDocumentView raise BsonError {
  let end = raw_view_document_end(bytes, 0, "$")
  if end != bytes.length() {
    raise bson_error(
      TrailingData,
      end,
      "$",
      "trailing bytes after raw BSON document",
    )
  }
  { bytes, }
}

///|
/// Create a borrowed view over the first BSON frame in `bytes`.
pub fn RawDocumentView::from_prefix(
  bytes : BytesView,
) -> (RawDocumentView, Int) raise BsonError {
  let end = raw_view_document_end(bytes, 0, "$")
  ({ bytes: bytes[0:end] }, end)
}

///|
pub fn RawDocumentView::bytes(self : RawDocumentView) -> BytesView {
  self.bytes
}

///|
pub fn RawDocumentView::length(self : RawDocumentView) -> Int {
  self.bytes.length()
}

///|
/// Return an iterator that keeps element keys and values as byte views.
pub fn RawDocumentView::iter(self : RawDocumentView) -> RawElementViewIter {
  { document: self, position: 4, end: self.bytes.length() - 1 }
}

///|
/// A single borrowed BSON element. `key()` allocates a String; `key_bytes()` does not.
pub struct RawElementView {
  document : RawDocumentView
  key_start : Int
  key_end : Int
  type_code : Byte
  value_start : Int
  value_end : Int
} derive(Eq, Debug)

///|
pub fn RawElementView::key_bytes(self : RawElementView) -> BytesView {
  self.document.bytes[self.key_start:self.key_end]
}

///|
pub fn RawElementView::key(self : RawElementView) -> String raise BsonError {
  @utf8.decode(self.key_bytes()) catch {
    _ =>
      raise bson_error(
        InvalidUtf8,
        self.key_start,
        "$.\u{24}key",
        "raw element key is not valid UTF-8",
      )
  }
}

///|
pub fn RawElementView::type_code(self : RawElementView) -> Byte {
  self.type_code
}

///|
pub fn RawElementView::raw_bytes(self : RawElementView) -> BytesView {
  self.document.bytes[self.value_start:self.value_end]
}

///|
/// Borrow a BSON string payload without decoding UTF-8 or allocating a String.
pub fn RawElementView::as_string_bytes(
  self : RawElementView,
) -> BytesView? raise BsonError {
  if self.type_code != 0x02 {
    return None
  }
  let length = raw_view_i32(self.document.bytes, self.value_start, "$.string")
  if length < 1 || self.value_start + 4 + length > self.value_end {
    raise bson_error(
      InvalidLength,
      self.value_start,
      "$.string",
      "invalid raw string length",
    )
  }
  let end = self.value_start + 4 + length
  if self.document.bytes[end - 1] != 0 {
    raise bson_error(
      InvalidCString,
      end - 1,
      "$.string",
      "raw string is not terminated",
    )
  }
  Some(self.document.bytes[self.value_start + 4:end - 1])
}

///|
/// Borrow binary payload bytes and retain the wire subtype.
pub fn RawElementView::as_binary_bytes(
  self : RawElementView,
) -> (BinarySubtype, BytesView)? raise BsonError {
  if self.type_code != 0x05 {
    return None
  }
  let length = raw_view_i32(self.document.bytes, self.value_start, "$.binary")
  if length < 0 || self.value_start + 5 + length > self.value_end {
    raise bson_error(
      InvalidBinary,
      self.value_start,
      "$.binary",
      "invalid raw binary length",
    )
  }
  let subtype = BinarySubtype::from_byte(
    self.document.bytes[self.value_start + 4],
  )
  let payload = self.value_start + 5
  if subtype == BinaryOld {
    if length < 4 ||
      raw_view_i32(self.document.bytes, payload, "$.binary") != length - 4 {
      raise bson_error(
        InvalidBinary,
        self.value_start,
        "$.binary",
        "invalid raw old binary length",
      )
    }
    return Some((subtype, self.document.bytes[payload + 4:payload + length]))
  }
  Some((subtype, self.document.bytes[payload:payload + length]))
}

///|
pub fn RawElementView::as_int32(self : RawElementView) -> Int? raise BsonError {
  if self.type_code == 0x10 {
    Some(raw_view_i32(self.document.bytes, self.value_start, "$.int32"))
  } else {
    None
  }
}

///|
pub fn RawElementView::as_int64(
  self : RawElementView,
) -> Int64? raise BsonError {
  if self.type_code == 0x12 {
    Some(raw_view_i64(self.document.bytes, self.value_start, "$.int64"))
  } else {
    None
  }
}

///|
pub fn RawElementView::as_double(
  self : RawElementView,
) -> Double? raise BsonError {
  if self.type_code == 0x01 {
    Some(
      raw_view_i64(self.document.bytes, self.value_start, "$.double").reinterpret_as_double(),
    )
  } else {
    None
  }
}

///|
pub fn RawElementView::as_datetime_millis(
  self : RawElementView,
) -> Int64? raise BsonError {
  if self.type_code == 0x09 {
    Some(raw_view_i64(self.document.bytes, self.value_start, "$.datetime"))
  } else {
    None
  }
}

///|
pub fn RawElementView::as_bool(self : RawElementView) -> Bool? raise BsonError {
  if self.type_code != 0x08 {
    return None
  }
  if self.value_start >= self.value_end {
    raise bson_error(
      UnexpectedEnd,
      self.value_start,
      "$.bool",
      "missing raw boolean byte",
    )
  }
  match self.document.bytes[self.value_start] {
    0 => Some(false)
    1 => Some(true)
    _ =>
      raise bson_error(
        InvalidBoolean,
        self.value_start,
        "$.bool",
        "invalid raw boolean byte",
      )
  }
}

///|
pub fn RawElementView::to_bson(self : RawElementView) -> Bson raise BsonError {
  let decoder = Decoder::new(self.document.bytes, DecodeOptions::new())
  decoder.position = self.value_start
  decoder.read_element(
    self.type_code,
    self.value_end,
    0,
    field_path("$", self.key()),
  )
}

///|
/// A stateful iterator over borrowed top-level elements.
pub struct RawElementViewIter {
  document : RawDocumentView
  mut position : Int
  end : Int
}

///|
pub fn RawElementViewIter::next(
  self : RawElementViewIter,
) -> RawElementView? raise BsonError {
  if self.position >= self.end {
    return None
  }
  let type_offset = self.position
  let type_code = self.document.bytes[self.position]
  self.position += 1
  if type_code == 0 {
    raise bson_error(
      InvalidLength,
      type_offset,
      "$",
      "raw document terminator appears before its boundary",
    )
  }
  let key_start = self.position
  let key_end = raw_view_cstring_end(
    self.document.bytes,
    key_start,
    self.end,
    "$.\u{24}key",
  )
  self.position = key_end + 1
  let value_start = self.position
  let value_end = raw_view_skip_value(
    self.document.bytes,
    type_code,
    value_start,
    self.end,
    "$.\u{24}value",
  )
  self.position = value_end
  Some({
    document: self.document,
    key_start,
    key_end,
    type_code,
    value_start,
    value_end,
  })
}

///|
fn raw_view_document_end(
  bytes : BytesView,
  start : Int,
  path : String,
) -> Int raise BsonError {
  if start < 0 || start + 4 > bytes.length() {
    raise bson_error(UnexpectedEnd, start, path, "raw document is too short")
  }
  let length = raw_view_i32(bytes, start, path)
  if length < 5 || start + length > bytes.length() {
    raise bson_error(InvalidLength, start, path, "invalid raw document length")
  }
  let end = start + length
  if bytes[end - 1] != 0 {
    raise bson_error(
      InvalidLength,
      end - 1,
      path,
      "raw document is not NUL terminated",
    )
  }
  end
}

///|
fn raw_view_skip_value(
  bytes : BytesView,
  type_code : Byte,
  start : Int,
  limit : Int,
  path : String,
) -> Int raise BsonError {
  match type_code {
    0x01 => raw_view_require(start + 8, limit, path)
    0x02 | 0x0D | 0x0E => raw_view_skip_string(bytes, start, limit, path)
    0x03 | 0x04 => raw_view_skip_container(bytes, start, limit, path)
    0x05 => raw_view_skip_binary(bytes, start, limit, path)
    0x06 | 0x0A | 0x7F | 0xFF => start
    0x07 => raw_view_require(start + 12, limit, path)
    0x08 | 0x09 =>
      raw_view_require(
        start + (if type_code == 0x08 { 1 } else { 8 }),
        limit,
        path,
      )
    0x0B => {
      let first = raw_view_cstring_end(bytes, start, limit, path)
      raw_view_cstring_end(bytes, first + 1, limit, path) + 1
    }
    0x0C => {
      let collection_end = raw_view_skip_string(bytes, start, limit, path)
      raw_view_require(collection_end + 12, limit, path)
    }
    0x0F => {
      let total = raw_view_i32(bytes, start, path)
      if total < 14 || start + total > limit {
        raise bson_error(
          InvalidLength,
          start,
          path,
          "invalid raw code-with-scope length",
        )
      }
      let code_end = raw_view_skip_string(bytes, start + 4, start + total, path)
      raw_view_skip_container(bytes, code_end, start + total, path)
    }
    0x10 => raw_view_require(start + 4, limit, path)
    0x11 | 0x12 => raw_view_require(start + 8, limit, path)
    0x13 => raw_view_require(start + 16, limit, path)
    _ =>
      raise bson_error(
        UnsupportedType,
        start,
        path,
        "unsupported raw BSON type",
      )
  }
}

///|
fn raw_view_skip_container(
  bytes : BytesView,
  start : Int,
  limit : Int,
  path : String,
) -> Int raise BsonError {
  let end = raw_view_document_end(bytes, start, path)
  if end > limit {
    raise bson_error(
      InvalidLength,
      start,
      path,
      "nested raw document exceeds parent",
    )
  }
  let mut position = start + 4
  while position < end - 1 {
    let type_code = bytes[position]
    position += 1
    let key_end = raw_view_cstring_end(bytes, position, end - 1, path)
    position = key_end + 1
    position = raw_view_skip_value(bytes, type_code, position, end - 1, path)
  }
  end
}

///|
fn raw_view_skip_string(
  bytes : BytesView,
  start : Int,
  limit : Int,
  path : String,
) -> Int raise BsonError {
  let length = raw_view_i32(bytes, start, path)
  if length < 1 || start + 4 + length > limit {
    raise bson_error(InvalidLength, start, path, "invalid raw string length")
  }
  let end = start + 4 + length
  if bytes[end - 1] != 0 {
    raise bson_error(
      InvalidCString,
      end - 1,
      path,
      "raw string is not terminated",
    )
  }
  end
}

///|
fn raw_view_skip_binary(
  bytes : BytesView,
  start : Int,
  limit : Int,
  path : String,
) -> Int raise BsonError {
  let length = raw_view_i32(bytes, start, path)
  if length < 0 || start + 5 + length > limit {
    raise bson_error(InvalidBinary, start, path, "invalid raw binary length")
  }
  let payload = start + 5
  if bytes[start + 4] == 2 {
    if length < 4 || raw_view_i32(bytes, payload, path) != length - 4 {
      raise bson_error(
        InvalidBinary,
        start,
        path,
        "invalid raw old binary length",
      )
    }
  }
  start + 5 + length
}

///|
fn raw_view_cstring_end(
  bytes : BytesView,
  start : Int,
  limit : Int,
  path : String,
) -> Int raise BsonError {
  let mut position = start
  while position < limit {
    if bytes[position] == 0 {
      return position
    }
    position += 1
  }
  raise bson_error(UnexpectedEnd, position, path, "unterminated raw cstring")
}

///|
fn raw_view_require(
  end : Int,
  limit : Int,
  path : String,
) -> Int raise BsonError {
  if end > limit {
    raise bson_error(
      UnexpectedEnd,
      limit,
      path,
      "raw value exceeds its container",
    )
  }
  end
}

///|
fn raw_view_i32(
  bytes : BytesView,
  start : Int,
  path : String,
) -> Int raise BsonError {
  if start < 0 || start + 4 > bytes.length() {
    raise bson_error(UnexpectedEnd, start, path, "raw integer exceeds input")
  }
  let value : UInt = bytes[start].to_uint() |
    (bytes[start + 1].to_uint() << 8) |
    (bytes[start + 2].to_uint() << 16) |
    (bytes[start + 3].to_uint() << 24)
  value.reinterpret_as_int()
}

///|
fn raw_view_i64(
  bytes : BytesView,
  start : Int,
  path : String,
) -> Int64 raise BsonError {
  if start < 0 || start + 8 > bytes.length() {
    raise bson_error(UnexpectedEnd, start, path, "raw int64 exceeds input")
  }
  let mut value : Int64 = 0L
  for index in 0..<8 {
    value = value | (bytes[start + index].to_int64() << (index * 8))
  }
  value
}