///|
fn bytes_slice(
  bytes : @pdfio.MutableBytes,
  start : Int,
  len : Int,
) -> @pdfio.MutableBytes {
  let out = @pdfio.mkbytes(len)
  for i in 0.. @pdfio.MutableBytes {
  let mut total = 0
  for part in parts {
    total = total + part.length()
  }
  let out = @pdfio.mkbytes(total)
  let mut pos = 0
  for part in parts {
    for i in 0.. Int {
  let mut x = 0
  for i in 0..<16 {
    x = x + @pdfio.bget(bytes, i)
  }
  x % 3
}

///|
fn shamix(password : String, udata : String?, s : String) -> String raise {
  let prims = @pdfcryptprimitives.PdfCryptPrimitives::new()
  let mut k = prims.sha256(@pdfio.Input::of_string(s))
  let mut k_bytes = @pdfio.bytes_of_string(k)
  let mut fin = false
  let mut round = 0
  let mut last_e = 0
  let pw_bytes = @pdfio.bytes_of_string(password)
  let u_bytes = match udata {
    Some(u) => Some(@pdfio.bytes_of_string(u))
    None => None
  }
  while !fin {
    round = round + 1
    let parts = Array::new()
    parts.push(pw_bytes)
    parts.push(k_bytes)
    match u_bytes {
      Some(ub) => parts.push(ub)
      None => ()
    }
    let k1 = bytes_concat(parts)
    let k1_64 = bytes_concat(Array::make(64, k1))
    let key = @pdfio.int_array_of_bytes(bytes_prefix(k_bytes, 16))
    let firstblock = @pdfio.int_array_of_bytes(bytes_slice(k_bytes, 16, 16))
    let raw = prims.aes_encrypt_data(4, key, k1_64, firstblock~)
    let raw_len = raw.length()
    let e = if raw_len > 32 {
      bytes_slice(raw, 16, raw_len - 32)
    } else {
      @pdfio.mkbytes(0)
    }
    if e.length() > 0 {
      last_e = @pdfio.bget(e, e.length() - 1)
    } else {
      last_e = 0
    }
    let next_k = match mod3_bytes(e) {
      0 => prims.sha256(@pdfio.Input::of_bytes(e))
      1 => prims.sha384(@pdfio.Input::of_bytes(e))
      _ => prims.sha512(@pdfio.Input::of_bytes(e))
    }
    k = next_k
    k_bytes = @pdfio.bytes_of_string(k)
    fin = round >= 64 && last_e <= round - 32
  }
  @pdfio.string_of_bytes(bytes_prefix(k_bytes, 32))
}

///|
fn file_encryption_key_aesv3(
  iso : Bool,
  utf8pw : String,
  o : String,
  oe : String,
  u : String,
  digest? : Array[Int],
) -> @pdfio.MutableBytes raise {
  let prims = @pdfcryptprimitives.PdfCryptPrimitives::new()
  if o.length() < 48 || u.length() < 48 {
    raise @pdf.PdfError::Msg(
      "/O too short in make_intermediate_owner_key_aesv3",
    )
  }
  let d = match digest {
    Some(digest) => digest
    None => {
      let o_bytes = @pdfio.bytes_of_string(o)
      let u_bytes = @pdfio.bytes_of_string(u)
      let concat = bytes_concat([
        @pdfio.bytes_of_string(utf8pw),
        bytes_slice(o_bytes, 40, 8),
        bytes_slice(u_bytes, 0, 48),
      ])
      let digest_str = if iso {
        shamix(utf8pw, Some(u), @pdfio.string_of_bytes(concat))
      } else {
        prims.sha256(@pdfio.Input::of_bytes(concat))
      }
      @pdfio.int_array_of_string(digest_str)
    }
  }
  let data = bytes_concat([
    @pdfio.bytes_of_string(zero_iv),
    @pdfio.bytes_of_string(oe),
  ])
  prims.aes_decrypt_data(8, d, data, remove_padding=false)
}

///|
fn file_encryption_key_aesv3_user(
  iso : Bool,
  utf8pw : String,
  u : String,
  ue : String,
) -> @pdfio.MutableBytes raise {
  let prims = @pdfcryptprimitives.PdfCryptPrimitives::new()
  if u.length() < 48 {
    raise @pdf.PdfError::Msg("/U too short in file_encryption_key_aesv3_user")
  }
  let u_bytes = @pdfio.bytes_of_string(u)
  let concat = bytes_concat([
    @pdfio.bytes_of_string(utf8pw),
    bytes_slice(u_bytes, 40, 8),
  ])
  let digest_str = if iso {
    shamix(utf8pw, None, @pdfio.string_of_bytes(concat))
  } else {
    prims.sha256(@pdfio.Input::of_bytes(concat))
  }
  let key = @pdfio.int_array_of_string(digest_str)
  let data = bytes_concat([
    @pdfio.bytes_of_string(zero_iv),
    @pdfio.bytes_of_string(ue),
  ])
  prims.aes_decrypt_data(8, key, data, remove_padding=false)
}

///|
fn authenticate_owner_password_aesv3(
  iso : Bool,
  utf8pw : String,
  u : String,
  o : String,
) -> Bool raise {
  let prims = @pdfcryptprimitives.PdfCryptPrimitives::new()
  if o.length() < 48 || u.length() < 48 {
    raise @pdf.PdfError::Msg("/O too short in authenticate_owner_password")
  }
  let u_bytes = @pdfio.bytes_of_string(u)
  let o_bytes = @pdfio.bytes_of_string(o)
  let concat = bytes_concat([
    @pdfio.bytes_of_string(utf8pw),
    bytes_slice(o_bytes, 32, 8),
    bytes_slice(u_bytes, 0, 48),
  ])
  let digest_str = if iso {
    shamix(utf8pw, Some(u), @pdfio.string_of_bytes(concat))
  } else {
    prims.sha256(@pdfio.Input::of_bytes(concat))
  }
  let digest = @pdfio.bytes_of_string(digest_str)
  bytes_equal(bytes_slice(o_bytes, 0, 32), bytes_prefix(digest, 32))
}

///|
fn authenticate_user_password_aesv3(
  iso : Bool,
  utf8pw : String,
  u : String,
) -> Bool raise {
  let prims = @pdfcryptprimitives.PdfCryptPrimitives::new()
  if u.length() < 48 {
    raise @pdf.PdfError::Msg("/U too short in authenticate_owner_password")
  }
  let u_bytes = @pdfio.bytes_of_string(u)
  let concat = bytes_concat([
    @pdfio.bytes_of_string(utf8pw),
    bytes_slice(u_bytes, 32, 8),
  ])
  let digest_str = if iso {
    shamix(utf8pw, None, @pdfio.string_of_bytes(concat))
  } else {
    prims.sha256(@pdfio.Input::of_bytes(concat))
  }
  let digest = @pdfio.bytes_of_string(digest_str)
  bytes_equal(bytes_slice(u_bytes, 0, 32), bytes_prefix(digest, 32))
}

///|
fn p_of_perms(key : @pdfio.MutableBytes, perms : String) -> Int? raise {
  let prims = @pdfcryptprimitives.PdfCryptPrimitives::new()
  if perms.length() < 16 {
    raise @pdf.PdfError::Msg("Wrong length in /Perms")
  }
  let ps = prims.aes_decrypt_data_ecb(
    8,
    @pdfio.int_array_of_bytes(key),
    @pdfio.bytes_of_string(perms),
    remove_padding=false,
  )
  let ints = @pdfio.int_array_of_bytes(ps)
  if ints[9] != 'a'.to_int() ||
    ints[10] != 'd'.to_int() ||
    ints[11] != 'b'.to_int() {
    None
  } else {
    let p = (ints[0] & 0xff) |
      ((ints[1] & 0xff) << 8) |
      ((ints[2] & 0xff) << 16) |
      ((ints[3] & 0xff) << 24)
    Some(p)
  }
}

///|
fn perms_of_p(
  iso : Bool,
  encrypt_metadata : Bool,
  p : Int,
  utf8pw : String,
  o : String,
  oe : String,
  u : String,
  digest : Array[Int],
) -> @pdfio.MutableBytes raise {
  let prims = @pdfcryptprimitives.PdfCryptPrimitives::new()
  let extendedp = UInt64::lor(
    0xffff_ffff_0000_0000,
    UInt64::extend_uint(p.reinterpret_as_uint()),
  )
  let b = Array::make(16, 0)
  for n in 0..<8 {
    let v = (extendedp >> (n * 8)).to_int()
    b[n] = Int::land(v, 0xff)
  }
  b[8] = if encrypt_metadata { 'T'.to_int() } else { 'F'.to_int() }
  b[9] = 'a'.to_int()
  b[10] = 'd'.to_int()
  b[11] = 'b'.to_int()
  let key = @pdfio.int_array_of_bytes(
    file_encryption_key_aesv3(iso, utf8pw, o, oe, u, digest~),
  )
  prims.aes_encrypt_data_ecb(8, key, @pdfio.bytes_of_int_array(b))
}

///|
fn make_ue(
  iso : Bool,
  file_encryption_key : @pdfio.MutableBytes,
  user_pw : String,
  user_validation_salt : String,
  user_key_salt : String,
) -> (String, String) raise {
  let prims = @pdfcryptprimitives.PdfCryptPrimitives::new()
  let hash = fn(s : String) -> String raise {
    if iso {
      shamix(user_pw, None, s)
    } else {
      prims.sha256(@pdfio.Input::of_string(s))
    }
  }
  let u = hash(user_pw + user_validation_salt) +
    user_validation_salt +
    user_key_salt
  let ue = prims.aes_encrypt_data(
    8,
    @pdfio.int_array_of_string(hash(user_pw + user_key_salt)),
    file_encryption_key,
    firstblock=@pdfio.int_array_of_string(zero_iv),
  )
  let ue_slice = bytes_slice(ue, 16, 32)
  (u, @pdfio.string_of_bytes(ue_slice))
}

///|
fn make_oe(
  iso : Bool,
  file_encryption_key : @pdfio.MutableBytes,
  owner_pw : String,
  owner_validation_salt : String,
  owner_key_salt : String,
  u : String,
) -> (String, String, Array[Int]) raise {
  let prims = @pdfcryptprimitives.PdfCryptPrimitives::new()
  let hash = fn(s : String) -> String raise {
    if iso {
      shamix(owner_pw, Some(u), s)
    } else {
      prims.sha256(@pdfio.Input::of_string(s))
    }
  }
  let o = hash(owner_pw + owner_validation_salt + u) +
    owner_validation_salt +
    owner_key_salt
  let digest = @pdfio.int_array_of_string(hash(owner_pw + owner_key_salt + u))
  let oe = prims.aes_encrypt_data(
    8,
    digest,
    file_encryption_key,
    firstblock=@pdfio.int_array_of_string(zero_iv),
  )
  let oe_slice = bytes_slice(oe, 16, 32)
  (o, @pdfio.string_of_bytes(oe_slice), digest)
}