// License: see LICENSE file at root directory of `master` branch

//! # Hash

use {
    core::{
        fmt::{self, Display, Formatter},
        str::FromStr,
    },
    std::io::{Error, ErrorKind},
};

/// # Hash
#[derive(Debug, Eq, PartialEq, Clone)]
pub enum Hash {
    Sha3_224,
    Sha3_256,
    Sha3_384,
    Sha3_512,
    Shake128,
    Shake256,
}

impl From<Hash> for zeros::Hash {

    fn from(hash: Hash) -> Self {
        match hash {
            Hash::Sha3_224 => zeros::Hash::Sha3_224,
            Hash::Sha3_256 => zeros::Hash::Sha3_256,
            Hash::Sha3_384 => zeros::Hash::Sha3_384,
            Hash::Sha3_512 => zeros::Hash::Sha3_512,
            Hash::Shake128 => zeros::Hash::Shake128,
            Hash::Shake256 => zeros::Hash::Shake256,
        }
    }

}

impl FromStr for Hash {

    type Err = Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match parse_sha3_names(s) {
            Some(hash) => Ok(hash),
            None => match parse_shake_names(s) {
                Some(hash) => Ok(hash),
                None => Err(Error::new(ErrorKind::InvalidData, format!("Algorithm name not supported: {:?}", s))),
            },
        }
    }

}

/// # Parses SHA3 name
fn parse_sha3_names(s: &str) -> Option<Hash> {
    for n in &["Sha3_224", "Sha3-224"] {
        if s.eq_ignore_ascii_case(n) {
            return Some(Hash::Sha3_224);
        }
    }
    for n in &["Sha3_256", "Sha3-256"] {
        if s.eq_ignore_ascii_case(n) {
            return Some(Hash::Sha3_256);
        }
    }
    for n in &["Sha3_384", "Sha3-384"] {
        if s.eq_ignore_ascii_case(n) {
            return Some(Hash::Sha3_384);
        }
    }
    for n in &["Sha3_512", "Sha3-512"] {
        if s.eq_ignore_ascii_case(n) {
            return Some(Hash::Sha3_512);
        }
    }
    None
}

/// # Parses Shake name
fn parse_shake_names(s: &str) -> Option<Hash> {
    for n in &["Shake128", "Shake_128", "Shake-128"] {
        if s.eq_ignore_ascii_case(n) {
            return Some(Hash::Shake128);
        }
    }
    for n in &["Shake256", "Shake_256", "Shake-256"] {
        if s.eq_ignore_ascii_case(n) {
            return Some(Hash::Shake256);
        }
    }
    None
}

impl Display for Hash {

    fn fmt(&self, f: &mut Formatter) -> Result<(), fmt::Error> {
        match self {
            Hash::Sha3_224 => f.write_str("SHA3-224"),
            Hash::Sha3_256 => f.write_str("SHA3-256"),
            Hash::Sha3_384 => f.write_str("SHA3-384"),
            Hash::Sha3_512 => f.write_str("SHA3-512"),
            Hash::Shake128 => f.write_str("Shake-128"),
            Hash::Shake256 => f.write_str("Shake-256"),
        }
    }

}

#[test]
fn test_parsing_hash_from_strings() {
    for s in &["Sha3_224", "Sha3-224"] {
        assert_eq!(Hash::from_str(s).unwrap(), Hash::Sha3_224);
    }
    for s in &["Sha3_256", "Sha3-256"] {
        assert_eq!(Hash::from_str(s).unwrap(), Hash::Sha3_256);
    }
    for s in &["Sha3_384", "Sha3-384"] {
        assert_eq!(Hash::from_str(s).unwrap(), Hash::Sha3_384);
    }
    for s in &["Sha3_512", "Sha3-512"] {
        assert_eq!(Hash::from_str(s).unwrap(), Hash::Sha3_512);
    }

    for s in &["Shake128", "Shake_128", "Shake-128"] {
        assert_eq!(Hash::from_str(s).unwrap(), Hash::Shake128);
    }
    for s in &["Shake256", "Shake_256", "Shake-256"] {
        assert_eq!(Hash::from_str(s).unwrap(), Hash::Shake256);
    }
}
