///|
fn pdf_writer_push_classic_xref_and_trailer(
  output : Array[Byte],
  document : PdfDocument,
  xrefs : ArrayView[(Int, Int, Int)],
  xref_start : Int,
) -> Unit raise @core.PdfError {
  let size = document.objects.max_object_number() + 1
  pdf_writer_push_ascii(output, "xref\n0 ")
  pdf_writer_push_ascii(output, size.to_string())
  pdf_writer_push_byte(output, 10)
  pdf_writer_push_ascii(output, "0000000000 65535 f \n")
  let mut xref_index = 0
  for number in 1.. Bool {
  for item in values {
    if item == value {
      break true
    }
  } nobreak {
    false
  }
}

///|
fn pdf_writer_changed_object_numbers(document : PdfDocument) -> Array[Int] {
  let log = document.event_log()
  let numbers : Array[Int] = Array(capacity=log.length() + 1)
  numbers.push(0)
  let seen : @hashmap.HashMap[Int, Bool] = HashMap([])
  seen[0] = true
  for item in log {
    let (number, _) = item
    if number > 0 && !seen.contains(number) {
      seen[number] = true
      numbers.push(number)
    }
  }
  numbers.sort()
  numbers
}

///|
fn pdf_writer_write_document_objects_for_numbers(
  output : Array[Byte],
  document : PdfDocument,
  numbers : ArrayView[Int],
) -> Array[(Int, Int, Int)] raise @core.PdfError {
  let xrefs : Array[(Int, Int, Int)] = Array(capacity=numbers.length())
  for number in numbers {
    if number > 0 {
      match document.objects.get_entry(number) {
        Some({ data: ObjectParsed(object), generation, })
        | Some({ data: ObjectParsedAlreadyDecrypted(object), generation, }) => {
          xrefs.push((number, output.length(), generation))
          pdf_writer_push_view(
            output,
            pdf_write_indirect_object({ number, generation, object, }),
          )
        }
        Some(_) => raise ParsedObjectExpected
        None => ()
      }
    }
  }
  xrefs
}

///|
fn pdf_writer_push_classic_xref_entry(
  output : Array[Byte],
  xrefs : ArrayView[(Int, Int, Int)],
  number : Int,
  xref_index : Ref[Int],
) -> Unit {
  if number == 0 {
    pdf_writer_push_ascii(output, "0000000000 65535 f \n")
  } else {
    while xref_index.val < xrefs.length() && xrefs[xref_index.val].0 < number {
      xref_index.val += 1
    }
    if xref_index.val < xrefs.length() && xrefs[xref_index.val].0 == number {
      let (_, offset, generation) = xrefs[xref_index.val]
      pdf_writer_push_padded_decimal(output, offset, 10)
      pdf_writer_push_byte(output, 32)
      pdf_writer_push_padded_decimal(output, generation, 5)
      pdf_writer_push_ascii(output, " n \n")
    } else {
      pdf_writer_push_ascii(output, "0000000000 65535 f \n")
    }
  }
}

///|
fn pdf_writer_push_classic_xref_subsection(
  output : Array[Byte],
  xrefs : ArrayView[(Int, Int, Int)],
  start : Int,
  length : Int,
  xref_index : Ref[Int],
) -> Unit {
  pdf_writer_push_ascii(output, start.to_string())
  pdf_writer_push_byte(output, 32)
  pdf_writer_push_ascii(output, length.to_string())
  pdf_writer_push_byte(output, 10)
  for number in start..<(start + length) {
    pdf_writer_push_classic_xref_entry(output, xrefs, number, xref_index)
  }
}

///|
fn pdf_writer_push_classic_sparse_xref_and_trailer(
  output : Array[Byte],
  document : PdfDocument,
  xrefs : ArrayView[(Int, Int, Int)],
  entry_numbers : ArrayView[Int],
  xref_start : Int,
  previous_xref : Int,
) -> Unit raise @core.PdfError {
  pdf_writer_push_ascii(output, "xref\n")
  let xref_index = Ref::{ val: 0, }
  let mut run_start = -1
  let mut run_length = 0
  for number in entry_numbers {
    if run_length == 0 {
      run_start = number
      run_length = 1
    } else if number == run_start + run_length {
      run_length += 1
    } else {
      pdf_writer_push_classic_xref_subsection(
        output, xrefs, run_start, run_length, xref_index,
      )
      run_start = number
      run_length = 1
    }
  }
  if run_length > 0 {
    pdf_writer_push_classic_xref_subsection(
      output, xrefs, run_start, run_length, xref_index,
    )
  }
  pdf_writer_push_ascii(output, "trailer\n")
  let trailer = pdf_writer_trailer_dictionary(document).add_dict_entry(
    pdf_writer_prev_name(),
    PdfInteger(previous_xref),
  )
  pdf_writer_push_view(output, pdf_write_object(trailer))
  pdf_writer_push_ascii(output, "\nstartxref\n")
  pdf_writer_push_ascii(output, xref_start.to_string())
  pdf_writer_push_ascii(output, "\n%%EOF\n")
}

///|
/// Serialize a complete document with a classic xref table.
pub fn pdf_write_document(
  document : PdfDocument,
) -> @core.PdfBytes raise @core.PdfError {
  let output : Array[Byte] = []
  let (major, minor) = document.version()
  pdf_writer_push_header(output, major, minor)
  let xrefs = pdf_writer_write_document_objects(output, document)
  let xref_start = output.length()
  pdf_writer_push_classic_xref_and_trailer(output, document, xrefs, xref_start)
  Bytes::from_array(output)
}

///|
/// Append a classic incremental update to original PDF bytes.
///
/// Only objects marked as changed in `document` are written. The previous
/// `startxref` position is read from `original` and emitted as `/Prev`.
pub fn pdf_write_document_incremental_update(
  original : @core.PdfBytes,
  document : PdfDocument,
) -> @core.PdfBytes raise @core.PdfError {
  let previous_xref = @reader.pdf_read_startxref_position(original)
  let output : Array[Byte] = Array(capacity=original.length() + 1)
  for byte in original {
    output.push(byte)
  }
  pdf_writer_push_byte(output, 10)
  let changed_numbers = pdf_writer_changed_object_numbers(document)
  let xrefs = pdf_writer_write_document_objects_for_numbers(
    output, document, changed_numbers,
  )
  let xref_start = output.length()
  pdf_writer_push_classic_sparse_xref_and_trailer(
    output, document, xrefs, changed_numbers, xref_start, previous_xref,
  )
  Bytes::from_array(output)
}