//! Algorithm parameters.

use crate::{Error, Result};
use core::{
    default::Default,
    fmt::{self, Display},
    str::FromStr,
};

/// Name of the parameter when serialized in an MCF string.
const ROUNDS_PARAM: &str = "rounds=";

/// Algorithm parameters.
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub struct Params {
    /// Number of times to apply the digest function
    pub(crate) rounds: u32,
}

impl Params {
    /// Recommended parameters.
    pub const RECOMMENDED: Self = Self {
        rounds: Self::RECOMMENDED_ROUNDS,
    };

    /// Recommended number of rounds.
    pub const RECOMMENDED_ROUNDS: u32 = 5_000;

    /// Minimum number of rounds allowed.
    pub const ROUNDS_MIN: u32 = 1_000;

    /// Maximum number of rounds allowed.
    pub const ROUNDS_MAX: u32 = 999_999_999;

    /// Create new algorithm parameters.
    ///
    /// # Errors
    /// Returns [`Error::RoundsInvalid`] if `rounds` is outside the range [`Params::ROUNDS_MIN`]
    /// to [`Params::ROUNDS_MAX`] inclusive.
    pub fn new(rounds: u32) -> Result<Params> {
        match rounds {
            Self::ROUNDS_MIN..=Self::ROUNDS_MAX => Ok(Params { rounds }),
            _ => Err(Error::RoundsInvalid),
        }
    }
}

impl Default for Params {
    fn default() -> Self {
        Params::RECOMMENDED
    }
}

impl Display for Params {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "rounds={}", self.rounds)
    }
}

impl FromStr for Params {
    type Err = Error;

    fn from_str(s: &str) -> Result<Self> {
        if s.is_empty() {
            return Ok(Self::default());
        }

        if let Some(rounds_str) = s.strip_prefix(ROUNDS_PARAM) {
            Self::new(rounds_str.parse().map_err(|_| Error::RoundsInvalid)?)
        } else {
            Err(Error::ParamsInvalid)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::Params;

    #[test]
    fn test_sha256_crypt_invalid_rounds() {
        let params = Params::new(Params::ROUNDS_MAX + 1);
        assert!(params.is_err());

        let params = Params::new(Params::ROUNDS_MIN - 1);
        assert!(params.is_err());
    }

    #[test]
    fn test_sha512_crypt_invalid_rounds() {
        let params = Params::new(Params::ROUNDS_MAX + 1);
        assert!(params.is_err());

        let params = Params::new(Params::ROUNDS_MIN - 1);
        assert!(params.is_err());
    }
}
