use std::marker::PhantomData;

use rustls::crypto::hash;
use sha2::Digest;

pub(crate) struct ShaHash<Sha>(PhantomData<Sha>);
pub(crate) struct ShaContext<Sha>(Sha);

pub(crate) static SHA256: ShaHash<sha2::Sha256> = ShaHash(PhantomData);
pub(crate) static SHA384: ShaHash<sha2::Sha384> = ShaHash(PhantomData);

impl hash::Hash for ShaHash<sha2::Sha256> {
    fn start(&self) -> Box<dyn hash::Context> {
        Box::new(ShaContext(sha2::Sha256::new()))
    }

    fn hash(&self, data: &[u8]) -> hash::Output {
        hash::Output::new(&sha2::Sha256::digest(data)[..])
    }

    fn algorithm(&self) -> hash::HashAlgorithm {
        hash::HashAlgorithm::SHA256
    }

    fn output_len(&self) -> usize {
        32
    }
}

impl hash::Hash for ShaHash<sha2::Sha384> {
    fn start(&self) -> Box<dyn hash::Context> {
        Box::new(ShaContext(sha2::Sha384::new()))
    }

    fn hash(&self, data: &[u8]) -> hash::Output {
        hash::Output::new(&sha2::Sha384::digest(data)[..])
    }

    fn algorithm(&self) -> hash::HashAlgorithm {
        hash::HashAlgorithm::SHA384
    }

    fn output_len(&self) -> usize {
        48
    }
}

impl<Sha> hash::Context for ShaContext<Sha>
where
    Sha: Send + Sync + Clone + Digest + 'static,
{
    fn fork_finish(&self) -> hash::Output {
        hash::Output::new(&self.0.clone().finalize()[..])
    }

    fn fork(&self) -> Box<dyn hash::Context> {
        Box::new(Self(self.0.clone()))
    }

    fn finish(self: Box<Self>) -> hash::Output {
        hash::Output::new(&self.0.finalize()[..])
    }

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