use super::*;
use snarkvm_utilities::{bits_from_bytes_le, bytes_from_bits_le};
impl<const TYPE: u8, const VARIANT: usize> Hash for Keccak<TYPE, VARIANT> {
    type Input = bool;
    type Output = Vec<bool>;
    #[inline]
    fn hash(&self, input: &[Self::Input]) -> Result<Self::Output> {
        let result = match (TYPE, VARIANT) {
            (0, 224) => bits_from_bytes_le(&keccak_224_native(&bytes_from_bits_le(input))).collect(),
            (0, 256) => bits_from_bytes_le(&keccak_256_native(&bytes_from_bits_le(input))).collect(),
            (0, 384) => bits_from_bytes_le(&keccak_384_native(&bytes_from_bits_le(input))).collect(),
            (0, 512) => bits_from_bytes_le(&keccak_512_native(&bytes_from_bits_le(input))).collect(),
            (1, 224) => bits_from_bytes_le(&sha3_224_native(&bytes_from_bits_le(input))).collect(),
            (1, 256) => bits_from_bytes_le(&sha3_256_native(&bytes_from_bits_le(input))).collect(),
            (1, 384) => bits_from_bytes_le(&sha3_384_native(&bytes_from_bits_le(input))).collect(),
            (1, 512) => bits_from_bytes_le(&sha3_512_native(&bytes_from_bits_le(input))).collect(),
            _ => unreachable!("Invalid Keccak type and variant"),
        };
        Ok(result)
    }
}
fn keccak_224_native(preimage: &[u8]) -> [u8; 28] {
    let mut keccak = TinyKeccak::v224();
    keccak.update(preimage);
    let mut hash = [0u8; 28];
    keccak.finalize(&mut hash);
    hash
}
fn keccak_256_native(preimage: &[u8]) -> [u8; 32] {
    let mut keccak = TinyKeccak::v256();
    keccak.update(preimage);
    let mut hash = [0u8; 32];
    keccak.finalize(&mut hash);
    hash
}
fn keccak_384_native(preimage: &[u8]) -> [u8; 48] {
    let mut keccak = TinyKeccak::v384();
    keccak.update(preimage);
    let mut hash = [0u8; 48];
    keccak.finalize(&mut hash);
    hash
}
fn keccak_512_native(preimage: &[u8]) -> [u8; 64] {
    let mut keccak = TinyKeccak::v512();
    keccak.update(preimage);
    let mut hash = [0u8; 64];
    keccak.finalize(&mut hash);
    hash
}
fn sha3_224_native(preimage: &[u8]) -> [u8; 28] {
    let mut keccak = TinySha3::v224();
    keccak.update(preimage);
    let mut hash = [0u8; 28];
    keccak.finalize(&mut hash);
    hash
}
fn sha3_256_native(preimage: &[u8]) -> [u8; 32] {
    let mut keccak = TinySha3::v256();
    keccak.update(preimage);
    let mut hash = [0u8; 32];
    keccak.finalize(&mut hash);
    hash
}
fn sha3_384_native(preimage: &[u8]) -> [u8; 48] {
    let mut keccak = TinySha3::v384();
    keccak.update(preimage);
    let mut hash = [0u8; 48];
    keccak.finalize(&mut hash);
    hash
}
fn sha3_512_native(preimage: &[u8]) -> [u8; 64] {
    let mut keccak = TinySha3::v512();
    keccak.update(preimage);
    let mut hash = [0u8; 64];
    keccak.finalize(&mut hash);
    hash
}
#[cfg(test)]
mod tests {
    use super::*;
    use crate::Rng;
    use snarkvm_utilities::{bits_from_bytes_le, bytes_from_bits_le};
    macro_rules! check_equivalence {
        ($console:expr, $native:expr) => {
            let rng = &mut TestRng::default();
            let mut input_sizes = vec![1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 16, 32, 64, 128, 256, 512, 1024];
            input_sizes.extend((0..100).map(|_| rng.gen_range(1..1024)));
            for num_inputs in input_sizes {
                println!("Checking equivalence for {num_inputs} inputs");
                let input = (0..num_inputs).map(|_| Uniform::rand(rng)).collect::<Vec<bool>>();
                let expected = $native(&bytes_from_bits_le(&input));
                let expected = bits_from_bytes_le(&expected).collect::<Vec<_>>();
                let candidate = $console.hash(&input).unwrap();
                assert_eq!(expected, candidate);
            }
        };
    }
    #[test]
    fn test_keccak_224_equivalence() {
        check_equivalence!(Keccak224::default(), keccak_224_native);
    }
    #[test]
    fn test_keccak_256_equivalence() {
        check_equivalence!(Keccak256::default(), keccak_256_native);
    }
    #[test]
    fn test_keccak_384_equivalence() {
        check_equivalence!(Keccak384::default(), keccak_384_native);
    }
    #[test]
    fn test_keccak_512_equivalence() {
        check_equivalence!(Keccak512::default(), keccak_512_native);
    }
    #[test]
    fn test_sha3_224_equivalence() {
        check_equivalence!(Sha3_224::default(), sha3_224_native);
    }
    #[test]
    fn test_sha3_256_equivalence() {
        check_equivalence!(Sha3_256::default(), sha3_256_native);
    }
    #[test]
    fn test_sha3_384_equivalence() {
        check_equivalence!(Sha3_384::default(), sha3_384_native);
    }
    #[test]
    fn test_sha3_512_equivalence() {
        check_equivalence!(Sha3_512::default(), sha3_512_native);
    }
}