///|
fn PdfDocument::direct_depth(
  self : PdfDocument,
  object : @syntax.PdfObject,
  depth : Int,
) -> @syntax.PdfObject {
  if depth > 1024 {
    PdfNull
  } else {
    match object {
      PdfIndirect(number) =>
        match self.objects.get_entry(number) {
          Some({ data: ObjectParsed(value), .. }) =>
            self.direct_depth(value, depth + 1)
          Some({ data: ObjectParsedAlreadyDecrypted(value), .. }) =>
            self.direct_depth(value, depth + 1)
          _ => PdfNull
        }
      _ => object
    }
  }
}

///|
/// Resolve an object through the document object table.
///
/// Direct objects are returned unchanged. Indirect references are followed
/// recursively through parsed entries in `self.objects`; missing, unparsed, or
/// excessively deep references resolve to `PdfNull` rather than raising.
pub fn PdfDocument::direct(
  self : PdfDocument,
  object : @syntax.PdfObject,
) -> @syntax.PdfObject {
  self.direct_depth(object, 0)
}

///|
/// Look up `key` in a dictionary or stream dictionary and resolve the value.
///
/// The containing object is first passed through `direct`. If the object is not
/// dictionary-like, the key is absent, or the resolved value is `PdfNull`, this
/// returns `None`.
pub fn PdfDocument::lookup_direct(
  self : PdfDocument,
  key : @core.PdfName,
  object : @syntax.PdfObject,
) -> @syntax.PdfObject? {
  match self.direct(object).dictionary_view() {
    Some(entries) =>
      for entry in entries {
        let (name, value) = entry
        if name == key {
          let direct_value = self.direct(value)
          break if direct_value == PdfNull { None } else { Some(direct_value) }
        }
      } nobreak {
        None
      }
    None => None
  }
}

///|
/// Look up a required dictionary entry and resolve the value.
///
/// This is the raising counterpart to `lookup_direct`: it raises the caller
/// supplied `error` when the containing object is not dictionary-like, when the
/// key is absent, or when the resolved value is `PdfNull`.
pub fn PdfDocument::lookup_required(
  self : PdfDocument,
  key : @core.PdfName,
  object : @syntax.PdfObject,
  error : @core.PdfError,
) -> @syntax.PdfObject raise @core.PdfError {
  match self.direct(object).dictionary_view() {
    Some(entries) =>
      for entry in entries {
        if entry.0 == key {
          let direct_value = self.direct(entry.1)
          break if direct_value == PdfNull { raise error } else { direct_value }
        }
      } nobreak {
        raise error
      }
    None => raise error
  }
}

///|
/// Look up either of two alternative dictionary keys.
///
/// `first_key` is preferred. `second_key` is checked only when the first lookup
/// is absent or resolves to `PdfNull`.
pub fn PdfDocument::lookup_direct_or_else(
  self : PdfDocument,
  first_key : @core.PdfName,
  second_key : @core.PdfName,
  object : @syntax.PdfObject,
) -> @syntax.PdfObject? {
  match self.lookup_direct(first_key, object) {
    Some(value) => Some(value)
    None => self.lookup_direct(second_key, object)
  }
}

///|
fn pdf_lookup_array_index_name(name : @core.PdfName) -> Int? {
  let bytes = name.bytes()
  guard bytes.length() >= 3 &&
    bytes[0].to_int() == 47 &&
    bytes[1].to_int() == 91 else {
    return None
  }
  let first = bytes[2].to_int()
  guard first >= 48 && first <= 57 else { return None }
  let mut value = 0
  let mut index = 2
  while index < bytes.length() {
    let byte = bytes[index].to_int()
    if byte < 48 || byte > 57 {
      break
    }
    value = value * 10 + byte - 48
    index += 1
  }
  Some(value)
}

///|
/// Look up a dictionary entry, or address an array by a pseudo-key.
///
/// Names that begin with `/[` followed by decimal digits are treated as array
/// indexes when `object` is a `PdfArray`; other names are resolved with
/// `lookup_direct`. The returned value is resolved through `direct`.
pub fn PdfDocument::lookup_direct_or_array(
  self : PdfDocument,
  name : @core.PdfName,
  object : @syntax.PdfObject,
) -> @syntax.PdfObject? {
  match pdf_lookup_array_index_name(name) {
    Some(index) =>
      match object {
        PdfArray(values) =>
          match values.get(index) {
            Some(value) => Some(self.direct(value))
            None => None
          }
        _ => None
      }
    None => self.lookup_direct(name, object)
  }
}

///|
/// Return the indirect object number stored under a dictionary key.
///
/// The containing object is resolved before lookup, but the entry itself is
/// inspected before dereferencing. This returns `Some(number)` only when the
/// immediate entry value is `PdfIndirect(number)`.
pub fn PdfDocument::indirect_number(
  self : PdfDocument,
  key : @core.PdfName,
  object : @syntax.PdfObject,
) -> Int? {
  match self.direct(object).dictionary_view() {
    Some(entries) =>
      for entry in entries {
        if entry.0 == key {
          break match entry.1 {
            PdfIndirect(number) => Some(number)
            _ => None
          }
        }
      } nobreak {
        None
      }
    None => None
  }
}