//! RustCrypto trait bridges (feature-gated with `rust-crypto-traits`).
//!
//! When the `rust-crypto-traits` feature is enabled, this module exposes
//! **wrapper types** that implement the standard RustCrypto traits
//! (`digest::Digest`, `digest::ExtendableOutput`, ...) by delegating to
//! our native primitives. Users who want to plug arcana into
//! the wider Rust crypto ecosystem (HMAC, HKDF, PBKDF2, MAC traits,
//! ...) use these wrappers; users who don't enable the feature don't
//! pay the dependency cost.
//!
//! # Why wrappers and not direct impls?
//!
//! We deliberately do **not** implement `digest::Digest` directly on
//! our native `crate::hash::Sha256` etc. Two reasons:
//!
//! 1. **Isolation**: all the RustCrypto glue lives in this one file.
//!    The native modules stay focused on their cryptographic primitive
//!    and are independent of any external ecosystem's version churn.
//!
//! 2. **No accidental ambient trait exposure**: if we impl'd
//!    `Digest` directly, adding a new method to our native `Hasher`
//!    trait could inadvertently clash with the same-named method
//!    from `digest::Digest` on the native type. Wrappers keep the
//!    two trait universes cleanly separated.
//!
//! The performance cost of the wrapper is zero: it is a newtype over
//! the native state and the trait impls are all `#[inline]`
//! single-line delegations.
//!
//! # Example
//!
//! ```rust,ignore
//! use arcana::bridge::Sha256;
//! use digest::Digest;
//!
//! // Generic over any `digest::Digest` implementor, including our
//! // Sha256 wrapper.
//! fn hash<D: Digest>(data: &[u8]) -> Vec<u8> {
//!     let mut h = D::new();
//!     h.update(data);
//!     h.finalize().to_vec()
//! }
//!
//! let out = hash::<Sha256>(b"hello");
//! assert_eq!(out.len(), 32);
//! ```
//!
//! # Scope
//!
//! This commit bridges:
//!
//! | Native type         | Wrapper        | Traits                              |
//! |---------------------|----------------|-------------------------------------|
//! | `hash::Sha1`        | `Sha1`         | `Digest` (20-byte output)           |
//! | `hash::Sha256`      | `Sha256`       | `Digest` (32-byte output)           |
//! | `hash::Sha384`     | `Sha384`       | `Digest` (48-byte output)           |
//! | `hash::Sha512`      | `Sha512`       | `Digest` (64-byte output)           |
//! | `hash::Sha3_256`    | `Sha3_256`     | `Digest` (32-byte output)           |
//! | `hash::Sha3_384`    | `Sha3_384`     | `Digest` (48-byte output)           |
//! | `hash::Sha3_512`    | `Sha3_512`     | `Digest` (64-byte output)           |
//! | `hash::Ripemd160`   | `Ripemd160`    | `Digest` (20-byte output)           |
//! | `hash::Shake128`    | `Shake128`     | `ExtendableOutput`                  |
//! | `hash::Shake256`    | `Shake256`     | `ExtendableOutput`                  |
//!
//! Block-cipher and signature bridges are a follow-up ticket (the
//! `cipher` 0.4 and `signature` 2.0 trait machinery is more involved
//! and deserves its own commit).

#![cfg(feature = "rust-crypto-traits")]

use digest::{
    FixedOutput, HashMarker, Output, OutputSizeUser, Reset, Update, XofReader,
    typenum::{U20, U32, U48, U64},
};

use crate::Hasher as NativeHasher;
use crate::Xof as NativeXof;

// ============================================================================
// Fixed-output hash wrappers (SHA-1, SHA-2, SHA-3, RIPEMD-160)
// ============================================================================

/// Emit a wrapper newtype + the 5 trait impls required for the
/// `digest::Digest` blanket impl to fire:
/// `OutputSizeUser`, `HashMarker`, `Update`, `FixedOutput`, `Default`.
///
/// Also implements `Reset` and `FixedOutputReset` so callers get
/// `Digest::finalize_reset` / `finalize_into_reset` for free.
macro_rules! bridge_fixed_hash {
    ($wrapper:ident, $native:path, $out_size:ty, $doc:literal) => {
        #[doc = $doc]
        #[derive(Clone)]
        pub struct $wrapper($native);

        impl Default for $wrapper {
            #[inline]
            fn default() -> Self {
                Self(<$native as NativeHasher>::new())
            }
        }

        impl OutputSizeUser for $wrapper {
            type OutputSize = $out_size;
        }

        impl HashMarker for $wrapper {}

        impl Update for $wrapper {
            #[inline]
            fn update(&mut self, data: &[u8]) {
                NativeHasher::update(&mut self.0, data);
            }
        }

        impl FixedOutput for $wrapper {
            #[inline]
            fn finalize_into(self, out: &mut Output<Self>) {
                NativeHasher::finalize_into(self.0, out.as_mut_slice());
            }
        }

        impl Reset for $wrapper {
            #[inline]
            fn reset(&mut self) {
                self.0 = <$native as NativeHasher>::new();
            }
        }

        impl digest::FixedOutputReset for $wrapper {
            #[inline]
            fn finalize_into_reset(&mut self, out: &mut Output<Self>) {
                let taken = core::mem::replace(&mut self.0, <$native as NativeHasher>::new());
                NativeHasher::finalize_into(taken, out.as_mut_slice());
            }
        }
    };
}

bridge_fixed_hash!(
    Sha1,
    crate::hash::sha1::Sha1,
    U20,
    "`digest::Digest` wrapper for [`crate::hash::sha1::Sha1`] (SHA-1, 160-bit)."
);
bridge_fixed_hash!(
    Sha256,
    crate::hash::sha256::Sha256,
    U32,
    "`digest::Digest` wrapper for [`crate::hash::sha256::Sha256`]."
);
bridge_fixed_hash!(
    Sha384,
    crate::hash::sha384::Sha384,
    U48,
    "`digest::Digest` wrapper for [`crate::hash::sha384::Sha384`]."
);
bridge_fixed_hash!(
    Sha512,
    crate::hash::sha512::Sha512,
    U64,
    "`digest::Digest` wrapper for [`crate::hash::sha512::Sha512`]."
);
bridge_fixed_hash!(
    Sha3_256,
    crate::hash::sha3::Sha3_256,
    U32,
    "`digest::Digest` wrapper for [`crate::hash::sha3::Sha3_256`]."
);
bridge_fixed_hash!(
    Sha3_384,
    crate::hash::sha3::Sha3_384,
    U48,
    "`digest::Digest` wrapper for [`crate::hash::sha3::Sha3_384`]."
);
bridge_fixed_hash!(
    Sha3_512,
    crate::hash::sha3::Sha3_512,
    U64,
    "`digest::Digest` wrapper for [`crate::hash::sha3::Sha3_512`]."
);
bridge_fixed_hash!(
    Ripemd160,
    crate::hash::ripemd160::Ripemd160,
    U20,
    "`digest::Digest` wrapper for [`crate::hash::ripemd160::Ripemd160`] (RIPEMD-160, 160-bit)."
);

// ============================================================================
// Extendable-output (XOF) wrappers: SHAKE128 / SHAKE256
// ============================================================================

/// Common `XofReader` wrapper: holds an already-initialised native
/// XOF and yields bytes on demand by calling its `squeeze`.
///
/// Used for both SHAKE128 and SHAKE256 since they share the same
/// native `Xof` interface and only differ in construction.
pub struct XofStream<X: NativeXof>(X);

impl<X: NativeXof> XofReader for XofStream<X> {
    #[inline]
    fn read(&mut self, buffer: &mut [u8]) {
        self.0.squeeze(buffer);
    }
}

macro_rules! bridge_xof {
    ($wrapper:ident, $native:path, $doc:literal) => {
        #[doc = $doc]
        #[derive(Clone, Default)]
        pub struct $wrapper($native);

        impl Update for $wrapper {
            #[inline]
            fn update(&mut self, data: &[u8]) {
                NativeXof::update(&mut self.0, data);
            }
        }

        impl Reset for $wrapper {
            #[inline]
            fn reset(&mut self) {
                self.0 = <$native as NativeXof>::new();
            }
        }

        impl digest::ExtendableOutput for $wrapper {
            type Reader = XofStream<$native>;

            #[inline]
            fn finalize_xof(self) -> Self::Reader {
                XofStream(self.0)
            }
        }
    };
}

// Shake128 / Shake256 don't have Default in the native trait, and
// `Default::default()` on the wrapper uses the inner field's default
// via #[derive(Default)]. We add a manual Default on the native
// types indirectly by relying on Default derive working over the
// inner Shake128/Shake256 type -- but those don't derive Default
// either. So we override Default manually:
impl Default for crate::hash::sha3::Shake128 {
    fn default() -> Self {
        <Self as NativeXof>::new()
    }
}
impl Default for crate::hash::sha3::Shake256 {
    fn default() -> Self {
        <Self as NativeXof>::new()
    }
}

bridge_xof!(
    Shake128,
    crate::hash::sha3::Shake128,
    "`digest::ExtendableOutput` wrapper for [`crate::hash::sha3::Shake128`]."
);
bridge_xof!(
    Shake256,
    crate::hash::sha3::Shake256,
    "`digest::ExtendableOutput` wrapper for [`crate::hash::sha3::Shake256`]."
);

// ============================================================================
// Tests — use the wrappers through the RustCrypto `Digest` trait and
// verify they produce byte-identical output to our native hashers.
// ============================================================================

#[cfg(test)]
mod tests {
    use super::*;
    use digest::{Digest as RcDigest, ExtendableOutput as RcExtendableOutput};

    /// SHA-256 empty hash via the `digest::Digest` blanket impl.
    /// Pinned against FIPS 180-4 Appendix A.
    #[test]
    fn bridge_sha256_empty_matches_fips() {
        let out = <Sha256 as RcDigest>::digest(b"");
        let expected = hex_lit::hex("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855");
        assert_eq!(out.as_slice(), expected.as_slice());
    }

    /// SHA-256 "abc" pinned vector, via the trait.
    #[test]
    fn bridge_sha256_abc_matches_fips() {
        let out = <Sha256 as RcDigest>::digest(b"abc");
        let expected = hex_lit::hex("ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad");
        assert_eq!(out.as_slice(), expected.as_slice());
    }

    /// Cross-check: the trait path and our native `Hasher::hash` must
    /// produce the same bytes for every fixed-output wrapper.
    ///
    /// Note the fully-qualified native paths here: `crate::hash::*`
    /// re-exports `Sha1`, `Sha256`, etc. with the same names as our
    /// bridge wrappers, so a glob import would shadow them.
    #[test]
    fn bridge_all_fixed_hashes_match_native() {
        let data = b"cross-check";

        assert_eq!(
            <Sha1 as RcDigest>::digest(data).as_slice(),
            <crate::hash::sha1::Sha1 as NativeHasher>::hash(data).as_slice(),
        );
        assert_eq!(
            <Sha256 as RcDigest>::digest(data).as_slice(),
            <crate::hash::sha256::Sha256 as NativeHasher>::hash(data).as_slice(),
        );
        assert_eq!(
            <Sha384 as RcDigest>::digest(data).as_slice(),
            <crate::hash::sha384::Sha384 as NativeHasher>::hash(data).as_slice(),
        );
        assert_eq!(
            <Sha512 as RcDigest>::digest(data).as_slice(),
            <crate::hash::sha512::Sha512 as NativeHasher>::hash(data).as_slice(),
        );
        assert_eq!(
            <Sha3_256 as RcDigest>::digest(data).as_slice(),
            <crate::hash::sha3::Sha3_256 as NativeHasher>::hash(data).as_slice(),
        );
        assert_eq!(
            <Sha3_384 as RcDigest>::digest(data).as_slice(),
            <crate::hash::sha3::Sha3_384 as NativeHasher>::hash(data).as_slice(),
        );
        assert_eq!(
            <Sha3_512 as RcDigest>::digest(data).as_slice(),
            <crate::hash::sha3::Sha3_512 as NativeHasher>::hash(data).as_slice(),
        );
        assert_eq!(
            <Ripemd160 as RcDigest>::digest(data).as_slice(),
            <crate::hash::ripemd160::Ripemd160 as NativeHasher>::hash(data).as_slice(),
        );
    }

    /// Incremental `update` via the trait must match the one-shot path.
    /// Uses fully-qualified call syntax because both `digest::Digest`
    /// and `digest::Update` are in scope and both provide `update`.
    #[test]
    fn bridge_incremental_update_matches_oneshot() {
        let mut h = <Sha256 as RcDigest>::new();
        RcDigest::update(&mut h, b"abc");
        RcDigest::update(&mut h, b"def");
        let incremental = h.finalize();
        let oneshot = <Sha256 as RcDigest>::digest(b"abcdef");
        assert_eq!(incremental, oneshot);
    }

    /// `finalize_reset` must reset state so the next hash starts clean.
    #[test]
    fn bridge_finalize_reset_leaves_clean_state() {
        let mut h = <Sha256 as RcDigest>::new();
        RcDigest::update(&mut h, b"first");
        let first = h.finalize_reset();
        RcDigest::update(&mut h, b"second");
        let second = h.finalize();
        assert_ne!(first, second);
        assert_eq!(first, <Sha256 as RcDigest>::digest(b"first"));
        assert_eq!(second, <Sha256 as RcDigest>::digest(b"second"));
    }

    /// SHAKE128 through the `ExtendableOutput` trait must match the
    /// native `squeeze` path byte-for-byte.
    #[test]
    fn bridge_shake128_matches_native() {
        let data = b"shake it up";

        // Through the bridge.
        let mut bridge = Shake128::default();
        Update::update(&mut bridge, data);
        let mut reader = bridge.finalize_xof();
        let mut out_bridge = [0u8; 64];
        reader.read(&mut out_bridge);

        // Through the native API.
        let mut native = <crate::hash::sha3::Shake128 as NativeXof>::new();
        NativeXof::update(&mut native, data);
        let mut out_native = [0u8; 64];
        NativeXof::squeeze(&mut native, &mut out_native);

        assert_eq!(out_bridge, out_native);
    }

    /// SHAKE128 reader can be read in multiple chunks and must yield
    /// the same sequence as a single big read (because the XOF state
    /// is stateful across squeeze calls).
    #[test]
    fn bridge_shake128_chunked_read_matches_single() {
        let data = b"hello shake";

        let mut bridge1 = Shake128::default();
        Update::update(&mut bridge1, data);
        let mut reader1 = bridge1.finalize_xof();
        let mut full = [0u8; 80];
        reader1.read(&mut full);

        let mut bridge2 = Shake128::default();
        Update::update(&mut bridge2, data);
        let mut reader2 = bridge2.finalize_xof();
        let mut chunked = [0u8; 80];
        reader2.read(&mut chunked[..32]);
        reader2.read(&mut chunked[32..48]);
        reader2.read(&mut chunked[48..]);

        assert_eq!(full, chunked);
    }

    /// A tiny local hex literal helper so this test module does not
    /// need a proc-macro crate dependency.
    mod hex_lit {
        pub fn hex(s: &str) -> Vec<u8> {
            assert!(s.len() % 2 == 0);
            (0..s.len())
                .step_by(2)
                .map(|i| u8::from_str_radix(&s[i..i + 2], 16).unwrap())
                .collect()
        }
    }
}
