use {
    crate::{
        rfc3447::DigestInfo,
        rfc5280::{AlgorithmIdentifier, AlgorithmParameter},
        X509CertificateError as Error,
    },
    bcder::{encode::Values, ConstOid, OctetString, Oid},
    ring::{digest, signature},
    spki::ObjectIdentifier,
    std::fmt::{Display, Formatter},
};
const OID_SHA1: ConstOid = Oid(&[43, 14, 3, 2, 26]);
const OID_SHA256: ConstOid = Oid(&[96, 134, 72, 1, 101, 3, 4, 2, 1]);
const OID_SHA384: ConstOid = Oid(&[96, 134, 72, 1, 101, 3, 4, 2, 2]);
const OID_SHA512: ConstOid = Oid(&[96, 134, 72, 1, 101, 3, 4, 2, 3]);
const OID_SHA1_RSA: ConstOid = Oid(&[42, 134, 72, 134, 247, 13, 1, 1, 5]);
const OID_SHA256_RSA: ConstOid = Oid(&[42, 134, 72, 134, 247, 13, 1, 1, 11]);
const OID_SHA384_RSA: ConstOid = Oid(&[42, 134, 72, 134, 247, 13, 1, 1, 12]);
const OID_SHA512_RSA: ConstOid = Oid(&[42, 134, 72, 134, 247, 13, 1, 1, 13]);
const OID_RSA: ConstOid = Oid(&[42, 134, 72, 134, 247, 13, 1, 1, 1]);
pub(crate) const OID_ECDSA_SHA256: ConstOid = Oid(&[42, 134, 72, 206, 61, 4, 3, 2]);
pub(crate) const OID_ECDSA_SHA384: ConstOid = Oid(&[42, 134, 72, 206, 61, 4, 3, 3]);
pub(crate) const OID_EC_PUBLIC_KEY: ConstOid = Oid(&[42, 134, 72, 206, 61, 2, 1]);
const OID_ED25519_KEY_AGREEMENT: ConstOid = Oid(&[43, 101, 110]);
const OID_ED25519_SIGNATURE_ALGORITHM: ConstOid = Oid(&[43, 101, 112]);
pub(crate) const OID_EC_SECP256R1: ConstOid = Oid(&[42, 134, 72, 206, 61, 3, 1, 7]);
pub(crate) const OID_EC_SECP384R1: ConstOid = Oid(&[43, 129, 4, 0, 34]);
pub(crate) const OID_NO_SIGNATURE_ALGORITHM: ConstOid = Oid(&[43, 6, 1, 5, 5, 7, 6, 2]);
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
pub enum DigestAlgorithm {
    Sha1,
    Sha256,
    Sha384,
    Sha512,
}
impl Display for DigestAlgorithm {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            DigestAlgorithm::Sha1 => f.write_str("SHA-1"),
            DigestAlgorithm::Sha256 => f.write_str("SHA-256"),
            DigestAlgorithm::Sha384 => f.write_str("SHA-384"),
            DigestAlgorithm::Sha512 => f.write_str("SHA-512"),
        }
    }
}
impl From<DigestAlgorithm> for Oid {
    fn from(alg: DigestAlgorithm) -> Self {
        Oid(match alg {
            DigestAlgorithm::Sha1 => OID_SHA1.as_ref(),
            DigestAlgorithm::Sha256 => OID_SHA256.as_ref(),
            DigestAlgorithm::Sha384 => OID_SHA384.as_ref(),
            DigestAlgorithm::Sha512 => OID_SHA512.as_ref(),
        }
        .into())
    }
}
impl TryFrom<&Oid> for DigestAlgorithm {
    type Error = Error;
    fn try_from(v: &Oid) -> Result<Self, Self::Error> {
        if v == &OID_SHA1 {
            Ok(Self::Sha1)
        } else if v == &OID_SHA256 {
            Ok(Self::Sha256)
        } else if v == &OID_SHA384 {
            Ok(Self::Sha384)
        } else if v == &OID_SHA512 {
            Ok(Self::Sha512)
        } else {
            Err(Error::UnknownDigestAlgorithm(format!("{}", v)))
        }
    }
}
impl TryFrom<&AlgorithmIdentifier> for DigestAlgorithm {
    type Error = Error;
    fn try_from(v: &AlgorithmIdentifier) -> Result<Self, Self::Error> {
        Self::try_from(&v.algorithm)
    }
}
impl From<DigestAlgorithm> for AlgorithmIdentifier {
    fn from(alg: DigestAlgorithm) -> Self {
        Self {
            algorithm: alg.into(),
            parameters: None,
        }
    }
}
impl From<DigestAlgorithm> for digest::Context {
    fn from(alg: DigestAlgorithm) -> Self {
        digest::Context::new(match alg {
            DigestAlgorithm::Sha1 => &digest::SHA1_FOR_LEGACY_USE_ONLY,
            DigestAlgorithm::Sha256 => &digest::SHA256,
            DigestAlgorithm::Sha384 => &digest::SHA384,
            DigestAlgorithm::Sha512 => &digest::SHA512,
        })
    }
}
impl DigestAlgorithm {
    pub fn digester(&self) -> digest::Context {
        digest::Context::from(*self)
    }
    pub fn digest_data(&self, data: &[u8]) -> Vec<u8> {
        let mut h = self.digester();
        h.update(data);
        h.finish().as_ref().to_vec()
    }
    pub fn digest_reader<R: std::io::Read>(&self, fh: &mut R) -> Result<Vec<u8>, std::io::Error> {
        let mut h = self.digester();
        loop {
            let mut buffer = [0u8; 16384];
            let count = fh.read(&mut buffer)?;
            h.update(&buffer[0..count]);
            if count < buffer.len() {
                break;
            }
        }
        Ok(h.finish().as_ref().to_vec())
    }
    pub fn digest_path(&self, path: &std::path::Path) -> Result<Vec<u8>, std::io::Error> {
        self.digest_reader(&mut std::fs::File::open(path)?)
    }
    pub fn rsa_pkcs1_encode(
        &self,
        message: &[u8],
        target_length_in_bytes: usize,
    ) -> Result<Vec<u8>, Error> {
        let digest = self.digest_data(message);
        let digest_info = DigestInfo {
            algorithm: (*self).into(),
            digest: OctetString::new(digest.into()),
        };
        let mut digest_info_der = vec![];
        digest_info.write_encoded(bcder::Mode::Der, &mut digest_info_der)?;
        let encoded_digest_len = digest_info_der.len();
        if encoded_digest_len + 11 > target_length_in_bytes {
            return Err(Error::PkcsEncodeTooShort);
        }
        let pad_len = target_length_in_bytes - encoded_digest_len - 3;
        let mut res = vec![0xff; target_length_in_bytes];
        res[0] = 0x00;
        res[1] = 0x01;
        res[2 + pad_len] = 0x00;
        let digest_destination = &mut res[3 + pad_len..];
        digest_destination.copy_from_slice(&digest_info_der);
        Ok(res)
    }
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum SignatureAlgorithm {
    RsaSha1,
    RsaSha256,
    RsaSha384,
    RsaSha512,
    EcdsaSha256,
    EcdsaSha384,
    Ed25519,
    NoSignature(DigestAlgorithm)
}
impl SignatureAlgorithm {
    pub fn from_oid_and_digest_algorithm(
        oid: &Oid,
        digest_algorithm: DigestAlgorithm,
    ) -> Result<Self, Error> {
        if let Ok(alg) = Self::try_from(oid) {
            Ok(alg)
        } else if let Ok(key_alg) = KeyAlgorithm::try_from(oid) {
            match key_alg {
                KeyAlgorithm::Rsa => match digest_algorithm {
                    DigestAlgorithm::Sha1 => Ok(Self::RsaSha1),
                    DigestAlgorithm::Sha256 => Ok(Self::RsaSha256),
                    DigestAlgorithm::Sha384 => Ok(Self::RsaSha384),
                    DigestAlgorithm::Sha512 => Ok(Self::RsaSha512),
                },
                KeyAlgorithm::Ed25519 => Ok(Self::Ed25519),
                KeyAlgorithm::Ecdsa(_) => match digest_algorithm {
                    DigestAlgorithm::Sha256 => Ok(Self::EcdsaSha256),
                    DigestAlgorithm::Sha384 => Ok(Self::EcdsaSha384),
                    DigestAlgorithm::Sha1 | DigestAlgorithm::Sha512 => {
                        Err(Error::UnknownSignatureAlgorithm(format!(
                            "cannot use digest {:?} with ECDSA",
                            digest_algorithm
                        )))
                    }
                },
            }
        } else if oid == &OID_NO_SIGNATURE_ALGORITHM {
            Ok(Self::NoSignature(digest_algorithm))
        } else {
            Err(Error::UnknownSignatureAlgorithm(format!(
                "do not know how to resolve {} to a signature algorithm",
                oid
            )))
        }
    }
    pub fn from_digest_algorithm(
        digest_algorithm: DigestAlgorithm,
    ) -> Self {
        Self::NoSignature(digest_algorithm)
    }
    pub fn resolve_verification_algorithm(
        &self,
        key_algorithm: KeyAlgorithm,
    ) -> Result<&'static dyn signature::VerificationAlgorithm, Error> {
        match key_algorithm {
            KeyAlgorithm::Rsa => match self {
                Self::RsaSha1 => Ok(&signature::RSA_PKCS1_2048_8192_SHA1_FOR_LEGACY_USE_ONLY),
                Self::RsaSha256 => Ok(&signature::RSA_PKCS1_2048_8192_SHA256),
                Self::RsaSha384 => Ok(&signature::RSA_PKCS1_2048_8192_SHA384),
                Self::RsaSha512 => Ok(&signature::RSA_PKCS1_2048_8192_SHA512),
                alg => Err(Error::UnsupportedSignatureVerification(key_algorithm, *alg)),
            },
            KeyAlgorithm::Ed25519 => match self {
                Self::Ed25519 => Ok(&signature::ED25519),
                alg => Err(Error::UnsupportedSignatureVerification(key_algorithm, *alg)),
            },
            KeyAlgorithm::Ecdsa(curve) => match curve {
                EcdsaCurve::Secp256r1 => match self {
                    Self::EcdsaSha256 => Ok(&signature::ECDSA_P256_SHA256_ASN1),
                    Self::EcdsaSha384 => Ok(&signature::ECDSA_P256_SHA384_ASN1),
                    alg => Err(Error::UnsupportedSignatureVerification(key_algorithm, *alg)),
                },
                EcdsaCurve::Secp384r1 => match self {
                    Self::EcdsaSha256 => Ok(&signature::ECDSA_P384_SHA256_ASN1),
                    Self::EcdsaSha384 => Ok(&signature::ECDSA_P384_SHA384_ASN1),
                    alg => Err(Error::UnsupportedSignatureVerification(key_algorithm, *alg)),
                },
            },
        }
    }
    pub fn digest_algorithm(&self) -> Option<DigestAlgorithm> {
        match self {
            SignatureAlgorithm::RsaSha1 => Some(DigestAlgorithm::Sha1),
            SignatureAlgorithm::RsaSha256 => Some(DigestAlgorithm::Sha256),
            SignatureAlgorithm::RsaSha384 => Some(DigestAlgorithm::Sha384),
            SignatureAlgorithm::RsaSha512 => Some(DigestAlgorithm::Sha512),
            SignatureAlgorithm::EcdsaSha256 => Some(DigestAlgorithm::Sha256),
            SignatureAlgorithm::EcdsaSha384 => Some(DigestAlgorithm::Sha384),
            SignatureAlgorithm::Ed25519 => None,
            SignatureAlgorithm::NoSignature(digest_algorithm) => Some(*digest_algorithm),
        }
    }
}
impl Display for SignatureAlgorithm {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            SignatureAlgorithm::RsaSha1 => f.write_str("SHA-1 with RSA encryption"),
            SignatureAlgorithm::RsaSha256 => f.write_str("SHA-256 with RSA encryption"),
            SignatureAlgorithm::RsaSha384 => f.write_str("SHA-384 with RSA encryption"),
            SignatureAlgorithm::RsaSha512 => f.write_str("SHA-512 with RSA encryption"),
            SignatureAlgorithm::EcdsaSha256 => f.write_str("ECDSA with SHA-256"),
            SignatureAlgorithm::EcdsaSha384 => f.write_str("ECDSA with SHA-384"),
            SignatureAlgorithm::Ed25519 => f.write_str("ED25519"),
            SignatureAlgorithm::NoSignature(digest_algorithm) => f.write_fmt(format_args!("No signature with {}", digest_algorithm)),
        }
    }
}
impl From<SignatureAlgorithm> for Oid {
    fn from(alg: SignatureAlgorithm) -> Self {
        Oid(match alg {
            SignatureAlgorithm::RsaSha1 => OID_SHA1_RSA.as_ref(),
            SignatureAlgorithm::RsaSha256 => OID_SHA256_RSA.as_ref(),
            SignatureAlgorithm::RsaSha384 => OID_SHA384_RSA.as_ref(),
            SignatureAlgorithm::RsaSha512 => OID_SHA512_RSA.as_ref(),
            SignatureAlgorithm::EcdsaSha256 => OID_ECDSA_SHA256.as_ref(),
            SignatureAlgorithm::EcdsaSha384 => OID_ECDSA_SHA384.as_ref(),
            SignatureAlgorithm::Ed25519 => OID_ED25519_SIGNATURE_ALGORITHM.as_ref(),
            SignatureAlgorithm::NoSignature(_) => OID_NO_SIGNATURE_ALGORITHM.as_ref(),
        }
        .into())
    }
}
impl TryFrom<&Oid> for SignatureAlgorithm {
    type Error = Error;
    fn try_from(v: &Oid) -> Result<Self, Self::Error> {
        if v == &OID_SHA1_RSA {
            Ok(Self::RsaSha1)
        } else if v == &OID_SHA256_RSA {
            Ok(Self::RsaSha256)
        } else if v == &OID_SHA384_RSA {
            Ok(Self::RsaSha384)
        } else if v == &OID_SHA512_RSA {
            Ok(Self::RsaSha512)
        } else if v == &OID_ECDSA_SHA256 {
            Ok(Self::EcdsaSha256)
        } else if v == &OID_ECDSA_SHA384 {
            Ok(Self::EcdsaSha384)
        } else if v == &OID_ED25519_SIGNATURE_ALGORITHM {
            Ok(Self::Ed25519)
        } else {
            Err(Error::UnknownSignatureAlgorithm(format!("{}", v)))
        }
    }
}
impl TryFrom<&AlgorithmIdentifier> for SignatureAlgorithm {
    type Error = Error;
    fn try_from(v: &AlgorithmIdentifier) -> Result<Self, Self::Error> {
        Self::try_from(&v.algorithm)
    }
}
impl From<SignatureAlgorithm> for AlgorithmIdentifier {
    fn from(alg: SignatureAlgorithm) -> Self {
        Self {
            algorithm: alg.into(),
            parameters: None,
        }
    }
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum EcdsaCurve {
    Secp256r1,
    Secp384r1,
}
impl EcdsaCurve {
    pub fn all() -> &'static [Self] {
        &[Self::Secp256r1, Self::Secp384r1]
    }
    pub fn as_signature_oid(&self) -> Oid {
        Oid(match self {
            Self::Secp256r1 => OID_EC_SECP256R1.as_ref().into(),
            Self::Secp384r1 => OID_EC_SECP384R1.as_ref().into(),
        })
    }
}
impl TryFrom<&Oid> for EcdsaCurve {
    type Error = Error;
    fn try_from(v: &Oid) -> Result<Self, Self::Error> {
        if v == &OID_EC_SECP256R1 {
            Ok(Self::Secp256r1)
        } else if v == &OID_EC_SECP384R1 {
            Ok(Self::Secp384r1)
        } else {
            Err(Error::UnknownEllipticCurve(format!("{}", v)))
        }
    }
}
impl From<EcdsaCurve> for &'static signature::EcdsaSigningAlgorithm {
    fn from(curve: EcdsaCurve) -> Self {
        match curve {
            EcdsaCurve::Secp256r1 => &signature::ECDSA_P256_SHA256_ASN1_SIGNING,
            EcdsaCurve::Secp384r1 => &signature::ECDSA_P384_SHA384_ASN1_SIGNING,
        }
    }
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum KeyAlgorithm {
    Rsa,
    Ecdsa(EcdsaCurve),
    Ed25519,
}
impl Display for KeyAlgorithm {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Rsa => f.write_str("RSA"),
            Self::Ecdsa(_) => f.write_str("ECDSA"),
            Self::Ed25519 => f.write_str("ED25519"),
        }
    }
}
impl TryFrom<&Oid> for KeyAlgorithm {
    type Error = Error;
    fn try_from(v: &Oid) -> Result<Self, Self::Error> {
        if v == &OID_RSA {
            Ok(Self::Rsa)
        } else if v == &OID_EC_PUBLIC_KEY {
            Ok(Self::Ecdsa(EcdsaCurve::Secp384r1))
        } else if v == &OID_ED25519_KEY_AGREEMENT || v == &OID_ED25519_SIGNATURE_ALGORITHM {
            Ok(Self::Ed25519)
        } else {
            Err(Error::UnknownKeyAlgorithm(format!("{}", v)))
        }
    }
}
impl TryFrom<&ObjectIdentifier> for KeyAlgorithm {
    type Error = Error;
    fn try_from(v: &ObjectIdentifier) -> Result<Self, Self::Error> {
        match v.as_bytes() {
            x if x == OID_RSA.as_ref() => Ok(Self::Rsa),
            x if x == OID_EC_PUBLIC_KEY.as_ref() => Ok(Self::Ecdsa(EcdsaCurve::Secp384r1)),
            x if x == OID_ED25519_KEY_AGREEMENT.as_ref()
                || x == OID_ED25519_SIGNATURE_ALGORITHM.as_ref() =>
            {
                Ok(Self::Ed25519)
            }
            _ => Err(Error::UnknownKeyAlgorithm(v.to_string())),
        }
    }
}
impl From<KeyAlgorithm> for Oid {
    fn from(alg: KeyAlgorithm) -> Self {
        Oid(match alg {
            KeyAlgorithm::Rsa => OID_RSA.as_ref(),
            KeyAlgorithm::Ecdsa(_) => OID_EC_PUBLIC_KEY.as_ref(),
            KeyAlgorithm::Ed25519 => OID_ED25519_KEY_AGREEMENT.as_ref(),
        }
        .into())
    }
}
impl From<KeyAlgorithm> for ObjectIdentifier {
    fn from(alg: KeyAlgorithm) -> Self {
        let bytes = match alg {
            KeyAlgorithm::Rsa => OID_RSA.as_ref(),
            KeyAlgorithm::Ecdsa(_) => OID_EC_PUBLIC_KEY.as_ref(),
            KeyAlgorithm::Ed25519 => OID_ED25519_KEY_AGREEMENT.as_ref(),
        };
        ObjectIdentifier::from_bytes(bytes).expect("OID bytes should be valid")
    }
}
impl TryFrom<&AlgorithmIdentifier> for KeyAlgorithm {
    type Error = Error;
    fn try_from(v: &AlgorithmIdentifier) -> Result<Self, Self::Error> {
        let ka = Self::try_from(&v.algorithm)?;
        let ka = if let Some(params) = &v.parameters {
            match ka {
                Self::Ecdsa(_) => {
                    let curve_oid = params.decode_oid()?;
                    let curve = EcdsaCurve::try_from(&curve_oid)?;
                    Ok(Self::Ecdsa(curve))
                }
                Self::Ed25519 => {
                    if params.as_slice() == [0x05, 0x00] {
                        Ok(ka)
                    } else {
                        Err(Error::UnhandledKeyAlgorithmParameters("on ED25519"))
                    }
                }
                Self::Rsa => {
                    if params.as_slice() == [0x05, 0x00] {
                        Ok(ka)
                    } else {
                        Err(Error::UnhandledKeyAlgorithmParameters("on RSA"))
                    }
                }
            }?
        } else {
            ka
        };
        Ok(ka)
    }
}
impl From<KeyAlgorithm> for AlgorithmIdentifier {
    fn from(alg: KeyAlgorithm) -> Self {
        let parameters = match alg {
            KeyAlgorithm::Ed25519 => None,
            KeyAlgorithm::Rsa => None,
            KeyAlgorithm::Ecdsa(curve) => {
                Some(AlgorithmParameter::from_oid(curve.as_signature_oid()))
            }
        };
        Self {
            algorithm: alg.into(),
            parameters,
        }
    }
}
#[cfg(test)]
mod test {
    use super::*;
    #[test]
    fn digest_pkcs1() -> Result<(), Error> {
        let message = b"deadbeef";
        let raw_digest = DigestAlgorithm::Sha256.digest_data(message);
        let encoded = DigestAlgorithm::Sha256.rsa_pkcs1_encode(message, 128)?;
        assert_eq!(&encoded[0..3], &[0x00, 0x01, 0xff]);
        assert_eq!(&encoded[96..], &raw_digest);
        Ok(())
    }
    #[test]
    fn key_algorithm_oids() -> Result<(), Error> {
        let oid = ObjectIdentifier::from(KeyAlgorithm::Rsa);
        assert_eq!(oid.to_string(), "1.2.840.113549.1.1.1");
        let oid = ObjectIdentifier::new("1.2.840.113549.1.1.1").unwrap();
        assert_eq!(KeyAlgorithm::try_from(&oid)?, KeyAlgorithm::Rsa);
        let oid = ObjectIdentifier::from(KeyAlgorithm::Ecdsa(EcdsaCurve::Secp256r1));
        assert_eq!(oid.to_string(), "1.2.840.10045.2.1");
        let oid = ObjectIdentifier::new("1.2.840.10045.2.1").unwrap();
        assert_eq!(
            KeyAlgorithm::try_from(&oid)?,
            KeyAlgorithm::Ecdsa(EcdsaCurve::Secp384r1)
        );
        let oid = ObjectIdentifier::from(KeyAlgorithm::Ed25519);
        assert_eq!(oid.to_string(), "1.3.101.110");
        let oid = ObjectIdentifier::new("1.3.101.110").unwrap();
        assert_eq!(KeyAlgorithm::try_from(&oid)?, KeyAlgorithm::Ed25519);
        Ok(())
    }
}