///|
fn PdfDocument::pdf_object_stream_required_int(
  self : PdfDocument,
  dictionary : PdfObject,
  key : PdfName,
) -> Int raise PdfError {
  match self.lookup_direct(key, dictionary) {
    Some(PdfInteger(value)) if value >= 0 => value
    _ => raise XRefEntryExpected
  }
}

///|
fn pdf_object_stream_number_offsets(
  data : BytesView,
  first : Int,
  count : Int,
) -> Array[(Int, Int)] raise PdfError {
  if first < 0 || first > data.length() || count < 0 {
    raise StreamDataExpected
  }
  let values = pdf_parse_lexemes(pdf_lex_view(data[0:first]))
  if values.length() < count * 2 {
    raise XRefEntryExpected
  }
  let pairs : Array[(Int, Int)] = Array::new(capacity=count)
  for i in 0.. 0 &&
        offset >= 0 => pairs.push((object_number, offset))
      _ => raise XRefEntryExpected
    }
  }
  pairs
}

///|
fn pdf_object_stream_end_offsets(
  pairs : ArrayView[(Int, Int)],
  data_length : Int,
  first : Int,
) -> Array[Int] {
  let default_end = data_length - first
  let sorted : Array[(Int, Int)] = Array::new(capacity=pairs.length())
  for index in 0.. PdfObjectStreamContext raise PdfError {
  match object_stream_object {
    PdfStreamObject(stream) => {
      match stream.dictionary.lookup_immediate(pdf_reader_type_name()) {
        Some(PdfNameObject(name)) if name == pdf_reader_objstm_name() => ()
        Some(_) => raise XRefEntryExpected
        None => ()
      }
      let decoded_stream = self.pdf_decode_stream_value_direct(stream)
      let count = self.pdf_object_stream_required_int(
        decoded_stream.dictionary,
        pdf_reader_n_name(),
      )
      let first = self.pdf_object_stream_required_int(
        decoded_stream.dictionary,
        pdf_reader_first_name(),
      )
      let data = @syntax.pdf_stream_data_bytes(decoded_stream.data)
      let pairs = pdf_object_stream_number_offsets(data, first, count)
      let end_offsets = pdf_object_stream_end_offsets(
        pairs,
        data.length(),
        first,
      )
      { data, first, pairs, end_offsets }
    }
    _ => raise XRefEntryExpected
  }
}

///|
fn PdfObjectStreamContext::read_reference(
  self : PdfObjectStreamContext,
  reference : ObjectStreamReference,
) -> (Int, PdfObject) raise PdfError {
  if reference.index < 0 || reference.index >= self.pairs.length() {
    raise XRefEntryExpected
  }
  let next_offset = self.end_offsets[reference.index]
  let (object_number, offset) = self.pairs[reference.index]
  let start = self.first + offset
  let end = self.first + next_offset
  if start < self.first || start > end || end > self.data.length() {
    raise StreamDataExpected
  }
  (
    object_number,
    pdf_parse_object_stream_member_from_view(self.data[start:end]),
  )
}

///|
fn PdfDocument::read_object_stream_context(
  self : PdfDocument,
  object_stream_number : Int,
) -> PdfObjectStreamContext raise PdfError {
  self.read_object_stream_context_from_object(
    self.lookup_object_or_null(object_stream_number),
  )
}

///|
fn PdfDocument::read_object_stream_context_with_passwords(
  self : PdfDocument,
  object_stream_number : Int,
  user_password : BytesView?,
  owner_password : BytesView?,
) -> (PdfObjectStreamContext, Bool) raise PdfError {
  if !self.is_encrypted() {
    (self.read_object_stream_context(object_stream_number), false)
  } else {
    let entry = match self.objects.get_entry(object_stream_number) {
      Some(entry) => entry
      None => raise XRefEntryExpected
    }
    let object_stream_object = self.lookup_object_or_null(object_stream_number)
    match
      self.decrypt_single_stream(
        object_stream_number,
        entry.generation,
        object_stream_object,
        user_password~,
        owner_password~,
      ) {
      Some(decrypted_stream) =>
        (self.read_object_stream_context_from_object(decrypted_stream), true)
      None => raise EncryptionAuthenticationUnsupported(PdfCryptAESV2)
    }
  }
}

///|
fn pdf_reader_object_stream_group_counts(
  xrefs : ArrayView[PdfClassicXRefEntry],
) -> (@hashmap.HashMap[Int, Int], Int) {
  let counts : @hashmap.HashMap[Int, Int] = HashMap([])
  let mut group_count = 0
  for xref in xrefs {
    match xref.object_stream {
      Some(reference) =>
        if xref.in_use && xref.object_number != 0 {
          let object_stream_number = reference.object_stream_number
          match counts.get(object_stream_number) {
            Some(count) => counts[object_stream_number] = count + 1
            None => {
              counts[object_stream_number] = 1
              group_count += 1
            }
          }
        }
      None => ()
    }
  }
  (counts, group_count)
}

///|
fn pdf_reader_object_stream_groups(
  xrefs : ArrayView[PdfClassicXRefEntry],
) -> Array[(Int, Array[(PdfClassicXRefEntry, ObjectStreamReference)])] {
  let (counts, group_count) = pdf_reader_object_stream_group_counts(xrefs)
  let groups : Array[(Int, Array[(PdfClassicXRefEntry, ObjectStreamReference)])] = Array::new(
    capacity=group_count,
  )
  let indexes : @hashmap.HashMap[Int, Int] = HashMap([])
  for xref in xrefs {
    match xref.object_stream {
      Some(reference) =>
        if xref.in_use && xref.object_number != 0 {
          match indexes.get(reference.object_stream_number) {
            Some(index) => groups[index].1.push((xref, reference))
            None => {
              indexes[reference.object_stream_number] = groups.length()
              let entries : Array[(PdfClassicXRefEntry, ObjectStreamReference)] = Array::new(
                capacity=match counts.get(reference.object_stream_number) {
                  Some(count) => count
                  None => 1
                },
              )
              entries.push((xref, reference))
              groups.push((reference.object_stream_number, entries))
            }
          }
        }
      None => ()
    }
  }
  groups
}

///|
fn PdfDocument::load_embedded_objects_from_context(
  self : PdfDocument,
  xrefs : ArrayView[(PdfClassicXRefEntry, ObjectStreamReference)],
  object_stream_number : Int,
  context : PdfObjectStreamContext,
  already_decrypted : Bool,
) -> Unit raise PdfError {
  for entry in xrefs {
    let xref = entry.0
    let reference = entry.1
    if reference.object_stream_number == object_stream_number {
      let (object_number, object) = context.read_reference(reference)
      if object_number != xref.object_number {
        raise XRefEntryExpected
      }
      if already_decrypted {
        self.load_object_given_number_already_decrypted(
          xref.object_number,
          object,
        )
      } else {
        self.load_object_given_number(xref.object_number, object)
      }
    }
  }
}