//! Traits around message digest

use std::{rc::Rc, sync::Arc};

/// Describes computing message digests for arbitrary bytes
///
/// This trait has notable implementations such as apub-openssl's OpenSslDigest, and
/// apub-rustcrypto's Sha256Digest
pub trait Digest {
    /// This NAME should correspond with an HTTP Digest algorithm name
    const NAME: &'static str;

    /// Produce a message digest from the given bytes
    fn digest(&mut self, input: &[u8]) -> String;

    /// Update the Digest with the given bytes
    fn update(&mut self, input: &[u8]);

    /// Verify an encoded string with the current digest
    fn verify(&mut self, encoded: &str) -> bool;
}

/// Describes creating a new Digest type
pub trait DigestBuilder: Digest {
    /// Create the Digest
    fn build() -> Self;
}

/// Allows associating a given Digest type with any arbitrary type
///
/// Some downstreams have dependencies on types that implement DigestFactory in order to generate
/// the desired type for computing digests
pub trait DigestFactory {
    /// The Digest type associated
    type Digest: DigestBuilder + Digest + Send + 'static;
}

impl<'a, T> DigestFactory for &'a T
where
    T: DigestFactory,
{
    type Digest = T::Digest;
}

impl<'a, T> DigestFactory for &'a mut T
where
    T: DigestFactory,
{
    type Digest = T::Digest;
}

impl<T> DigestFactory for Box<T>
where
    T: DigestFactory,
{
    type Digest = T::Digest;
}

impl<T> DigestFactory for Rc<T>
where
    T: DigestFactory,
{
    type Digest = T::Digest;
}

impl<T> DigestFactory for Arc<T>
where
    T: DigestFactory,
{
    type Digest = T::Digest;
}

impl<'a, T> Digest for &'a mut T
where
    T: Digest,
{
    const NAME: &'static str = T::NAME;

    fn digest(&mut self, input: &[u8]) -> String {
        T::digest(self, input)
    }

    fn update(&mut self, input: &[u8]) {
        T::update(self, input)
    }

    fn verify(&mut self, encoded: &str) -> bool {
        T::verify(self, encoded)
    }
}

impl<T> Digest for Box<T>
where
    T: Digest,
{
    const NAME: &'static str = T::NAME;

    fn digest(&mut self, input: &[u8]) -> String {
        T::digest(self, input)
    }

    fn update(&mut self, input: &[u8]) {
        T::update(self, input)
    }

    fn verify(&mut self, encoded: &str) -> bool {
        T::verify(self, encoded)
    }
}
