use crate::{DigestPart, VerifyDigest};
use base64::{prelude::BASE64_STANDARD, Engine};
use sha2::{Sha224, Sha256, Sha384, Sha512};

trait DigestName {
    const NAME: &'static str;
}

impl DigestName for Sha224 {
    const NAME: &'static str = "SHA-244";
}

impl DigestName for Sha256 {
    const NAME: &'static str = "SHA-256";
}

impl DigestName for Sha384 {
    const NAME: &'static str = "SHA-384";
}

impl DigestName for Sha512 {
    const NAME: &'static str = "SHA-512";
}

fn verify<D: sha2::Digest + sha2::digest::FixedOutputReset>(
    digest: &mut D,
    name: &str,
    parts: &[DigestPart],
) -> bool {
    use subtle::ConstantTimeEq;

    if let Some(decoded) = parts.iter().find_map(|p| {
        if p.algorithm.to_lowercase() == name.to_lowercase() {
            BASE64_STANDARD.decode(&p.digest).ok()
        } else {
            None
        }
    }) {
        return digest.finalize_reset().ct_eq(&decoded).into();
    }

    false
}

impl VerifyDigest for Sha224 {
    fn update(&mut self, part: &[u8]) {
        sha2::Digest::update(self, part);
    }

    fn verify(&mut self, parts: &[DigestPart]) -> bool {
        verify(self, Self::NAME, parts)
    }
}

impl VerifyDigest for Sha256 {
    fn update(&mut self, part: &[u8]) {
        sha2::Digest::update(self, part);
    }

    fn verify(&mut self, parts: &[DigestPart]) -> bool {
        verify(self, Self::NAME, parts)
    }
}

impl VerifyDigest for Sha384 {
    fn update(&mut self, part: &[u8]) {
        sha2::Digest::update(self, part);
    }

    fn verify(&mut self, parts: &[DigestPart]) -> bool {
        verify(self, Self::NAME, parts)
    }
}

impl VerifyDigest for Sha512 {
    fn update(&mut self, part: &[u8]) {
        sha2::Digest::update(self, part);
    }

    fn verify(&mut self, parts: &[DigestPart]) -> bool {
        verify(self, Self::NAME, parts)
    }
}
