///|
fn pdf_pad_name(text : String) -> @core.PdfName {
  @core.pdf_name_of_bytes(@ascii.encode(text))
}

///|
let pdf_pad_annots_key_name : @core.PdfName = pdf_pad_name("/Annots")

///|
fn pdf_pad_annots_key() -> @core.PdfName {
  pdf_pad_annots_key_name
}

///|
fn pdf_pad_sorted_page_numbers(
  page_numbers : ArrayView[Int],
  end_page : Int,
  context : String,
) -> Array[Int] raise @core.PdfError {
  let sorted : Array[Int] = Array(capacity=page_numbers.length())
  for page_number in page_numbers {
    sorted.push(page_number)
  }
  sorted.sort()
  for page_number in sorted {
    if page_number < 1 || page_number > end_page {
      raise BadPageSpecification(
        context + ": range contains pages not present in pdf",
      )
    }
  }
  sorted
}

///|
fn pdf_pad_unique_sorted_page_numbers(
  page_numbers : ArrayView[Int],
  end_page : Int,
  context : String,
) -> Array[Int] raise @core.PdfError {
  let sorted = pdf_pad_sorted_page_numbers(page_numbers, end_page, context)
  let unique : Array[Int] = Array(capacity=sorted.length())
  for page_number in sorted {
    if unique.length() == 0 || unique[unique.length() - 1] != page_number {
      unique.push(page_number)
    }
  }
  unique
}

///|
fn pdf_pad_insert_positions(
  page_numbers : ArrayView[Int],
  before : Bool,
) -> Array[Int] {
  let output : Array[Int] = Array(capacity=page_numbers.length())
  for page_number in page_numbers {
    output.push(if before { page_number - 1 } else { page_number })
  }
  output
}

///|
fn pdf_pad_blank_page(
  page : @page.PdfPage,
) -> @page.PdfPage raise @core.PdfError {
  {
    content: [],
    mediabox: page.mediabox,
    resources: PdfDictionary([]),
    rotate: page.rotate,
    rest: page.rest.remove_dict_entry(pdf_pad_annots_key()),
  }
}

///|
fn pdf_pad_blank_pages(
  pages : ArrayView[@page.PdfPage],
  positions : ArrayView[Int],
  before : Bool,
) -> Array[@page.PdfPage] raise @core.PdfError {
  let output : Array[@page.PdfPage] = Array(capacity=positions.length())
  for position in positions {
    let page_index = if before { position } else { position - 1 }
    output.push(pdf_pad_blank_page(pages[page_index]))
  }
  output
}

///|
fn pdf_pad_inserted_page_count(
  inserted_pages : ArrayView[Array[@page.PdfPage]],
) -> Int {
  let mut count = 0
  for inserted in inserted_pages {
    count += inserted.length()
  }
  count
}

///|
fn pdf_pad_insert_pages(
  pages : ArrayView[@page.PdfPage],
  positions : ArrayView[Int],
  inserted_pages : ArrayView[Array[@page.PdfPage]],
) -> Array[@page.PdfPage] {
  let output : Array[@page.PdfPage] = Array(
    capacity=pages.length() + pdf_pad_inserted_page_count(inserted_pages),
  )
  let mut insertion = 0
  while insertion < positions.length() && positions[insertion] == 0 {
    for page in inserted_pages[insertion] {
      output.push(page)
    }
    insertion += 1
  }
  for index in 0.. Array[(Int, Int)] {
  let changes : Array[(Int, Int)] = Array(capacity=original_count)
  let mut insertion = 0
  for old_page in 1..<=original_count {
    while insertion < positions.length() && positions[insertion] < old_page {
      insertion += 1
    }
    changes.push((old_page, old_page + insertion))
  }
  changes
}

///|
fn pdf_pad_page_number_range(count : Int) -> Array[Int] {
  let output : Array[Int] = Array(capacity=if count > 0 { count } else { 0 })
  for page_number in 1..<=count {
    output.push(page_number)
  }
  output
}

///|
fn PdfDocument::pdf_pad_with_blank_pages(
  self : PdfDocument,
  page_numbers : ArrayView[Int],
  before : Bool,
  context : String,
) -> PdfDocument raise @core.PdfError {
  let end_page = self.endpage()
  let sorted = pdf_pad_sorted_page_numbers(page_numbers, end_page, context)
  let positions = pdf_pad_insert_positions(sorted, before)
  let pages = self.pages_of_pagetree()
  let blank_pages = pdf_pad_blank_pages(pages, positions, before)
  let inserted : Array[Array[@page.PdfPage]] = Array(
    capacity=blank_pages.length(),
  )
  for page in blank_pages {
    let single : Array[@page.PdfPage] = Array(capacity=1)
    single.push(page)
    inserted.push(single)
  }
  self.change_pages(
    pdf_pad_insert_pages(pages, positions, inserted),
    change_references=true,
    changes=pdf_pad_change_table(pages.length(), positions),
  )
}

///|
fn PdfDocument::pdf_pad_with_document(
  self : PdfDocument,
  page_numbers : ArrayView[Int],
  before : Bool,
  pad_document : PdfDocument,
  context : String,
) -> PdfDocument raise @core.PdfError {
  let end_page = self.endpage()
  let sorted = pdf_pad_unique_sorted_page_numbers(
    page_numbers, end_page, context,
  )
  let positions = pdf_pad_insert_positions(sorted, before)
  let pad_end_page = pad_document.endpage()
  let merged = pdf_merge_pdfs(false, false, ["a", "b"], [self, pad_document], [
    pdf_pad_page_number_range(end_page),
    pdf_pad_page_number_range(pad_end_page),
  ])
  let pages = merged.pages_of_pagetree()
  let original_pages : Array[@page.PdfPage] = Array(capacity=end_page)
  let pad_pages : Array[@page.PdfPage] = Array(capacity=pad_end_page)
  for index in 0.. PdfDocument raise @core.PdfError {
  match pad_with {
    Some(pad_document) =>
      self.pdf_pad_with_document(page_numbers, before, pad_document, context)
    None => self.pdf_pad_with_blank_pages(page_numbers, before, context)
  }
}

///|
/// Insert blank pages or pages from `pad_with` after selected one-based pages.
pub fn PdfDocument::pad_after(
  self : PdfDocument,
  page_numbers : ArrayView[Int],
  pad_with? : PdfDocument? = None,
) -> PdfDocument raise @core.PdfError {
  self.pdf_pad_pages(page_numbers, false, pad_with, "padafter")
}

///|
/// Compatibility wrapper for `PdfDocument::pad_after`.
pub fn pdf_pad_after(
  document : PdfDocument,
  page_numbers : ArrayView[Int],
  pad_with? : PdfDocument? = None,
) -> PdfDocument raise @core.PdfError {
  document.pad_after(page_numbers, pad_with~)
}

///|
/// Source-spelled cpdfpad `padafter` wrapper with cpdf argument order.
pub fn pdf_padafter(
  page_numbers : ArrayView[Int],
  document : PdfDocument,
  pad_with? : PdfDocument? = None,
) -> PdfDocument raise @core.PdfError {
  document.pad_after(page_numbers, pad_with~)
}

///|
/// Insert blank pages or pages from `pad_with` before selected one-based pages.
pub fn PdfDocument::pad_before(
  self : PdfDocument,
  page_numbers : ArrayView[Int],
  pad_with? : PdfDocument? = None,
) -> PdfDocument raise @core.PdfError {
  self.pdf_pad_pages(page_numbers, true, pad_with, "padbefore")
}

///|
/// Compatibility wrapper for `PdfDocument::pad_before`.
pub fn pdf_pad_before(
  document : PdfDocument,
  page_numbers : ArrayView[Int],
  pad_with? : PdfDocument? = None,
) -> PdfDocument raise @core.PdfError {
  document.pad_before(page_numbers, pad_with~)
}

///|
/// Source-spelled cpdfpad `padbefore` wrapper with cpdf argument order.
pub fn pdf_padbefore(
  page_numbers : ArrayView[Int],
  document : PdfDocument,
  pad_with? : PdfDocument? = None,
) -> PdfDocument raise @core.PdfError {
  document.pad_before(page_numbers, pad_with~)
}

///|
fn pdf_pad_multiple_blank_page(page : @page.PdfPage) -> @page.PdfPage {
  {
    content: [],
    mediabox: page.mediabox,
    resources: PdfDictionary([]),
    rotate: page.rotate,
    rest: PdfDictionary([]),
  }
}

///|
/// Pad a document to a page count that is a multiple of `multiple`.
///
/// Positive multiples append blank pages. Negative multiples prepend them. A
/// multiple of zero is invalid.
pub fn PdfDocument::pad_to_multiple(
  self : PdfDocument,
  multiple : Int,
) -> PdfDocument raise @core.PdfError {
  if multiple == 0 {
    raise BadNumberArgument("pad_to_multiple: zero multiple")
  }
  let prepend = multiple < 0
  let size = if prepend { -multiple } else { multiple }
  let pages = self.pages_of_pagetree()
  if pages.length() == 0 {
    self
  } else {
    let remainder = pages.length() % size
    let pages_to_add = if remainder == 0 { 0 } else { size - remainder }
    if pages_to_add == 0 {
      self
    } else {
      let blank = pdf_pad_multiple_blank_page(pages[pages.length() - 1])
      let blanks : Array[@page.PdfPage] = Array(capacity=pages_to_add)
      for _ in 0.. PdfDocument raise @core.PdfError {
  document.pad_to_multiple(multiple)
}

///|
/// Source-spelled cpdfpad `padmultiple` wrapper with cpdf argument order.
pub fn pdf_padmultiple(
  multiple : Int,
  document : PdfDocument,
) -> PdfDocument raise @core.PdfError {
  document.pad_to_multiple(multiple)
}