//! Translates GnuPG-speak from and to Sequoia-speak.

use std::{
    fmt,
    str::FromStr,
};

use sequoia_openpgp as openpgp;
use openpgp::{
    types::*,
};

/// Until Sequoia 2.0, we have to match on the OID to recognize this
/// curve.
pub const BRAINPOOL_P384_OID: &[u8] =
    &[0x2B, 0x24, 0x03, 0x03, 0x02, 0x08, 0x01, 0x01, 0x0B];

/// Translates values to and from human-readable forms.
pub struct Fish<T>(pub T);

impl fmt::Display for Fish<PublicKeyAlgorithm> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use PublicKeyAlgorithm::*;
        #[allow(deprecated)]
        match self.0 {
            RSAEncryptSign => f.write_str("RSA"),
            RSAEncrypt => f.write_str("RSA"),
            RSASign => f.write_str("RSA"),
            ElGamalEncrypt => f.write_str("ELG"),
            DSA => f.write_str("DSA"),
            ECDSA => f.write_str("ECDSA"),
            ElGamalEncryptSign => f.write_str("ELG"),
            ECDH => f.write_str("ECDH"),
            EdDSA => f.write_str("EDDSA"),
            Private(u) => write!(f, "Private({})", u),
            Unknown(u) => write!(f, "Unknown({})", u),
            catchall => write!(f, "{:?}", catchall),
        }
    }
}

impl FromStr for Fish<PublicKeyAlgorithm> {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "rsa" |
            "openpgp-rsa" |
            "oid.1.2.840.113549.1.1.1" =>
                Ok(Fish(PublicKeyAlgorithm::RSAEncryptSign)),
            _ => {
                if let Ok(o) = u8::from_str(s) {
                    Ok(Fish(o.into()))
                } else {
                    Err(openpgp::Error::InvalidArgument(
                        format!("Unknown public key algorithm: {}", s)).into())
                }
            },
        }
    }
}

impl fmt::Display for Fish<&Curve> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use Curve::*;
        match self.0 {
            NistP256 => f.write_str("nistp256"),
            NistP384 => f.write_str("nistp384"),
            NistP521 => f.write_str("nistp521"),
            BrainpoolP256 => f.write_str("brainpoolP256r1"),
            Unknown(oid) if &oid[..] == BRAINPOOL_P384_OID =>
                f.write_str("brainpoolP384r1"),
            BrainpoolP512 => f.write_str("brainpoolP512r1"),
            Ed25519 => f.write_str("ed25519"),
            Cv25519 => f.write_str("cv25519"),
            Unknown(ref oid) => write!(f, "Unknown curve {:?}", oid),
        }
    }
}

impl FromStr for Fish<Curve> {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        match s.to_lowercase().as_str() {
            "ed25519" => Ok(Fish(Curve::Ed25519)),
            "cv25519" | "curve25519" => Ok(Fish(Curve::Cv25519)),
            "nistp256" => Ok(Fish(Curve::NistP256)),
            "nistp384" => Ok(Fish(Curve::NistP384)),
            "nistp521" => Ok(Fish(Curve::NistP521)),
            "brainpoolp256" => Ok(Fish(Curve::BrainpoolP256)),
            "brainpoolp384" =>
                Ok(Fish(Curve::Unknown(BRAINPOOL_P384_OID.into()))),
            "brainpoolp512" => Ok(Fish(Curve::BrainpoolP512)),
            _ => Err(openpgp::Error::InvalidArgument(
                format!("Unknown curve: {}", s)).into()),
        }
    }
}

impl fmt::Display for Fish<SymmetricAlgorithm> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use SymmetricAlgorithm::*;
        #[allow(deprecated)]
        match self.0 {
            Unencrypted => f.write_str("Unencrypted"),
            IDEA => f.write_str("IDEA"),
            TripleDES => f.write_str("3DES"),
            CAST5 => f.write_str("CAST5"),
            Blowfish => f.write_str("BLOWFISH"),
            AES128 => f.write_str("AES"),
            AES192 => f.write_str("AES192"),
            AES256 => f.write_str("AES256"),
            Twofish => f.write_str("TWOFISH"),
            Camellia128 => f.write_str("CAMELLIA128"),
            Camellia192 => f.write_str("CAMELLIA192"),
            Camellia256 => f.write_str("CAMELLIA256"),
            Private(u) => write!(f, "Private({})", u),
            Unknown(u) => write!(f, "Unknown({})", u),
            catchall => write!(f, "{:?}", catchall),
        }
    }
}

impl FromStr for Fish<SymmetricAlgorithm> {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let sl = s.to_lowercase();
        if sl.starts_with('s') {
            if let Ok(a) = sl[1..].parse::<u8>() {
                return Ok(a.into()).map(Fish);
            }
        }

        match sl.as_str() {
            "idea" => Ok(SymmetricAlgorithm::IDEA),
            "3des" => Ok(SymmetricAlgorithm::TripleDES),
            "cast5" => Ok(SymmetricAlgorithm::CAST5),
            "blowfish" => Ok(SymmetricAlgorithm::Blowfish),
            "aes" => Ok(SymmetricAlgorithm::AES128),
            "aes192" => Ok(SymmetricAlgorithm::AES192),
            "aes256" => Ok(SymmetricAlgorithm::AES256),
            "twofish" => Ok(SymmetricAlgorithm::Twofish),
            "twofish128" => Ok(SymmetricAlgorithm::Twofish),
            "camellia128" => Ok(SymmetricAlgorithm::Camellia128),
            "camellia192" => Ok(SymmetricAlgorithm::Camellia192),
            "camellia256" => Ok(SymmetricAlgorithm::Camellia256),
            _ => Err(anyhow::anyhow!("Unknown cipher algorithm {:?}", s)),
        }.map(Fish)
    }
}

impl fmt::Display for Fish<AEADAlgorithm> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use AEADAlgorithm::*;
        #[allow(deprecated)]
        match self.0 {
            EAX => f.write_str("EAX"),
            OCB => f.write_str("OCB"),
            GCM => f.write_str("GCM"),
            catchall => write!(f, "{:?}", catchall),
        }
    }
}

impl fmt::Display for Fish<HashAlgorithm> {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        use HashAlgorithm::*;
        #[allow(deprecated)]
        match self.0 {
            MD5 => f.write_str("MD5"),
            SHA1 => f.write_str("SHA1"),
            RipeMD => f.write_str("RIPEMD160"),
            SHA256 => f.write_str("SHA256"),
            SHA384 => f.write_str("SHA384"),
            SHA512 => f.write_str("SHA512"),
            SHA224 => f.write_str("SHA224"),
            Private(u) => write!(f, "Private({})", u),
            Unknown(u) => write!(f, "Unknown({})", u),
            catchall => write!(f, "{:?}", catchall),
        }
    }
}

impl FromStr for Fish<HashAlgorithm> {
    type Err = anyhow::Error;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let sl = s.to_lowercase();
        if sl.starts_with('h') {
            if let Ok(a) = sl[1..].parse::<u8>() {
                return Ok(a.into()).map(Fish);
            }
        }

        match sl.as_str() {
            "md5" => Ok(HashAlgorithm::MD5),
            "sha1" => Ok(HashAlgorithm::SHA1),
            "ripemd160" => Ok(HashAlgorithm::RipeMD),
            "sha256" => Ok(HashAlgorithm::SHA256),
            "sha384" => Ok(HashAlgorithm::SHA384),
            "sha512" => Ok(HashAlgorithm::SHA512),
            "sha224" => Ok(HashAlgorithm::SHA224),
            _ => Err(anyhow::anyhow!("Unknown hash algorithm {:?}", s)),
        }.map(Fish)
    }
}
