///|
let pdf_object_length_key_name : @core.PdfName = @core.pdf_name_of_bytes(
  b"/Length",
)

///|
/// Return the canonical `/Length` name used by stream dictionaries.
pub fn pdf_object_length_name() -> @core.PdfName {
  pdf_object_length_key_name
}

///|
/// Return whether this stream crypt policy leaves bytes unchanged.
pub fn PdfStreamDataCrypt::is_identity(self : PdfStreamDataCrypt) -> Bool {
  match self {
    PdfStreamDataNoCrypt => true
    _ => false
  }
}

///|
/// Decrypt one deferred stream-data view using this stream crypt policy.
pub fn PdfStreamDataCrypt::decrypt(
  self : PdfStreamDataCrypt,
  data : BytesView,
) -> @core.PdfBytes raise @core.PdfError {
  match self {
    PdfStreamDataNoCrypt => data.to_owned()
    PdfStreamDataArc4Decrypt(
      object_number,
      generation,
      file_key,
      key_length_bits
    ) =>
      @crypt_core.pdf_arc4_crypt_object(
        object_number, generation, file_key, key_length_bits, data,
      )
    PdfStreamDataAesV2Decrypt(
      object_number,
      generation,
      file_key,
      key_length_bits
    ) =>
      @crypt_core.pdf_aesv2_decrypt_object_data(
        object_number, generation, file_key, key_length_bits, data,
      )
    PdfStreamDataAesV3Decrypt(file_key) =>
      @crypt_core.pdf_aesv3_decrypt_object_data(file_key, data)
  }
}

///|
fn pdf_stream_to_get_raw_view(to_get : ToGet) -> BytesView raise @core.PdfError {
  let input = to_get.input()
  if to_get.position() < 0 {
    raise InvalidCursorPosition(to_get.position())
  }
  input.view_at(to_get.position(), to_get.length())
}

///|
/// Borrow or materialize stream data as a byte view.
pub fn pdf_stream_data_view(
  data : PdfStreamData,
) -> BytesView raise @core.PdfError {
  match data {
    StreamGot(bytes) => bytes
    StreamToGet(to_get) => {
      let raw = pdf_stream_to_get_raw_view(to_get)
      if to_get.crypt().is_identity() {
        raw
      } else {
        to_get.crypt().decrypt(raw)
      }
    }
  }
}

///|
/// Return stream data as owned bytes, decrypting deferred data when needed.
pub fn pdf_stream_data_bytes(
  data : PdfStreamData,
) -> @core.PdfBytes raise @core.PdfError {
  match data {
    StreamGot(bytes) => bytes
    StreamToGet(to_get) => {
      let raw = pdf_stream_to_get_raw_view(to_get)
      if to_get.crypt().is_identity() {
        raw.to_owned()
      } else {
        to_get.crypt().decrypt(raw)
      }
    }
  }
}

///|
/// Return a stream object's materialized bytes.
///
/// `StreamGot` data is returned directly. Deferred `StreamToGet` data is read
/// from its source cursor and decrypted if necessary, but this method does not
/// mutate the stream object or update `/Length`; use `get_stream` for that.
/// Non-stream objects raise `@core.PdfError::ParseStreamExpected`.
pub fn PdfObject::stream_bytes(
  self : PdfObject,
) -> @core.PdfBytes raise @core.PdfError {
  match self {
    PdfStreamObject(stream) => pdf_stream_data_bytes(stream.data)
    _ => raise ParseStreamExpected
  }
}

///|
/// Materialize a stream in place.
///
/// Deferred stream data is read, decrypted if needed, stored back as
/// `StreamGot`, and `/Length` is corrected when the stream dictionary is a PDF
/// dictionary. Calling this on an already materialized stream is a no-op.
/// Non-stream objects raise `@core.PdfError::ParseStreamExpected`.
pub fn PdfObject::get_stream(self : PdfObject) -> Unit raise @core.PdfError {
  match self {
    PdfStreamObject(stream) =>
      match stream.data {
        StreamGot(_) => ()
        StreamToGet(_) => {
          let data = pdf_stream_data_bytes(stream.data)
          match stream.dictionary {
            PdfDictionary(_) =>
              stream.dictionary = stream.dictionary.replace_dict_entry(
                pdf_object_length_name(),
                PdfInteger(data.length()),
              )
            _ => ()
          }
          stream.data = StreamGot(data)
        }
      }
    _ => raise ParseStreamExpected
  }
}