//! Digest verification

pub use digest::Output as HashOutput;
use {
    ::serde::{Deserialize, Deserializer, Serialize, Serializer},
    base64::Engine,
    digest::{FixedOutput, FixedOutputReset, OutputSizeUser},
    pin_project_lite::pin_project,
    smol::{
        io::{AsyncRead, AsyncWrite},
        ready,
    },
    std::{
        ffi::{OsStr, OsString},
        fmt,
        path::{Path, PathBuf},
        pin::Pin,
        task::{Context, Poll},
    },
};

pub trait HashAlgo: FixedOutput + FixedOutputReset + Default + Send {
    const NAME: &'static str;
    const SRI_NAME: &'static str;
    fn hash(hash: HashOutput<Self>) -> Hash;
    fn into_hash(mut self) -> Hash {
        Self::hash(self.finalize_fixed_reset())
    }
    fn from_hex(hash: &str) -> std::io::Result<HashOutput<Self>> {
        InnerHash::<Self>::from_hex(hash).map(|h| h.inner)
    }
}
pub trait Hashable<H: HashAlgo> {
    fn hash_into(self, hasher: &mut H);
}
impl<H: HashAlgo> Hashable<H> for &[u8] {
    fn hash_into(self, hasher: &mut H) {
        hasher.update(self);
    }
}
impl<H: HashAlgo> Hashable<H> for &str {
    fn hash_into(self, hasher: &mut H) {
        hasher.update(self.as_bytes());
    }
}
impl<H: HashAlgo> Hashable<H> for usize {
    fn hash_into(self, hasher: &mut H) {
        hasher.update(&self.to_le_bytes());
    }
}
impl<H: HashAlgo> Hashable<H> for u64 {
    fn hash_into(self, hasher: &mut H) {
        hasher.update(&self.to_le_bytes());
    }
}
impl<H: HashAlgo> Hashable<H> for u32 {
    fn hash_into(self, hasher: &mut H) {
        hasher.update(&self.to_le_bytes());
    }
}
impl<H: HashAlgo> Hashable<H> for bool {
    fn hash_into(self, hasher: &mut H) {
        hasher.update(&[u8::from(self)]);
    }
}

impl HashAlgo for sha1::Sha1 {
    const NAME: &'static str = "SHA1";
    const SRI_NAME: &'static str = "sha1";
    fn hash(hash: HashOutput<Self>) -> Hash {
        Hash::SHA1(InnerHash { inner: hash })
    }
}

impl HashAlgo for sha2::Sha256 {
    const NAME: &'static str = "SHA256";
    const SRI_NAME: &'static str = "sha256";
    fn hash(hash: HashOutput<Self>) -> Hash {
        Hash::SHA256(InnerHash { inner: hash })
    }
}
impl HashAlgo for sha2::Sha512 {
    const NAME: &'static str = "SHA512";
    const SRI_NAME: &'static str = "sha512";
    fn hash(hash: HashOutput<Self>) -> Hash {
        Hash::SHA512(InnerHash { inner: hash })
    }
}
impl HashAlgo for md5::Md5 {
    const NAME: &'static str = "MD5sum";
    const SRI_NAME: &'static str = "md5";
    fn hash(hash: HashOutput<Self>) -> Hash {
        Hash::MD5sum(InnerHash { inner: hash })
    }
}
impl HashAlgo for blake3::Hasher {
    const NAME: &'static str = "Blake3";
    const SRI_NAME: &'static str = "blake3";
    fn hash(hash: HashOutput<Self>) -> Hash {
        Hash::Blake3(InnerHash { inner: hash })
    }
}

#[derive(Clone, Default)]
/// Typed hash wrapper with algorithm-specific helpers.
pub struct InnerHash<D: HashAlgo> {
    inner: HashOutput<D>,
}

impl<D: HashAlgo> std::fmt::Debug for InnerHash<D> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}({})", D::NAME, hex::encode(&self.inner))
    }
}

impl<D: HashAlgo> PartialEq for InnerHash<D> {
    fn eq(&self, other: &Self) -> bool {
        self.as_bytes() == other.as_bytes()
    }
}

#[allow(dead_code)]
impl<D: HashAlgo> InnerHash<D> {
    const fn sri_hash_name() -> &'static str {
        D::SRI_NAME
    }
    const fn hash_name() -> &'static str {
        D::NAME
    }
    const fn sri_name(&self) -> &'static str {
        D::SRI_NAME
    }
    const fn name(&self) -> &'static str {
        D::NAME
    }
    fn size(&self) -> usize {
        <D as OutputSizeUser>::output_size()
    }
    fn hash_size() -> usize {
        <D as OutputSizeUser>::output_size()
    }
    pub fn as_bytes(&self) -> &[u8] {
        &self.inner
    }
    pub fn hash(&self) -> Hash {
        D::hash(self.inner.clone())
    }
    fn reader<'b, R: AsyncRead + Send + 'b>(
        &self,
        size: u64,
        reader: R,
    ) -> Pin<Box<dyn AsyncRead + Send + 'b>>
    where
        D: 'b,
    {
        Box::pin(VerifyingReader::<D, _>::new(
            reader,
            size,
            self.inner.clone(),
        ))
    }
    fn verifying_reader<'b, R: AsyncRead + Send + 'b>(
        &self,
        size: u64,
        reader: R,
    ) -> Pin<Box<dyn AsyncHashingRead + Send + 'b>>
    where
        D: 'b,
    {
        Box::pin(VerifyingReader::<D, _>::new(
            reader,
            size,
            self.inner.clone(),
        ))
    }
    fn hashing_reader<'b, R: AsyncRead + Send + 'b>(
        reader: R,
    ) -> Pin<Box<dyn AsyncHashingRead + Send + 'b>>
    where
        D: 'b,
    {
        Box::pin(HashingReader::<D, _>::new(reader))
    }
    fn hex_size() -> usize {
        <D as OutputSizeUser>::output_size() * 2
    }
    fn from_hex(value: &str) -> std::io::Result<Self> {
        if value.len() != Self::hex_size() {
            return Err(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!(
                    "invalid hex digest length {}, expected {}",
                    value.len(),
                    Self::hex_size()
                ),
            ));
        }
        let mut inner = HashOutput::<D>::default();
        hex::decode_to_slice(value, &mut inner).map_err(|err| {
            std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!("error decoding hex digest: {}", err),
            )
        })?;
        Ok(InnerHash { inner })
    }
    fn to_hex(&self) -> String {
        hex::encode(&self.inner)
    }
    fn base64_size() -> usize {
        base64::encoded_len(<D as OutputSizeUser>::output_size(), false).unwrap()
    }
    fn from_base64(value: &str) -> Result<Self, std::io::Error> {
        let mut inner = HashOutput::<D>::default();
        let expected: usize = <D as OutputSizeUser>::output_size();
        match base64::engine::general_purpose::URL_SAFE_NO_PAD.decode_slice(value, &mut inner) {
            Ok(n) if n == expected => Ok(InnerHash { inner }),
            Ok(n) => Err(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!(
                    "error decoding base64 digest, expected {} bytes, got {}",
                    expected, n
                ),
            )),
            Err(err) => Err(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!("error decoding base64 digest: {}", err),
            )),
        }
    }
    fn to_base64(&self) -> String {
        base64::engine::general_purpose::URL_SAFE_NO_PAD.encode(&self.inner)
    }
    fn from_sri(value: &str) -> std::io::Result<Self> {
        let mut parts = value.splitn(2, '-');
        let name = parts.next().ok_or_else(|| {
            std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                "error decoding SRI hash: missing hash name",
            )
        })?;
        let b64 = parts.next().ok_or_else(|| {
            std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                "error decoding SRI hash: missing base64 digest",
            )
        })?;
        if name != D::SRI_NAME {
            return Err(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!(
                    "error decoding SRI hash: invalid hash name {}, expected {}",
                    name,
                    D::SRI_NAME
                ),
            ));
        }
        let mut inner = HashOutput::<D>::default();
        let expected: usize = <D as OutputSizeUser>::output_size();
        match base64::engine::general_purpose::STANDARD.decode_slice(b64, &mut inner) {
            Ok(n) if n == expected => Ok(InnerHash { inner }),
            Ok(n) => Err(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!(
                    "error decoding SRI hash: expected {} bytes, got {}",
                    expected, n
                ),
            )),
            Err(err) => Err(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!("error decoding SRI hash: base64 error: {}", err),
            )),
        }
    }
    fn to_sri(&self) -> String {
        format!(
            "{}-{}",
            D::SRI_NAME,
            base64::engine::general_purpose::STANDARD.encode(&self.inner)
        )
    }
}
impl<D: HashAlgo> AsRef<[u8]> for InnerHash<D> {
    fn as_ref(&self) -> &[u8] {
        self.as_bytes()
    }
}
impl<D: HashAlgo> From<HashOutput<D>> for InnerHash<D> {
    fn from(value: HashOutput<D>) -> Self {
        Self { inner: value }
    }
}
impl<D: HashAlgo> From<InnerHash<D>> for HashOutput<D> {
    fn from(value: InnerHash<D>) -> Self {
        value.inner
    }
}

#[derive(Clone, PartialEq)]
pub enum Hash {
    MD5sum(InnerHash<md5::Md5>),
    SHA1(InnerHash<sha1::Sha1>),
    SHA256(InnerHash<sha2::Sha256>),
    SHA512(InnerHash<sha2::Sha512>),
    Blake3(InnerHash<blake3::Hasher>),
}

impl Default for Hash {
    fn default() -> Self {
        Hash::SHA256(InnerHash::<sha2::Sha256>::default())
    }
}

macro_rules! delegate {
    ( $vis:vis fn $name:ident $(< $($gen:tt),* $(,)? >)?
        (&self $(, $p:ident : $ty:ty)* $(,)?) -> $ret:ty $(where $($where:tt)* )? ) => {
        $vis fn $name $(< $($gen),* >)? (&self $(, $p : $ty)*) -> $ret $(where $($where)* )? {
            match self {
                Self::SHA1(h) => h.$name($($p),*),
                Self::MD5sum(h) => h.$name($($p),*),
                Self::SHA256(h) => h.$name($($p),*),
                Self::SHA512(h) => h.$name($($p),*),
                Self::Blake3(h) => h.$name($($p),*),
            }
        }
    };
}

macro_rules! delegate_block {
    ( $var:expr => $test:ident $block:block or $err:expr ) => {
        match $var {
            val if val == InnerHash::<md5::Md5>::$test() => {
                type D = ::md5::Md5;
                $block
            }
            val if val == InnerHash::<sha1::Sha1>::$test() => {
                type D = ::sha1::Sha1;
                $block
            }
            val if val == InnerHash::<sha2::Sha256>::$test() => {
                type D = ::sha2::Sha256;
                $block
            }
            val if val == InnerHash::<sha2::Sha512>::$test() => {
                type D = ::sha2::Sha512;
                $block
            }
            val if val == InnerHash::<blake3::Hasher>::$test() => {
                type D = ::blake3::Hasher;
                $block
            }
            err => $err(err),
        }
    };
}

impl Hash {
    delegate! { pub fn size(&self) -> usize }
    delegate! { pub fn name(&self) -> &'static str }
    delegate! { pub fn sri_name(&self) -> &'static str }
    delegate! { pub fn as_bytes(&self) -> &[u8] }
    delegate! { pub fn to_hex(&self) -> String }
    delegate! { pub fn to_base64(&self) -> String }
    delegate! { pub fn to_sri(&self) -> String }
    delegate! { pub fn reader<'b, R>(
            &self,
            size: u64,
            reader: R,
        ) -> Pin<Box<dyn AsyncRead + Send + 'b>>
        where
            R: AsyncRead + Send + 'b
    }
    delegate! { pub fn verifying_reader<'b, R>(
            &self,
            size: u64,
            reader: R,
        ) -> Pin<Box<dyn AsyncHashingRead + Send + 'b>>
        where
            R: AsyncRead + Send + 'b
    }
    pub fn hashing_reader_for<'b, R: AsyncRead + Send + 'b>(
        hash: &str,
        reader: R,
    ) -> std::io::Result<Pin<Box<dyn AsyncHashingRead + Send + 'b>>> {
        delegate_block! { hash => hash_name {
                Ok(Self::hashing_reader::<D, _>(reader))
            } or |err| {
                Err(std::io::Error::new(std::io::ErrorKind::InvalidInput, format!("hash {} not supported", err)))
            }
        }
    }
    pub fn new_from_hash<D>(hash: HashOutput<D>) -> Self
    where
        D: HashAlgo,
    {
        D::hash(hash)
    }
    pub fn hashing_reader<'b, D, R>(reader: R) -> Pin<Box<dyn AsyncHashingRead + Send + 'b>>
    where
        D: HashAlgo + 'static,
        R: AsyncRead + Send + 'b,
    {
        InnerHash::<D>::hashing_reader(reader)
    }
    pub fn store_name<P: AsRef<Path>>(
        &self,
        prefix: Option<P>,
        suffix: Option<&str>,
        mut levels: usize,
    ) -> PathBuf {
        let pref = prefix.as_ref().map(|p| p.as_ref().as_os_str());
        let total_len = pref.as_ref().map_or(0, |p| p.len() + 1)
            + suffix.map_or(0, |s| s.len() + 1)
            + self.sri_name().len()
            + 1
            + self.size() * 2
            + levels;
        let mut buffer = OsString::with_capacity(total_len);
        if let Some(p) = prefix {
            buffer.push(p.as_ref());
            buffer.push("/");
        }
        buffer.push(self.sri_name());
        buffer.push("/");
        const fn hexadecimal(c: u8) -> [u8; 2] {
            const HEX: &[u8; 16] = b"0123456789abcdef";
            [HEX[(c >> 4) as usize], HEX[(c & 0x0f) as usize]]
        }
        let mut hash = self.as_ref();
        while !hash.is_empty() {
            let d = hexadecimal(hash[0]);
            buffer.push(unsafe { OsStr::from_encoded_bytes_unchecked(&d) });
            if levels > 0 {
                buffer.push("/");
                levels -= 1;
            }
            hash = &hash[1..];
        }
        if let Some(s) = suffix {
            buffer.push(".");
            buffer.push(s);
        }
        buffer.into()
    }
    pub fn from_hex<S: AsRef<str>>(hash_name: &str, value: S) -> std::io::Result<Self> {
        let value = value.as_ref();
        delegate_block!(hash_name => hash_name {
            Ok(InnerHash::<D>::from_hex(value)?.hash())
        } or |err| {
            Err(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!("invalid digest length {}", err),
            ))
        })
    }
    pub fn from_base64<S: AsRef<str>>(hash_name: &str, value: S) -> std::io::Result<Self> {
        let value = value.as_ref();
        delegate_block!(hash_name => hash_name {
            Ok(InnerHash::<D>::from_base64(value)?.hash())
        } or |err| {
            Err(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!("invalid digest length {}", err),
            ))
        })
    }
    pub fn from_sri<S: AsRef<str>>(value: S) -> std::io::Result<Self> {
        let value = value.as_ref();
        let mut parts = value.splitn(2, '-');
        let name = parts
            .next()
            .ok_or_else(|| std::io::Error::other("missing hash name"))?;
        let b64 = parts
            .next()
            .ok_or_else(|| std::io::Error::other("missing base64 digest"))?;
        delegate_block!(name => sri_hash_name {
            let mut inner = HashOutput::<D>::default();
            let inner = match base64::engine::general_purpose::STANDARD.decode_slice(b64, &mut inner) {
                Ok(n) if n == <D as OutputSizeUser>::output_size() => Ok(InnerHash::<D>{ inner }),
                Ok(n) => Err(base64::DecodeSliceError::DecodeError(
                    base64::DecodeError::InvalidLength(n),
                )),
                Err(err) => Err(err),
            }
            .map_err(|err| std::io::Error::other(format!("error decoding base64 digest: {}", err)))?;
            Ok(inner.hash())
        } or |err| {
            Err(std::io::Error::new(
                std::io::ErrorKind::InvalidData,
                format!("hash {} not supported", err),
            ))
        })
    }
}
impl AsRef<[u8]> for Hash {
    delegate! { fn as_ref(&self) -> &[u8] }
}
impl std::fmt::Debug for Hash {
    delegate! { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result }
}
impl TryFrom<&str> for Hash {
    type Error = std::io::Error;
    fn try_from(value: &str) -> std::io::Result<Self> {
        Hash::from_sri(value)
    }
}
impl From<Hash> for String {
    fn from(value: Hash) -> Self {
        value.to_sri()
    }
}
impl From<&Hash> for String {
    fn from(value: &Hash) -> Self {
        value.to_sri()
    }
}

impl Serialize for Hash {
    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        serde::sri::serialize(self, serializer)
    }
}

impl<'de> Deserialize<'de> for Hash {
    fn deserialize<DE>(deserializer: DE) -> Result<Self, DE::Error>
    where
        DE: Deserializer<'de>,
    {
        serde::sri::deserialize(deserializer)
    }
}

/// Serde helpers for hash encodings.
pub mod serde {
    use super::Hash;
    /// Serde helpers for Subresource Integrity (SRI) strings.
    pub mod sri {
        use {
            super::Hash,
            serde::{
                de::{self, Visitor},
                Deserializer, Serializer,
            },
            std::fmt,
        };

        pub fn serialize<S>(value: &Hash, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: Serializer,
        {
            serializer.serialize_str(&value.to_sri())
        }

        pub fn deserialize<'de, DE>(deserializer: DE) -> Result<Hash, DE::Error>
        where
            DE: Deserializer<'de>,
        {
            struct SriVisitor;

            impl Visitor<'_> for SriVisitor {
                type Value = Hash;

                fn expecting(&self, f: &mut fmt::Formatter) -> fmt::Result {
                    write!(f, "an SRI digest",)
                }

                fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
                where
                    E: de::Error,
                {
                    Hash::from_sri(v)
                        .map_err(|e| de::Error::custom(format!("error decoding SRI digest: {}", e)))
                }
            }

            deserializer.deserialize_str(SriVisitor)
        }
        /// Optional SRI hash encoding helpers.
        pub mod opt {
            use serde::{Deserialize, Deserializer, Serialize, Serializer};

            #[derive(Serialize, Deserialize)]
            #[serde(transparent)]
            struct WithSri(#[serde(with = "crate::hash::serde::sri")] super::Hash);

            pub fn serialize<S>(
                value: &Option<super::Hash>,
                serializer: S,
            ) -> Result<S::Ok, S::Error>
            where
                S: Serializer,
            {
                let mapped = value.as_ref().cloned().map(WithSri);
                mapped.serialize(serializer)
            }

            pub fn deserialize<'de, De>(deserializer: De) -> Result<Option<super::Hash>, De::Error>
            where
                De: Deserializer<'de>,
            {
                let opt = Option::<WithSri>::deserialize(deserializer)?;
                Ok(opt.map(|w| w.0))
            }
        }
    }
    /// Serde helpers for hex-encoded hashes.
    pub mod hex {
        use {super::Hash, serde::Serializer};

        pub fn serialize<S>(value: &Hash, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: Serializer,
        {
            let s = ::hex::encode(value.as_ref());
            serializer.serialize_str(&s)
        }

        /// Optional hex hash encoding helpers.
        pub mod opt {
            use serde::{Serialize, Serializer};

            #[derive(Serialize)]
            #[serde(transparent)]
            struct WithHex(#[serde(with = "crate::hash::serde::hex")] super::Hash);

            pub fn serialize<S>(
                value: &Option<super::Hash>,
                serializer: S,
            ) -> Result<S::Ok, S::Error>
            where
                S: Serializer,
            {
                let mapped = value.as_ref().cloned().map(WithHex);
                mapped.serialize(serializer)
            }
        }
    }

    /// Serde helpers for base64-encoded hashes.
    pub mod base64 {
        use {super::Hash, base64::prelude::*, serde::Serializer};

        pub fn serialize<S>(value: &Hash, serializer: S) -> Result<S::Ok, S::Error>
        where
            S: Serializer,
        {
            let s = ::base64::engine::general_purpose::URL_SAFE.encode(value.as_ref());
            serializer.serialize_str(&s)
        }

        /// Optional base64 hash encoding helpers.
        pub mod opt {
            use serde::{Serialize, Serializer};

            #[derive(Serialize)]
            #[serde(transparent)]
            struct WithB64(#[serde(with = "crate::hash::serde::base64")] super::Hash);

            pub fn serialize<S>(
                value: &Option<super::Hash>,
                serializer: S,
            ) -> Result<S::Ok, S::Error>
            where
                S: Serializer,
            {
                let mapped = value.as_ref().cloned().map(WithB64);
                mapped.serialize(serializer)
            }
        }
    }
}

pin_project! {
    /// Async reader that computes a hash as it reads.
    pub struct HashingReader<D, R> {
        digester: D,
        counter: u64,
        #[pin]
        inner: R,
    }
}

pub trait AsyncHashingRead: AsyncRead {
    fn hash(self: Pin<&mut Self>) -> Hash;
    fn size(self: Pin<&mut Self>) -> u64;
}

impl<D: HashAlgo, R: AsyncRead + Send> HashingReader<D, R> {
    pub fn new(reader: R) -> Self {
        Self {
            counter: 0,
            digester: D::default(),
            inner: reader,
        }
    }
    pub fn new_with_digester(digester: D, reader: R) -> Self {
        Self {
            counter: 0,
            digester,
            inner: reader,
        }
    }
    pub fn into_hash(self) -> Hash {
        D::hash(self.digester.finalize_fixed())
    }
    pub fn into_hash_output(self) -> HashOutput<D> {
        self.digester.finalize_fixed()
    }
    pub fn into_hash_and_size(self) -> (Hash, u64) {
        (D::hash(self.digester.finalize_fixed()), self.counter)
    }
}

impl<D: HashAlgo, R: AsyncRead + Send> AsyncHashingRead for HashingReader<D, R> {
    fn hash(self: Pin<&mut Self>) -> Hash {
        D::hash(self.project().digester.finalize_fixed_reset())
    }
    fn size(self: Pin<&mut Self>) -> u64 {
        *self.project().counter
    }
}
impl<D: HashAlgo, R: AsyncRead + Send> AsyncRead for HashingReader<D, R> {
    fn poll_read(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
        buf: &mut [u8],
    ) -> Poll<std::io::Result<usize>> {
        let mut this = self.project();
        Poll::Ready(match ready!(this.inner.as_mut().poll_read(cx, buf)) {
            Ok(0) => Ok(0),
            Ok(size) => {
                this.digester.update(&buf[0..size]);
                *this.counter += size as u64;
                Ok(size)
            }
            Err(err) => Err(err),
        })
    }
}

/// Synchronous writer that computes a hash of written bytes.
pub struct SyncHashingWriter<D: HashAlgo, W: std::io::Write> {
    digester: D,
    inner: W,
}

impl<D: HashAlgo + Default, W: std::io::Write> SyncHashingWriter<D, W> {
    pub fn new(writer: W) -> Self {
        Self {
            digester: D::default(),
            inner: writer,
        }
    }
    pub fn into_hash(self) -> Hash {
        D::hash(self.digester.finalize_fixed())
    }
}

impl<D: HashAlgo, W: std::io::Write> std::io::Write for SyncHashingWriter<D, W> {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        match self.inner.write(buf) {
            Ok(0) => Ok(0),
            Ok(size) => {
                self.digester.update(&buf[0..size]);
                Ok(size)
            }
            Err(err) => Err(err),
        }
    }
    fn flush(&mut self) -> std::io::Result<()> {
        self.inner.flush()
    }
}

pin_project! {
    /// Async writer that computes a hash of written bytes.
    pub struct HashingWriter<D, W> {
        digester: D,
        #[pin]
        inner: W,
    }
}

impl<D: HashAlgo + Default + Send, W: AsyncWrite + Send> HashingWriter<D, W> {
    pub fn new(writer: W) -> Self {
        Self {
            digester: D::default(),
            inner: writer,
        }
    }

    pub fn into_hash(self) -> Hash {
        D::hash(self.digester.finalize_fixed())
    }
}

impl<D: HashAlgo, W: AsyncWrite + Send> AsyncWrite for HashingWriter<D, W> {
    fn poll_write(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
        buf: &[u8],
    ) -> Poll<std::io::Result<usize>> {
        let mut this = self.project();
        match this.inner.as_mut().poll_write(cx, buf) {
            Poll::Ready(Ok(0)) => Poll::Ready(Ok(0)),
            Poll::Ready(Ok(size)) => {
                this.digester.update(&buf[..size]);
                Poll::Ready(Ok(size))
            }
            Poll::Ready(Err(e)) => Poll::Ready(Err(e)),
            Poll::Pending => Poll::Pending,
        }
    }

    fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
        self.project().inner.poll_flush(cx)
    }

    fn poll_close(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<std::io::Result<()>> {
        self.project().inner.poll_close(cx)
    }
}

pin_project! {
    /// Async reader that verifies a hash while reading.
    pub struct VerifyingReader<D: OutputSizeUser, R> {
        digester: D,
        digest: HashOutput<D>,
        size: u64,
        read: u64,
        #[pin]
        inner: R,
    }
}

impl<D: HashAlgo, R: AsyncRead + Send> VerifyingReader<D, R> {
    pub fn new(reader: R, size: u64, digest: HashOutput<D>) -> Self {
        Self {
            digester: D::default(),
            digest,
            size,
            read: 0,
            inner: reader,
        }
    }
}

impl<D: HashAlgo + Default + Send, R: AsyncRead + Send> AsyncRead for VerifyingReader<D, R> {
    fn poll_read(
        self: Pin<&mut Self>,
        cx: &mut Context<'_>,
        buf: &mut [u8],
    ) -> Poll<std::io::Result<usize>> {
        let mut this = self.project();
        match this.inner.as_mut().poll_read(cx, buf) {
            Poll::Ready(Ok(size)) => Poll::Ready(if size > 0 {
                this.digester.update(&buf[0..size]);
                *this.read += size as u64;
                if this.read > this.size {
                    Err(std::io::Error::new(
                        std::io::ErrorKind::InvalidData,
                        format!(
                            "error verifying stream: expected {}, got {} bytes",
                            this.size, this.read
                        ),
                    ))
                } else {
                    Ok(size)
                }
            } else if this.read < this.size {
                // size == 0, EOF
                Err(std::io::Error::new(
                    std::io::ErrorKind::InvalidData,
                    format!(
                        "error verifying stream: expected {}, got {} bytes",
                        this.size, this.read
                    ),
                ))
            } else if this.read == this.size {
                // size == 0, EOF
                *this.read += 1;
                let digest = this.digester.finalize_fixed_reset();
                if this.digest == &digest {
                    Ok(0)
                } else {
                    Err(std::io::Error::new(
                        std::io::ErrorKind::InvalidData,
                        format!(
                            "error verifying stream by {}: expected `{}`, got `{}`",
                            D::NAME,
                            hex::encode(&this.digest),
                            hex::encode(&digest),
                        ),
                    ))
                }
            } else {
                // size = 0, EOF
                Ok(0)
            }),
            st => st,
        }
    }
}

impl<D: HashAlgo, R: AsyncRead + Send> AsyncHashingRead for VerifyingReader<D, R> {
    fn hash(self: Pin<&mut Self>) -> Hash {
        D::hash(self.project().digest.clone())
    }
    fn size(self: Pin<&mut Self>) -> u64 {
        *self.project().size
    }
}
