///|
struct PdfClassicXRefEntry {
  object_number : Int
  offset : Int
  generation : Int
  in_use : Bool
  object_stream : ObjectStreamReference?
} derive(Debug, Eq, ToJson)

///|
fn pdf_xref_malformed_zero_offset_in_use_free(
  tokens : ArrayView[BytesView],
) -> Bool {
  tokens.length() >= 3 &&
  pdf_view_equals_ascii(tokens[0], [48, 48, 48, 48, 48, 48, 48, 48, 48, 48]) &&
  tokens[2].length() > 0 &&
  tokens[2][0].to_int() == 110
}

///|
fn pdf_xref_marker_token(token : BytesView) -> Bool {
  pdf_view_equals_ascii(token, [120, 114, 101, 102])
}

///|
fn pdf_xref_fixed_width_digits(
  line : BytesView,
  start : Int,
  end : Int,
) -> Bool {
  for i in start.. PdfClassicXRefEntry? raise PdfError {
  if line.length() >= 18 &&
    pdf_xref_fixed_width_digits(line, 0, 10) &&
    pdf_xref_fixed_width_digits(line, 11, 16) {
    let in_use = match line[17].to_int() {
      110 => true
      102 => false
      _ => raise XRefEntryExpected
    }
    Some({
      object_number,
      offset: pdf_parse_ascii_int_view(line[0:10]),
      generation: pdf_parse_ascii_int_view(line[11:16]),
      in_use,
      object_stream: None,
    })
  } else {
    None
  }
}

///|
fn pdf_read_startxref_position(data : BytesView) -> Int raise PdfError {
  let search = pdf_startxref_search_view(data)
  let start = pdf_find_last_ascii(search, [
    115, 116, 97, 114, 116, 120, 114, 101, 102,
  ])
  if start < 0 {
    raise StartXRefExpected
  }
  let rest = search[start + 9:]
  let mut index = 0
  while index < rest.length() && !pdf_is_digit_byte(rest[index].to_int()) {
    index += 1
  }
  let digits_start = index
  while index < rest.length() && pdf_is_digit_byte(rest[index].to_int()) {
    index += 1
  }
  pdf_parse_ascii_int_view(rest[digits_start:index])
}

///|
fn pdf_xref_token_in_use(token : BytesView) -> Bool raise PdfError {
  if token.length() == 0 {
    raise XRefEntryExpected
  }
  match token[0].to_int() {
    110 => true
    102 => false
    _ => raise XRefEntryExpected
  }
}

///|
fn pdf_classic_xref_lookup(
  xrefs : ArrayView[PdfClassicXRefEntry],
  object_number : Int,
) -> PdfClassicXRefEntry? {
  for xref in xrefs {
    if xref.in_use && xref.object_number == object_number {
      break Some(xref)
    }
  } nobreak {
    None
  }
}

///|
priv struct PdfClassicXRefIndex {
  entries : @hashmap.HashMap[Int, PdfClassicXRefEntry]
}

///|
fn pdf_classic_xref_index(
  xrefs : ArrayView[PdfClassicXRefEntry],
) -> PdfClassicXRefIndex {
  let entries : @hashmap.HashMap[Int, PdfClassicXRefEntry] = HashMap([])
  for xref in xrefs {
    if xref.in_use && !entries.contains(xref.object_number) {
      entries[xref.object_number] = xref
    }
  }
  { entries, }
}

///|
fn PdfClassicXRefIndex::lookup(
  self : PdfClassicXRefIndex,
  object_number : Int,
) -> PdfClassicXRefEntry? {
  self.entries.get(object_number)
}

///|
fn pdf_classic_xrefs_add_newer_first_with_seen(
  target : Array[PdfClassicXRefEntry],
  seen : @number_set.PdfNumberSet,
  entries : ArrayView[PdfClassicXRefEntry],
) -> Unit {
  for entry in entries {
    if !seen.contains(entry.object_number) {
      target.push(entry)
      seen.add(entry.object_number)
    }
  }
}

///|
fn pdf_cursor_read_classic_xref_entries(
  cursor : ByteCursor,
) -> Array[PdfClassicXRefEntry] raise PdfError {
  pdf_cursor_drop_whitespace(cursor)
  pdf_cursor_consume_ascii(cursor, [120, 114, 101, 102], XRefExpected)
  let entries : Array[PdfClassicXRefEntry] = []
  let mut object_number = 0
  let mut done = false
  while !done {
    pdf_cursor_drop_whitespace(cursor)
    let position = cursor.absolute_position()
    if pdf_cursor_matches_ascii_at(cursor, position, [
        116, 114, 97, 105, 108, 101, 114,
      ]) {
      done = true
    } else if cursor.peek_byte() == pdf_no_more {
      raise TrailerExpected
    } else {
      let line = cursor.read_line_view()
      let tokens = pdf_split_whitespace_tokens(line)
      match pdf_xref_fixed_width_entry(line, object_number) {
        Some(entry) => {
          entries.push(entry)
          object_number += 1
        }
        None =>
          if tokens.length() == 1 && pdf_xref_marker_token(tokens[0]) {
            ()
          } else if tokens.length() == 2 {
            object_number = pdf_parse_ascii_int_view(tokens[0])
            ignore(pdf_parse_ascii_int_view(tokens[1]))
          } else if tokens.length() == 3 && pdf_xref_marker_token(tokens[0]) {
            object_number = pdf_parse_ascii_int_view(tokens[1])
            ignore(pdf_parse_ascii_int_view(tokens[2]))
          } else if tokens.length() >= 3 {
            entries.push({
              object_number,
              offset: pdf_parse_ascii_int_view(tokens[0]),
              generation: if pdf_xref_malformed_zero_offset_in_use_free(tokens) {
                0
              } else {
                pdf_parse_ascii_int_view(tokens[1])
              },
              in_use: if pdf_xref_malformed_zero_offset_in_use_free(tokens) {
                false
              } else {
                pdf_xref_token_in_use(tokens[2])
              },
              object_stream: None,
            })
            object_number += 1
          } else {
            raise XRefEntryExpected
          }
      }
    }
  }
  entries
}