///|
priv struct PdfDuplicateStreamCandidate {
  number : Int
  dictionary : @syntax.PdfObject
  data : BytesView
}

///|
fn pdf_duplicate_stream_candidate(
  number : Int,
  object : @syntax.PdfObject,
) -> PdfDuplicateStreamCandidate? {
  match object {
    PdfStreamObject({ dictionary, data, }) =>
      Some({
        number,
        dictionary,
        data: try! @syntax.pdf_stream_data_view(data),
      })
    _ => None
  }
}

///|
fn PdfDuplicateStreamCandidate::equals(
  self : PdfDuplicateStreamCandidate,
  other : PdfDuplicateStreamCandidate,
) -> Bool {
  self.dictionary == other.dictionary && self.data == other.data
}

///|
fn PdfDocument::pdf_duplicate_stream_changes(
  self : PdfDocument,
) -> Array[(Int, Int)] {
  let unique_streams_by_length : @hashmap.HashMap[
    Int,
    Array[PdfDuplicateStreamCandidate],
  ] = HashMap([])
  let object_numbers = self.object_numbers()
  let changes : Array[(Int, Int)] = Array(capacity=object_numbers.length())
  for number in object_numbers {
    let object = self.lookup_object_or_null(number)
    match pdf_duplicate_stream_candidate(number, object) {
      Some(candidate) => {
        let length = candidate.data.length()
        let unique_streams = match unique_streams_by_length.get(length) {
          Some(streams) => streams
          None => {
            let streams : Array[PdfDuplicateStreamCandidate] = Array(capacity=1)
            unique_streams_by_length[length] = streams
            streams
          }
        }
        let is_duplicate = for canonical in unique_streams {
          if canonical.equals(candidate) {
            changes.push((number, canonical.number))
            break true
          }
        } nobreak {
          false
        }
        if !is_duplicate {
          unique_streams.push(candidate)
        }
      }
      None => ()
    }
  }
  changes
}

///|
/// Removes duplicate embedded font streams from this document.
///
/// Streams are considered duplicates when both their dictionaries and decoded
/// byte payloads match. References to duplicate streams are rewired to the
/// first matching stream and duplicate objects are removed.
pub fn PdfDocument::remove_duplicate_fonts(self : PdfDocument) -> Unit {
  let changes = self.pdf_duplicate_stream_changes()
  if changes.length() > 0 {
    let change_map = pdf_page_reference_map(changes)
    self.pdf_page_renumber_references(changes)
    self.set_trailer_dict(
      pdf_page_renumber_object_with_map(self.trailer_dict(), change_map),
    )
    for change in changes {
      let (duplicate_number, _) = change
      self.remove_object(duplicate_number)
    }
  }
}

///|
/// Removes duplicate embedded font streams from a document.
///
/// This is the standalone form of `PdfDocument::remove_duplicate_fonts`.
pub fn pdf_remove_duplicate_fonts(document : PdfDocument) -> Unit {
  document.remove_duplicate_fonts()
}