///|
/// Types of encryption.
pub(all) enum Encryption {
  ARC4(Int, Int)
  AESV2
  AESV3(Bool)
} derive(Debug)

///|
/// Return the PDF "revision" (`/R`) and key length (in bits) implied by the
/// encryption variant.
///
/// - `ARC4(bits, r)` uses the explicit values from the parsed encryption dict.
/// - `AESV2` is always revision 4 with a 128-bit key.
/// - `AESV3(is_iso)` is revision 5 (or 6 in ISO mode) with a 256-bit key.
///
/// This is a small convenience API to avoid scattering the same `match` in
/// multiple packages.
pub fn Encryption::r_and_keylength(self : Encryption) -> (Int, Int) {
  match self {
    ARC4(bits, r) => (r, bits)
    AESV2 => (4, 128)
    AESV3(is_iso) => (if is_iso { 6 } else { 5 }, 256)
  }
}

///|
pub struct PdfCryptPrimitives {
  unit : Unit
}

///|
pub fn PdfCryptPrimitives::new() -> PdfCryptPrimitives {
  { unit: () }
}

///|
fn bytes_of_int_arrays(values : Array[Array[Int]]) -> @pdfio.MutableBytes {
  let mut total = 0
  for v in values {
    total = total + v.length()
  }
  let out = @pdfio.mkbytes(total)
  let mut pos = 0
  for v in values {
    for value in v {
      @pdfio.bset(out, pos, value)
      pos = pos + 1
    }
  }
  out
}

///|
/// Given an object number, generation number, input key and key length in bits,
/// apply Algorithm 3.1 to obtain the hash to be used by the encryption function.
pub fn PdfCryptPrimitives::find_hash(
  self : PdfCryptPrimitives,
  crypt_type : Encryption,
  obj : Int,
  gen : Int,
  key : Array[Int],
  keylength : Int,
) -> Array[Int] {
  let from_obj = [
    Int::land(obj, 0xff),
    Int::land(obj >> 8, 0xff),
    Int::land(obj >> 16, 0xff),
  ]
  let from_gen = [Int::land(gen, 0xff), Int::land(gen >> 8, 0xff)]
  let extra = match crypt_type {
    AESV2 => [0x73, 0x41, 0x6c, 0x54]
    _ => []
  }
  let digest_input = bytes_of_int_arrays([key, from_obj, from_gen, extra])
  let digest = self.md5(digest_input)
  let out_len = Int::min(16, keylength / 8 + 5)
  let out = Array::make(out_len, 0)
  for i in 0.. @pdfio.MutableBytes raise {
  if r == 5 || r == 6 {
    let key_bytes = match file_encryption_key {
      Some(k) => @pdfio.int_array_of_string(k)
      None => fail("decrypt_stream_data: missing file encryption key")
    }
    match crypt_type {
      AESV2 =>
        if encrypt {
          self.aes_encrypt_data(4, key_bytes, data)
        } else {
          self.aes_decrypt_data(4, key_bytes, data)
        }
      AESV3(_) =>
        if encrypt {
          self.aes_encrypt_data(8, key_bytes, data)
        } else {
          self.aes_decrypt_data(8, key_bytes, data)
        }
      ARC4(_, _) => self.crypt(key_bytes, data)
    }
  } else {
    let hash = self.find_hash(crypt_type, obj, gen, key, keylength)
    match crypt_type {
      AESV2 =>
        if encrypt {
          self.aes_encrypt_data(4, hash, data)
        } else {
          self.aes_decrypt_data(4, hash, data)
        }
      AESV3(_) =>
        if encrypt {
          self.aes_encrypt_data(8, hash, data)
        } else {
          self.aes_decrypt_data(8, hash, data)
        }
      ARC4(_, _) => self.crypt(hash, data)
    }
  }
}