use std::cmp::min;

use crate::common::paddings::{Method2, PaddingModeImpl};
use crate::libs::utils::{bytes_to_dwords, dwords_to_bytes, dwords_xor};

use super::constants::{SM3_BLOCK_SIZE as BLOCK_SIZE, SM3_OUTPUT_SIZE as OUTPUT_SIZE, SM3_IV as IV};
use super::CryptographicHashImpl;


/// SM3密码杂凑算法流式计算器（SM3 Cryptographic Hash Algorthm Stream Mode Calculator）
///
/// SM3（商密3）是中华人民共和国政府采用的一种密码杂凑算法标准，由国家密码管理局于2010年12月17日发布。2016年，成为中国国家密码标准（GB/T 32905-2016）。\
/// ShangMi 3 (SM3) is a cryptographic hash function used in the Chinese National Standard.
/// It was published by the National Cryptography Administration on 2010-12-17.
///
/// 在商用密码体系中，SM3主要用于数字签名及验证、消息鉴别码生成及验证、随机数生成等，其算法公开，安全性及效率与SHA-256相当。\
/// SM3 is used for implementing digital signatures, message authentication codes, and pseudorandom number generators.
///  The algorithm is public and is considered similar to SHA-256 in security and efficiency.
///
/// # 示例（Examples）
///
/// ```rust
/// # fn main() -> Result<(), ()> {
/// use neuedu_cryptos::cryptographic_hashs::{CryptographicHashImpl, SM3};
///
/// const MESSAGE: [u8; 3] = [
///     0x61, 0x62, 0x63,
/// ];
///
/// let mut instance = SM3::default();
/// instance.update(&MESSAGE)?;
/// let digest = instance.r#final()?;
///
/// assert_eq!(digest, [
///     0x66, 0xC7, 0xF0, 0xF4, 0x62, 0xEE, 0xED, 0xD9,
///     0xD1, 0xF2, 0xD4, 0x6B, 0xDC, 0x10, 0xE4, 0xE2,
///     0x41, 0x67, 0xC4, 0x87, 0x5C, 0xF2, 0xF7, 0xA2,
///     0x29, 0x7D, 0xA0, 0x2B, 0x8F, 0x4B, 0xA8, 0xE0,
/// ]);
/// #
/// #     Ok(())
/// # }
/// ```
pub struct SM3 {
    buffer: [u8; BLOCK_SIZE as usize],
    total_length: u64,
    h: [u32; 8],
}

impl SM3 {

    fn new() -> Self {
        Self {
            buffer: [0u8; BLOCK_SIZE as usize],
            total_length: 0,
            h: IV,
        }
    }

    const fn f42_t(j: u8) -> u32 {
        match j {
            0..=15 => 0x79CC4519,
            16..=63 => 0x7A879D8A,
            _ => unreachable!(),
        }
    }

    const fn f43_ff(j: u8, x: u32, y: u32, z: u32) -> u32 {
        match j {
            0..=15 => x ^ y ^ z,
            16..=63 => (x & y) | (x & z) | (y & z),
            _ => unreachable!(),
        }
    }

    const fn f43_gg(j: u8, x: u32, y: u32, z: u32) -> u32 {
        match j {
            0..=15 => x ^ y ^ z,
            16..=63 => (x & y) | (!x & z),
            _ => unreachable!(),
        }
    }

    const fn f44_p0(x: u32) -> u32 {
        x ^ x.rotate_left(9) ^ x.rotate_left(17)
    }

    const fn f44_p1(x: u32) -> u32 {
        x ^ x.rotate_left(15) ^ x.rotate_left(23)
    }

    fn padding(&mut self) -> Box<[u8]> {
        let message_length = self.total_length * 8;

        let pad_message = Method2::new(BLOCK_SIZE as usize).pad(&self.buffer[..self.total_length as usize % BLOCK_SIZE as usize]);

        let mut result = Vec::<u8>::with_capacity(BLOCK_SIZE as usize * 2);
        match pad_message.len() {
            1..=8 => {
                result.append(&mut pad_message.into());
                result.resize(BLOCK_SIZE as usize - 8, 0);
            },
            9.. => {
                result.append(&mut pad_message[0..pad_message.len() - 8].into());
            },
            _ => unreachable!(),
        }
        result.append(&mut message_length.to_be_bytes().into());

        result.into()
    }

    fn extend(&self) -> ([u32; 68], [u32; 64]) {
        let words = bytes_to_dwords(&self.buffer);

        let mut w: [u32; 68] = [0u32; 68];
        w[..16].copy_from_slice(&words[..16]);
        for j in 16..=67 {
            w[j] = Self::f44_p1(w[j - 16] ^ w[j - 9] ^ w[j - 3].rotate_left(15)) ^ w[j - 13].rotate_left(7) ^ w[j - 6];
        }
        let mut w_1: [u32; 64] = [0u32; 64];
        for j in 0..=63 {
            w_1[j] = w[j] ^ w[j + 4];
        }

        (w, w_1)
    }

    fn compress(&mut self, (w, w_1): ([u32; 68], [u32; 64])) {
        let [mut a, mut b, mut c, mut d, mut e, mut f, mut g, mut h] = self.h;
        for j in 0..=63 {
            let ss1 = ((a.rotate_left(12)).wrapping_add(e).wrapping_add(Self::f42_t(j).rotate_left((j & 31) as u32))).rotate_left(7);
            let ss2 = ss1 ^ a.rotate_left(12);
            let tt1 = Self::f43_ff(j, a, b, c).wrapping_add(d).wrapping_add(ss2).wrapping_add(w_1[j as usize]);
            let tt2 = Self::f43_gg(j, e, f, g).wrapping_add(h).wrapping_add(ss1).wrapping_add(w[j as usize]);
            d = c;
            c = b.rotate_left(9);
            b = a;
            a = tt1;
            h = g;
            g = f.rotate_left(19);
            f = e;
            e = Self::f44_p0(tt2);
        }

        let result = dwords_xor(&self.h, &[a, b, c, d, e, f, g, h]);
        self.h.copy_from_slice(&result);
    }
}

impl Default for SM3 {

    /// 创建新的SM3实例。\
    /// Create a new SM3 instance。
    fn default() -> Self {
        Self::new()
    }
}

impl CryptographicHashImpl for SM3 {

    /// 块长度：64字节。\
    /// Block size: 64 Bytes.
    fn block_size(&self) -> usize {
        BLOCK_SIZE as usize
    }

    /// 输出长度：32字节。\
    /// Output size: 32 Bytes.
    fn output_size(&self) -> usize {
        OUTPUT_SIZE as usize
    }

    fn update(&mut self, message: &[u8]) -> Result<(), ()> {
        let mut overflow;
        let mut offset: usize = 0;
        while offset < message.len() {
            let current_length = min(
                message.len() - offset,
                BLOCK_SIZE as usize - self.total_length as usize % BLOCK_SIZE as usize
            ) as u8;
            self.buffer[(self.total_length as usize % BLOCK_SIZE as usize)..(self.total_length as usize % BLOCK_SIZE as usize + current_length as usize)]
                .copy_from_slice(&message[offset..(offset + current_length as usize)]);

            (self.total_length, overflow) = self.total_length.overflowing_add(current_length as u64);
            if overflow {
                return Err(());
            }
            offset += current_length as usize;

            if self.total_length as usize % BLOCK_SIZE as usize == 0 {
                self.compress(self.extend());
            }
        }

        Ok(())
    }

    fn r#final(&mut self) -> Result<Vec<u8>, ()> {
        let pad_message = self.padding();
        self.update(&pad_message).unwrap();

        Ok(dwords_to_bytes(&self.h).into())
    }
}
