use crate::utils::*;
use botan_sys::*;

use crate::mp::MPI;

/// Represents an instance of format preserving encryption
///
/// # Examples
///
/// ```
/// use std::str::FromStr;
/// let modulus = botan::MPI::from_str("1000000000").unwrap();
/// let key = vec![0; 32];
/// let rounds = 16;
/// let compat_mode = false;
/// let fpe = botan::FPE::new_fe1(&modulus, &key, rounds, compat_mode).unwrap();
/// let input = botan::MPI::from_str("9392024").unwrap();
/// let tweak = vec![1,2,3,4,5];
/// let ctext = fpe.encrypt(&input, &tweak).unwrap();
/// assert!(ctext < modulus);
/// let ptext = fpe.decrypt(&ctext, &tweak).unwrap();
/// assert_eq!(ptext, input);
/// ```
#[derive(Debug)]
#[allow(clippy::upper_case_acronyms)]
pub struct FPE {
    obj: botan_fpe_t,
}

unsafe impl Sync for FPE {}
unsafe impl Send for FPE {}

botan_impl_drop!(FPE, botan_fpe_destroy);

impl FPE {
    /// Create a new FPE instance, FE1 scheme
    /// Rounds should be 16 or higher for best security
    pub fn new_fe1(modulus: &MPI, key: &[u8], rounds: usize, compat_mode: bool) -> Result<FPE> {
        let flags = u32::from(compat_mode);

        let obj = botan_init!(
            botan_fpe_fe1_init,
            modulus.handle(),
            key.as_ptr(),
            key.len(),
            rounds,
            flags
        )?;

        Ok(FPE { obj })
    }

    /// Encrypt value under the FPE scheme using provided tweak
    pub fn encrypt(&self, x: &MPI, tweak: &[u8]) -> Result<MPI> {
        let r = x.duplicate()?;
        botan_call!(
            botan_fpe_encrypt,
            self.obj,
            r.handle(),
            tweak.as_ptr(),
            tweak.len()
        )?;
        Ok(r)
    }

    /// Decrypt value under the FPE scheme using provided tweak
    pub fn decrypt(&self, x: &MPI, tweak: &[u8]) -> Result<MPI> {
        let r = x.duplicate()?;
        botan_call!(
            botan_fpe_decrypt,
            self.obj,
            r.handle(),
            tweak.as_ptr(),
            tweak.len()
        )?;
        Ok(r)
    }
}
