use crate::cryptographic_hashs::{CryptographicHash, CryptographicHashImpl, SHA1, SHA2, SM3};
use crate::libs::utils::bytes_xor;


/// 密钥杂凑消息鉴别码流式计算器（Keyed-Hash Message Authentication Code Stream Mode Calculator）
///
/// # 示例（Examples）
///
/// ```rust
/// # fn main() -> Result<(), ()> {
/// use neuedu_cryptos::cryptographic_hashs::CryptographicHash;
/// use neuedu_cryptos::mac::HMAC;
///
/// let mut instance = HMAC::new(b"key", CryptographicHash::SM3);
/// instance.update(b"message")?;
/// let mac = instance.r#final()?;
///
/// assert_eq!(mac, [
///     0x34, 0xC9, 0x0B, 0x29, 0x1A, 0x80, 0xB7, 0xFD,
///     0x7F, 0x2B, 0xE4, 0x3D, 0x68, 0x39, 0x35, 0xBA,
///     0xB9, 0xD1, 0x49, 0x16, 0x46, 0x66, 0xC4, 0xEF,
///     0x88, 0x57, 0xC8, 0x15, 0xCF, 0x2E, 0xF8, 0x32,
/// ]);
/// #
/// #     Ok(())
/// # }
/// ```
pub struct HMAC {
    hash: CryptographicHash,

    inner_hash: Option<Box<dyn CryptographicHashImpl>>,
    outer_hash: Option<Box<dyn CryptographicHashImpl>>,
}

impl HMAC {

    /// 创建一个密钥杂凑消息鉴别码实例。\
    /// Create a Keyed-Hash Message Authentication Code Instance.
    pub fn new(key: &[u8], hash: CryptographicHash) -> Self {
        let mut instance = Self {
            hash,

            inner_hash: None,
            outer_hash: None,
        };

        instance.inner_hash = Some(instance.get_cryptographic_hash());
        instance.outer_hash = Some(instance.get_cryptographic_hash());

        let block_sized_key = instance.compute_block_sized_key(key);
        let mut o_key_pad = vec![0x5C; block_sized_key.len()];
        o_key_pad = bytes_xor(&block_sized_key, &o_key_pad).into();
        let mut i_key_pad = vec![0x36; block_sized_key.len()];
        i_key_pad = bytes_xor(&block_sized_key, &i_key_pad).into();

        instance.inner_hash.as_mut().expect("").update(&i_key_pad).unwrap();
        instance.outer_hash.as_mut().expect("").update(&o_key_pad).unwrap();

        instance
    }

    /// 填入任意长度的待计算的消息。\
    /// Feed a message of any length to be calculated.
    ///
    /// # 参数（Arguments）
    ///
    /// * message - 消息（Message）
    ///
    ///   任意长度的消息。\
    ///   Message of any length.
    pub fn update(&mut self, message: &[u8]) -> Result<(), ()> {
        self.inner_hash.as_mut().expect("").update(message)
    }

    /// 结束填入待计算的消息。\
    /// End feeding in messages to be calculated.
    pub fn r#final(&mut self) -> Result<Vec<u8>, ()> {
        let inner_digest = self.inner_hash.as_mut().expect("").r#final().unwrap();
        self.outer_hash.as_mut().expect("").update(&inner_digest).unwrap();

        self.outer_hash.as_mut().expect("").r#final()
    }

    fn compute_block_sized_key(&self, key: &[u8]) -> Vec<u8> {
        let mut cryptographic_hash = self.get_cryptographic_hash();
        let mut block_sized_key = Vec::<u8>::with_capacity(cryptographic_hash.block_size());

        // Keys longer than blockSize are shortened by hashing them
        if key.len() > cryptographic_hash.block_size() {
            cryptographic_hash.update(key).unwrap();
            block_sized_key.append(&mut cryptographic_hash.r#final().unwrap());

            return block_sized_key;
        }

        // Keys shorter than blockSize are padded to blockSize by padding with zeros on the right
        if key.len() < cryptographic_hash.block_size() {
            block_sized_key.append(&mut key.into());
            // Pad key with zeros to make it blockSize bytes long
            block_sized_key.resize(cryptographic_hash.block_size(), 0);

            return block_sized_key;
        }

        key.into()
    }

    fn get_cryptographic_hash(&self) -> Box<dyn CryptographicHashImpl> {
        match self.hash {
            CryptographicHash::SM3 => Box::<SM3>::default(),
            CryptographicHash::SHA1 => Box::<SHA1>::default(),
            CryptographicHash::SHA224 => Box::new(SHA2::new(CryptographicHash::SHA224)),
            CryptographicHash::SHA256 => Box::new(SHA2::new(CryptographicHash::SHA256)),
            CryptographicHash::SHA384 => Box::new(SHA2::new(CryptographicHash::SHA384)),
            CryptographicHash::SHA512 => Box::new(SHA2::new(CryptographicHash::SHA512)),
        }
    }
}
