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

use crate::rng::RandomNumberGenerator;

const BCRYPT_SIZE: usize = 60;

/// Produce a bcrypt password hash
///
/// # Examples
///
/// ```
/// let mut rng = botan::RandomNumberGenerator::new().unwrap();
/// let bcrypt1 = botan::bcrypt_hash("password", &mut rng, 10).unwrap();
/// let bcrypt2 = botan::bcrypt_hash("password", &mut rng, 10).unwrap();
/// assert_ne!(bcrypt1, bcrypt2); // different salt each time
/// ```
pub fn bcrypt_hash(
    pass: &str,
    rng: &mut RandomNumberGenerator,
    workfactor: usize,
) -> Result<String> {
    let mut out = vec![0; BCRYPT_SIZE + 1];
    let mut out_len = out.len();

    botan_call!(
        botan_bcrypt_generate,
        out.as_mut_ptr(),
        &mut out_len,
        make_cstr(pass)?.as_ptr(),
        rng.handle(),
        workfactor,
        0u32
    )?;

    out.resize(out_len - 1, 0);
    String::from_utf8(out).map_err(Error::conversion_error)
}

/// Verify a bcrypt password hash
///
/// # Examples
///
/// ```
/// let mut rng = botan::RandomNumberGenerator::new().unwrap();
/// let bcrypt = botan::bcrypt_hash("password", &mut rng, 10).unwrap();
/// assert_eq!(botan::bcrypt_verify("not even close", &bcrypt), Ok(false));
/// assert_eq!(botan::bcrypt_verify("password", &bcrypt), Ok(true));
/// ```
pub fn bcrypt_verify(pass: &str, hash: &str) -> Result<bool> {
    let rc = unsafe { botan_bcrypt_is_valid(make_cstr(pass)?.as_ptr(), make_cstr(hash)?.as_ptr()) };

    if rc == 0 {
        Ok(true)
    } else if rc == BOTAN_FFI_INVALID_VERIFIER {
        Ok(false)
    } else {
        Err(Error::from_rc(rc))
    }
}
