///|
fn pdf_cursor_lex_first_indirect_object_tokens(
  cursor : @core.ByteCursor,
) -> Array[@syntax.PdfLexeme] raise @core.PdfError {
  let tokens : Array[@syntax.PdfLexeme] = []
  let mut done = false
  while !done {
    match @syntax.pdf_cursor_lex_token(cursor) {
      StopLexing | LexNone => done = true
      LexEndObj as token => {
        tokens.push(token)
        done = true
      }
      token => tokens.push(token)
    }
  }
  tokens
}

///|
fn pdf_cursor_read_plain_indirect_object_at_current(
  cursor : @core.ByteCursor,
) -> @syntax.PdfParsedIndirectObject raise @core.PdfError {
  let (objects, prefix) = @syntax.pdf_parse_indirect_object_segments(
    pdf_cursor_lex_first_indirect_object_tokens(cursor),
  )
  if objects.length() > 0 {
    objects[0]
  } else {
    match prefix {
      Some(prefix) =>
        if pdf_cursor_stopped_at_stream_candidate(cursor) {
          raise StreamLengthExpected
        } else {
          prefix
        }
      None => raise ParseIndirectObjectExpected
    }
  }
}

///|
fn pdf_cursor_stopped_at_stream_candidate(cursor : @core.ByteCursor) -> Bool {
  let position = cursor.absolute_position()
  @syntax.pdf_cursor_matches_ascii_at(cursor, position, [115]) &&
  !@syntax.pdf_cursor_matches_ascii_at(cursor, position, [
    115, 116, 97, 114, 116, 120, 114, 101, 102,
  ])
}

///|
fn pdf_cursor_read_first_indirect_object_segment_at_current(
  cursor : @core.ByteCursor,
) -> (@syntax.PdfParsedIndirectObject, Bool) raise @core.PdfError {
  let (object, _, complete) = @syntax.pdf_parse_indirect_object_segment_at(
    pdf_cursor_lex_first_indirect_object_tokens(cursor),
    0,
  )
  (object, complete)
}

///|
fn pdf_cursor_resolve_classic_stream_length(
  cursor : @core.ByteCursor,
  object : @syntax.PdfObject,
  xrefs : ArrayView[@reader.PdfClassicXRefEntry],
  xref_index : @reader.PdfClassicXRefIndex?,
) -> Int raise @core.PdfError {
  match object.lookup_immediate(@reader.pdf_length_name()) {
    Some(PdfInteger(length)) if length >= 0 => length
    Some(PdfIndirect(number)) => {
      let xref = match xref_index {
        Some(index) => index.lookup(number)
        None => @reader.pdf_classic_xref_lookup(xrefs, number)
      }
      match xref {
        Some(xref) => {
          let saved_position = cursor.absolute_position()
          cursor.seek_absolute(xref.offset)
          let parsed_result : Result[
            (@syntax.PdfParsedIndirectObject, Bool),
            Error,
          ] = try
            pdf_cursor_read_first_indirect_object_segment_at_current(cursor)
          catch {
            err => Err(err)
          } noraise {
            value => Ok(value)
          }
          cursor.seek_absolute(saved_position)
          let (parsed, complete) = match parsed_result {
            Ok(parsed) => parsed
            Err(_) => raise StreamLengthExpected
          }
          if parsed.number != number || parsed.generation != xref.generation {
            raise XRefEntryExpected
          }
          if !complete {
            raise StreamLengthExpected
          }
          match parsed.object {
            PdfInteger(length) if length >= 0 => length
            _ => raise StreamLengthExpected
          }
        }
        None => raise StreamLengthExpected
      }
    }
    _ => raise StreamLengthExpected
  }
}

///|
fn pdf_cursor_read_first_indirect_object_at_current(
  cursor : @core.ByteCursor,
  xrefs : ArrayView[@reader.PdfClassicXRefEntry],
  xref_index? : @reader.PdfClassicXRefIndex? = None,
) -> @syntax.PdfParsedIndirectObject raise @core.PdfError {
  let (object, _, complete) = @syntax.pdf_parse_indirect_object_segment_at(
    pdf_cursor_lex_first_indirect_object_tokens(cursor),
    0,
  )
  if complete {
    object
  } else if !pdf_cursor_stopped_at_stream_candidate(cursor) {
    object
  } else {
    let length_result : Result[Int, Error] = try
      pdf_cursor_resolve_classic_stream_length(
        cursor,
        object.object,
        xrefs,
        xref_index,
      )
    catch {
      err => Err(err)
    } noraise {
      value => Ok(value)
    }
    match length_result {
      Ok(length) =>
        pdf_cursor_finish_stream_object_with_length(
          cursor,
          object,
          length,
          defer_stream_data=true,
        )
      Err(_) => pdf_cursor_finish_malformed_stream_object(cursor, object)
    }
  }
}