//! SHA-512/224 and SHA-512/256 hash functions (FIPS 180-4).
//!
//! Truncated variants of SHA-512 with distinct IV values generated
//! per FIPS 180-4 §5.3.6. These use the SHA-512 compression
//! function (128-byte blocks, 64-bit words) but produce shorter
//! outputs, which can matter for constrained protocols.

use super::sha512::Sha512;
use crate::Hasher;

// IV for SHA-512/224 (FIPS 180-4 §5.3.6.1).
const H0_512_224: [u64; 8] = [
    0x8C3D37C819544DA2,
    0x73E1996689DCD4D6,
    0x1DFAB7AE32FF9C82,
    0x679DD514582F9FCF,
    0x0F6D2B697BD44DA8,
    0x77E36F7304C48942,
    0x3F9D85A86A1D36C8,
    0x1112E6AD91D692A1,
];

// IV for SHA-512/256 (FIPS 180-4 §5.3.6.2).
const H0_512_256: [u64; 8] = [
    0x22312194FC2BF72C,
    0x9F555FA3C84C64C2,
    0x2393B86B6F53B151,
    0x963877195940EABD,
    0x96283EE2A88EFFE3,
    0xBE5E1E2553863992,
    0x2B0199FC2C85B8AA,
    0x0EB72DDC81C52CA2,
];

/// SHA-512/224 hasher (FIPS 180-4). 224-bit output, 128-byte blocks.
///
/// Uses the SHA-512 compression function (efficient on 64-bit
/// hardware) with a distinct IV and a 28-byte truncation.
#[derive(Clone)]
pub struct Sha512_224 {
    inner: Sha512,
}

impl Hasher for Sha512_224 {
    const OUTPUT_LEN: usize = 28;
    const BLOCK_LEN: usize = 128;

    fn new() -> Self {
        Self {
            inner: Sha512::new_with_iv(H0_512_224),
        }
    }

    fn update(&mut self, data: &[u8]) {
        self.inner.update(data);
    }

    fn finalize(self) -> Vec<u8> {
        let mut out = vec![0u8; 28];
        self.finalize_into(&mut out);
        out
    }

    fn finalize_into(self, out: &mut [u8]) {
        let mut full = [0u8; 64];
        self.inner.finalize_into(&mut full);
        let len = out.len().min(28);
        out[..len].copy_from_slice(&full[..len]);
    }
}

/// SHA-512/256 hasher (FIPS 180-4). 256-bit output, 128-byte blocks.
///
/// Uses the SHA-512 compression function with a distinct IV and a
/// 32-byte truncation. Provides 128-bit collision resistance (same
/// as SHA-256) but runs on the 64-bit oriented SHA-512 core, which
/// is faster than SHA-256 on 64-bit platforms.
#[derive(Clone)]
pub struct Sha512_256 {
    inner: Sha512,
}

impl Hasher for Sha512_256 {
    const OUTPUT_LEN: usize = 32;
    const BLOCK_LEN: usize = 128;

    fn new() -> Self {
        Self {
            inner: Sha512::new_with_iv(H0_512_256),
        }
    }

    fn update(&mut self, data: &[u8]) {
        self.inner.update(data);
    }

    fn finalize(self) -> Vec<u8> {
        let mut out = vec![0u8; 32];
        self.finalize_into(&mut out);
        out
    }

    fn finalize_into(self, out: &mut [u8]) {
        let mut full = [0u8; 64];
        self.inner.finalize_into(&mut full);
        let len = out.len().min(32);
        out[..len].copy_from_slice(&full[..len]);
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::Hasher;

    // FIPS 180-4 examples (from NIST CSRC).

    #[test]
    fn test_sha512_224_empty() {
        let digest = Sha512_224::hash(b"");
        let expected: [u8; 28] = [
            0x6e, 0xd0, 0xdd, 0x02, 0x80, 0x6f, 0xa8, 0x9e, 0x25, 0xde, 0x06, 0x0c, 0x19, 0xd3, 0xac, 0x86, 0xca, 0xbb,
            0x87, 0xd6, 0xa0, 0xdd, 0xd0, 0x5c, 0x33, 0x3b, 0x84, 0xf4,
        ];
        assert_eq!(&digest[..], &expected[..]);
    }

    #[test]
    fn test_sha512_224_abc() {
        let digest = Sha512_224::hash(b"abc");
        let expected: [u8; 28] = [
            0x46, 0x34, 0x27, 0x0F, 0x70, 0x7B, 0x6A, 0x54, 0xDA, 0xAE, 0x75, 0x30, 0x46, 0x08, 0x42, 0xE2, 0x0E, 0x37,
            0xED, 0x26, 0x5C, 0xEE, 0xE9, 0xA4, 0x3E, 0x89, 0x24, 0xAA,
        ];
        assert_eq!(&digest[..], &expected[..]);
    }

    #[test]
    fn test_sha512_256_empty() {
        let digest = Sha512_256::hash(b"");
        let expected: [u8; 32] = [
            0xc6, 0x72, 0xb8, 0xd1, 0xef, 0x56, 0xed, 0x28, 0xab, 0x87, 0xc3, 0x62, 0x2c, 0x51, 0x14, 0x06, 0x9b, 0xdd,
            0x3a, 0xd7, 0xb8, 0xf9, 0x73, 0x74, 0x98, 0xd0, 0xc0, 0x1e, 0xce, 0xf0, 0x96, 0x7a,
        ];
        assert_eq!(&digest[..], &expected[..]);
    }

    #[test]
    fn test_sha512_256_abc() {
        let digest = Sha512_256::hash(b"abc");
        let expected: [u8; 32] = [
            0x53, 0x04, 0x8E, 0x26, 0x81, 0x94, 0x1E, 0xF9, 0x9B, 0x2E, 0x29, 0xB7, 0x6B, 0x4C, 0x7D, 0xAB, 0xE4, 0xC2,
            0xD0, 0xC6, 0x34, 0xFC, 0x6D, 0x46, 0xE0, 0xE2, 0xF1, 0x31, 0x07, 0xE7, 0xAF, 0x23,
        ];
        assert_eq!(&digest[..], &expected[..]);
    }
}
