//! A Rust implementation of the Stateless OpenPGP Protocol.

use std::{
    fmt,
    io,
    time::SystemTime,
};

#[cfg(feature = "cli")]
pub mod cli;

/// The Stateless OpenPGP Protocol.
pub trait SOP {
    /// Gets version information.
    fn version<'a>(&'a self) -> Result<Box<dyn Version + 'a>>;

    /// Generates a Secret Key.
    ///
    /// Customize the operation using the builder [`GenerateKey`].
    fn generate_key<'a>(&'a self) -> Result<Box<dyn GenerateKey + 'a>>;

    /// Extracts a Certificate from a Secret Key.
    ///
    /// Customize the operation using the builder [`ExtractCert`].
    fn extract_cert<'a>(&'a self) -> Result<Box<dyn ExtractCert + 'a>>;

    /// Creates Detached Signatures.
    ///
    /// Customize the operation using the builder [`Sign`].
    fn sign<'a>(&'a self) -> Result<Box<dyn Sign + 'a>>;

    /// Verifies Detached Signatures.
    ///
    /// Customize the operation using the builder [`Verify`].
    fn verify<'a>(&'a self) -> Result<Box<dyn Verify + 'a>>;

    /// Encrypts a Message.
    ///
    /// Customize the operation using the builder [`Encrypt`].
    fn encrypt<'a>(&'a self) -> Result<Box<dyn Encrypt + 'a>>;

    /// Decrypts a Message.
    ///
    /// Customize the operation using the builder [`Decrypt`].
    fn decrypt<'a>(&'a self) -> Result<Box<dyn Decrypt + 'a>>;

    /// Converts binary OpenPGP data to ASCII.
    ///
    /// Customize the operation using the builder [`Armor`].
    fn armor<'a>(&'a self) -> Result<Box<dyn Armor + 'a>>;

    /// Converts ASCII OpenPGP data to binary.
    ///
    /// Customize the operation using the builder [`Dearmor`].
    fn dearmor<'a>(&'a self) -> Result<Box<dyn Dearmor + 'a>>;
}

pub trait Version<'a> {
    /// Returns name and version of the SOP implementation.
    fn frontend(&self) -> Result<VersionInfo>;

    /// Returns name and version of the primary underlying OpenPGP
    /// toolkit.
    fn backend(&self) -> Result<VersionInfo>;

    /// Returns multiple name and version pairs.
    ///
    /// The first one MUST match the information produced by
    /// [`Version::frontend`], but the rest have no defined structure.
    /// This is what the default implementation does.
    fn extended(&self) -> Result<Vec<VersionInfo>> {
        Ok(vec![
            self.frontend()?,
            self.backend()?,
        ])
    }
}

/// Represents a name and version tuple.
pub struct VersionInfo {
    /// Name of the implementation, library, or additional component.
    pub name: String,
    /// Version string.
    pub version: String,
}

/// Builder for [`SOP::generate_key`].
pub trait GenerateKey<'a> {
    /// Disables armor encoding.
    fn no_armor(self: Box<Self>) -> Box<dyn GenerateKey<'a> + 'a>;

    /// Adds a User ID.
    fn userid(self: Box<Self>, userid: &str) -> Box<dyn GenerateKey<'a> + 'a>;

    /// Generates the OpenPGP key.
    fn generate(self: Box<Self>) -> Result<Box<dyn Ready + 'a>>;
}

/// Builder for [`SOP::extract_cert`].
pub trait ExtractCert<'a> {
    /// Disables armor encoding.
    fn no_armor(self: Box<Self>) -> Box<dyn ExtractCert<'a> + 'a>;

    /// Extracts the cert from `key`.
    fn key(self: Box<Self>, key: &mut (dyn io::Read + Send + Sync))
           -> Result<Box<dyn Ready + 'a>>;

    /// Extracts the certs from `keys`.
    fn keys(self: Box<Self>, keys: &mut (dyn io::Read + Send + Sync))
            -> Result<Box<dyn Ready + 'a>>;
}

/// Builder for [`SOP::sign`].
pub trait Sign<'a> {
    /// Disables armor encoding.
    fn no_armor(self: Box<Self>) -> Box<dyn Sign<'a> + 'a>;

    /// Sets signature mode.
    fn mode(self: Box<Self>, mode: SignAs) -> Box<dyn Sign<'a> + 'a>;

    /// Adds the signer key.
    fn key(self: Box<Self>, key: &mut (dyn io::Read + Send + Sync))
           -> Result<Box<dyn Sign<'a> + 'a>>;

    /// Adds the signer keys.
    ///
    /// Like [`Sign::key`], but for multiple keys.
    fn keys(self: Box<Self>, keys: &mut (dyn io::Read + Send + Sync))
            -> Result<Box<dyn Sign<'a> + 'a>>;

    /// Signs data.
    fn data(self: Box<Self>, data: &'a mut (dyn io::Read + Send + Sync))
            -> Result<Box<dyn ReadyWithResult<Micalg> + 'a>>;
}

/// Builder for [`SOP::verify`].
pub trait Verify<'a> {
    /// Makes SOP consider signatures before this date invalid.
    fn not_before(self: Box<Self>, t: SystemTime) -> Box<dyn Verify<'a> + 'a>;

    /// Makes SOP consider signatures after this date invalid.
    fn not_after(self: Box<Self>, t: SystemTime) -> Box<dyn Verify<'a> + 'a>;

    /// Adds the verification cert.
    fn cert(self: Box<Self>, cert: &mut (dyn io::Read + Send + Sync))
            -> Result<Box<dyn Verify<'a> + 'a>>;

    /// Adds the verification certs.
    ///
    /// Like [`Verify::cert`], but for multiple certs.
    fn certs(self: Box<Self>, certs: &mut (dyn io::Read + Send + Sync))
             -> Result<Box<dyn Verify<'a> + 'a>>;

    /// Provides the signatures.
    fn signatures(self: Box<Self>,
                  signatures: &'a mut (dyn io::Read + Send + Sync))
                  -> Result<Box<dyn VerifySignatures + 'a>>;
}

/// Builder for [`SOP::verify`].
pub trait VerifySignatures {
    /// Verifies the authenticity of `data`.
    fn data(self: Box<Self>,
            data: &mut (dyn io::Read + Send + Sync))
            -> Result<Vec<Verification>>;
}

/// A successful signature verification.
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Verification {
    creation_time: SystemTime,
    signing_key_fingerprint: String,
    signing_cert_fingerprint: String,
    message: Option<String>,
}

#[cfg(feature = "cli")]
impl fmt::Display for Verification {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f,
               "{} {} {}{}",
               chrono::DateTime::<chrono::Utc>::from(self.creation_time())
               .format("%Y-%m-%dT%H:%M:%SZ"),
               self.signing_key_fingerprint(),
               self.signing_cert_fingerprint(),
               if let Some(m) = self.message() {
                   format!(" {}", m)
               } else {
                   "".into()
               })
    }
}

impl Verification {
    /// Creates a `Verification` object.
    pub fn new<'m, T, K, C, M>(creation_time: T,
                               signing_key_fingerprint: K,
                               signing_cert_fingerprint: C,
                               message: M)
                           -> Result<Verification>
    where T: Into<SystemTime>,
          K: ToString,
          C: ToString,
          M: Into<Option<&'m str>>,
    {
        fn normalize(s: String) -> Result<String> {
            // XXX
            Ok(s)
        }
        let signing_key_fingerprint =
            normalize(signing_key_fingerprint.to_string())?;
        let signing_cert_fingerprint =
            normalize(signing_cert_fingerprint.to_string())?;

        Ok(Verification {
            creation_time: creation_time.into(),
            signing_key_fingerprint,
            signing_cert_fingerprint,
            message: message.into().map(Into::into),
        })
    }

    /// Returns the signature's creation time.
    pub fn creation_time(&self) -> SystemTime {
        self.creation_time
    }

    /// Returns the fingerprint of the signing (sub)key.
    pub fn signing_key_fingerprint(&self) -> &str {
        &self.signing_key_fingerprint
    }

    /// Returns the fingerprint of the signing certificate.
    pub fn signing_cert_fingerprint(&self) -> &str {
        &self.signing_cert_fingerprint
    }

    /// Returns a free-form message describing the verification.
    pub fn message(&self) -> Option<&str> {
        self.message.as_ref().map(AsRef::as_ref)
    }
}

/// Builder for [`SOP::encrypt`].
pub trait Encrypt<'a> {
    /// Disables armor encoding.
    fn no_armor(self: Box<Self>) -> Box<dyn Encrypt<'a> + 'a>;

    /// Sets encryption mode.
    fn mode(self: Box<Self>, mode: EncryptAs) -> Box<dyn Encrypt<'a> + 'a>;

    /// Adds the signer key.
    fn sign_with_key(self: Box<Self>, key: &mut (dyn io::Read + Send + Sync))
                     -> Result<Box<dyn Encrypt<'a> + 'a>>;

    /// Adds the signer keys.
    ///
    /// Like [`Encrypt::sign_with_key`], but for multiple keys.
    fn sign_with_keys(self: Box<Self>, keys: &mut (dyn io::Read + Send + Sync))
                      -> Result<Box<dyn Encrypt<'a> + 'a>>;

    /// Encrypts with the given password.
    fn with_password(self: Box<Self>, password: Password)
                     -> Result<Box<dyn Encrypt<'a> + 'a>>;

    /// Encrypts with the given cert.
    fn with_cert(self: Box<Self>, cert: &mut (dyn io::Read + Send + Sync))
                 -> Result<Box<dyn Encrypt<'a> + 'a>>;

    /// Encrypts with the given certs.
    ///
    /// Like [`Encrypt::with_cert`], but for multiple certs.
    fn with_certs(self: Box<Self>, certs: &mut (dyn io::Read + Send + Sync))
                  -> Result<Box<dyn Encrypt<'a> + 'a>>;

    /// Encrypts the given data yielding the ciphertext.
    fn plaintext(self: Box<Self>,
                 plaintext: &'a mut (dyn io::Read + Send + Sync))
        -> Result<Box<dyn Ready + 'a>>;
}

/// Builder for [`SOP::decrypt`].
pub trait Decrypt<'a> {
    /// Makes SOP consider signatures before this date invalid.
    fn verify_not_before(self: Box<Self>,
                         t: SystemTime)
                         -> Box<dyn Decrypt<'a> + 'a>;

    /// Makes SOP consider signatures after this date invalid.
    fn verify_not_after(self: Box<Self>,
                        t: SystemTime)
                        -> Box<dyn Decrypt<'a> + 'a>;

    /// Adds the verification cert.
    fn verify_with_cert(self: Box<Self>,
                        cert: &mut (dyn io::Read + Send + Sync))
                        -> Result<Box<dyn Decrypt<'a> + 'a>>;

    /// Adds the verification certs.
    ///
    /// Like [`Decrypt::verify_with_cert`], but for multiple certs.
    fn verify_with_certs(self: Box<Self>,
                         certs: &mut (dyn io::Read + Send + Sync))
                         -> Result<Box<dyn Decrypt<'a> + 'a>>;

    /// Tries to decrypt with the given session key.
    fn with_session_key(self: Box<Self>, sk: SessionKey)
                        -> Result<Box<dyn Decrypt<'a> + 'a>>;

    /// Tries to decrypt with the given password.
    fn with_password(self: Box<Self>, password: Password)
                     -> Result<Box<dyn Decrypt<'a> + 'a>>;

    /// Adds the decryption key.
    fn with_key(self: Box<Self>, key: &mut (dyn io::Read + Send + Sync))
                -> Result<Box<dyn Decrypt<'a> + 'a>>;

    /// Adds the decryption keys.
    fn with_keys(self: Box<Self>, key: &mut (dyn io::Read + Send + Sync))
                 -> Result<Box<dyn Decrypt<'a> + 'a>>;

    /// Decrypts `ciphertext`, returning verification results and
    /// plaintext.
    fn ciphertext(self: Box<Self>,
                  ciphertext: &'a mut (dyn io::Read + Send + Sync))
                  -> Result<Box<dyn ReadyWithResult<(Option<SessionKey>,
                                                     Vec<Verification>)> + 'a>>;
}

/// Builder for [`SOP::armor`].
pub trait Armor<'a> {
    /// Overrides automatic detection of label.
    fn label(self: Box<Self>, label: ArmorLabel) -> Box<dyn Armor<'a> + 'a>;

    /// Armors `data`.
    fn data(self: Box<Self>, data: &'a mut (dyn io::Read + Send + Sync))
        -> Result<Box<dyn Ready + 'a>>;
}

/// Builder for [`SOP::dearmor`].
pub trait Dearmor<'a> {
    /// Dearmors `data`.
    fn data(self: Box<Self>, data: &'a mut (dyn io::Read + Send + Sync))
        -> Result<Box<dyn Ready + 'a>>;
}

/// A normalized password.
///
/// See [Passwords are Human-Readable] in the SOP spec.
///
///   [Passwords are Human-Readable]: https://datatracker.ietf.org/doc/html/draft-dkg-openpgp-stateless-cli-02#section-7.8
pub struct Password(Box<[u8]>);

impl From<&str> for Password {
    fn from(p: &str) -> Self {
        p.to_string().into()
    }
}

impl From<String> for Password {
    fn from(p: String) -> Self {
        Password(Self::normalize(p).into_bytes().into_boxed_slice())
    }
}

impl AsRef<[u8]> for Password {
    fn as_ref(&self) -> &[u8] {
        &self.0
    }
}

impl Password {
    fn normalize(p: String) -> String {
        // XXX: Maybe do additional checks.
        let normalized = p.trim_end().to_string();

        // Wipe original string.
        let mut p = p.into_bytes();
        unsafe {
            memsec::memzero(p.as_mut_ptr(), p.len());
        }

        normalized
    }
}

impl Drop for Password {
    fn drop(&mut self) {
        unsafe {
            memsec::memzero(self.0.as_mut_ptr(), self.0.len());
        }
    }
}

/// An operation ready to be executed.
///
/// To execute the operation, either supply an [`std::io::Write`]r
/// using [`Ready::write_to`] to write the resulting data to, or use
/// [`Ready::to_vec`] to write to a `Vec<u8>`.
pub trait Ready {
    /// Executes the operation writing the result to `sink`.
    fn write_to(self: Box<Self>, sink: &mut (dyn io::Write + Send + Sync))
        -> Result<()>;

    /// Executes the operation writing the result into a `Vec<u8>`.
    fn to_vec(self: Box<Self>) -> Result<Vec<u8>> {
        let mut v = Vec::new();
        self.write_to(&mut v)?;
        Ok(v)
    }
}

/// An operation that returns a value ready to be executed.
///
/// To execute the operation, either supply an [`std::io::Write`]r
/// using [`Ready::write_to`] to write the resulting data to, or use
/// [`Ready::to_vec`] to write to a `Vec<u8>`.
pub trait ReadyWithResult<T> {
    /// Executes the operation writing the result to `sink`.
    fn write_to(self: Box<Self>, sink: &mut (dyn io::Write + Send + Sync))
        -> Result<T>;

    /// Executes the operation writing the result into a `Vec<u8>`.
    fn to_vec(self: Box<Self>) -> Result<(T, Vec<u8>)> {
        let mut v = Vec::new();
        let r = self.write_to(&mut v)?;
        Ok((r, v))
    }
}

/// A session key.
pub struct SessionKey {
    algorithm: u8,
    key: Box<[u8]>,
}

impl SessionKey {
    /// Creates a new session key object.
    pub fn new<A, K>(algorithm: A, key: K) -> Result<Self>
    where A: Into<u8>,
          K: AsRef<[u8]>,
    {
        // XXX: Maybe sanity check key lengths.
        Ok(SessionKey {
            algorithm: algorithm.into(),
            key: key.as_ref().to_vec().into(),
        })
    }

    /// Returns the symmetric algorithm octet.
    pub fn algorithm(&self) -> u8 {
        self.algorithm
    }

    /// Returns the session key.
    pub fn key(&self) -> &[u8] {
        &self.key
    }
}

impl fmt::Display for SessionKey {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{}:", self.algorithm)?;
        for b in &self.key[..] {
            write!(f, "{:02X}", b)?
        }
        Ok(())
    }
}

impl Drop for SessionKey {
    fn drop(&mut self) {
        unsafe {
            memsec::memzero(self.key.as_mut_ptr(), self.key.len());
        }
    }
}

impl std::str::FromStr for SessionKey {
    type Err = ParseError;
    fn from_str(sk: &str) -> ParseResult<Self> {
        // The SOP format is:
        //
        //   <decimal-cipher-octet> ":" <hex-session-key>
        //
        // We most likely will change the first field, so we split
        // from the end of the string using `rsplit`, which puts the
        // last segment first.  This is rather unexpected.  Reverse
        // it.
        let fields = sk.rsplit(':').rev().collect::<Vec<_>>();

        if fields.len() != 2 {
            return Err(ParseError(format!(
                "Expected two colon-separated fields, got {:?}",
                fields)));
        }

        let algo: u8 = fields[0].parse().map_err(
            |e| ParseError(format!("Failed to parse algorithm: {}", e)))?;
        let sk = from_hex(&fields[1], true)?;
        Self::new(algo, sk).map_err(
            |e| ParseError(format!("Bad session key: {}", e)))
    }
}

/// A helpful function for converting a hexadecimal string to binary.
/// This function skips whitespace if `pretty` is set.
fn from_hex(hex: &str, pretty: bool) -> ParseResult<Vec<u8>> {
    const BAD: u8 = 255u8;
    const X: u8 = 'x' as u8;

    let mut nibbles = hex.chars().filter_map(|x| {
        match x {
            '0' => Some(0u8),
            '1' => Some(1u8),
            '2' => Some(2u8),
            '3' => Some(3u8),
            '4' => Some(4u8),
            '5' => Some(5u8),
            '6' => Some(6u8),
            '7' => Some(7u8),
            '8' => Some(8u8),
            '9' => Some(9u8),
            'a' | 'A' => Some(10u8),
            'b' | 'B' => Some(11u8),
            'c' | 'C' => Some(12u8),
            'd' | 'D' => Some(13u8),
            'e' | 'E' => Some(14u8),
            'f' | 'F' => Some(15u8),
            'x' | 'X' if pretty => Some(X),
            _ if pretty && x.is_whitespace() => None,
            _ => Some(BAD),
        }
    }).collect::<Vec<u8>>();

    if pretty && nibbles.len() >= 2 && nibbles[0] == 0 && nibbles[1] == X {
        // Drop '0x' prefix.
        nibbles.remove(0);
        nibbles.remove(0);
    }

    if nibbles.iter().any(|&b| b == BAD || b == X) {
        // Not a hex character.
        return Err(ParseError("Invalid characters".into()));
    }

    // We need an even number of nibbles.
    if nibbles.len() % 2 != 0 {
        return Err(ParseError("Odd number of nibbles".into()));
    }

    let bytes = nibbles.chunks(2).map(|nibbles| {
        (nibbles[0] << 4) | nibbles[1]
    }).collect::<Vec<u8>>();

    Ok(bytes)
}

/// Signature type.
///
/// This is used by [`SOP::sign`] to select the signature type.  See
/// [`sop sign`].
///
///   [`sop sign`]: https://datatracker.ietf.org/doc/html/draft-dkg-openpgp-stateless-cli-02#section-3.4
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum SignAs {
    Binary,
    Text,
}

impl Default for SignAs {
    fn default() -> Self {
        SignAs::Binary
    }
}

impl From<EncryptAs> for SignAs {
    fn from(a: EncryptAs) -> Self {
        match a {
            EncryptAs::Binary => SignAs::Binary,
            EncryptAs::Text => SignAs::Text,
            // XXX: We should inspect the serialized MIME structure
            // and use Text if it is UTF-8, Binary otherwise.  But, we
            // cannot be bothered at this point.
            EncryptAs::MIME => SignAs::Binary,
        }
    }
}

impl std::str::FromStr for SignAs {
    type Err = ParseError;
    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        match s {
            "binary" => Ok(SignAs::Binary),
            "text" => Ok(SignAs::Text),
            _ => Err(ParseError(format!(
                "{:?}, expected one of {{binary|text}}", s))),
        }
    }
}

impl fmt::Display for SignAs {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            SignAs::Binary => f.write_str("binary"),
            SignAs::Text => f.write_str("text"),
        }
    }
}

/// Plaintext data format.
///
/// This is used by [`SOP::encrypt`] to select the data format.  See
/// [`sop encrypt`].
///
///   [`sop encrypt`]: https://datatracker.ietf.org/doc/html/draft-dkg-openpgp-stateless-cli-02#section-3.6
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum EncryptAs {
    Binary,
    Text,
    MIME,
}

impl Default for EncryptAs {
    fn default() -> Self {
        EncryptAs::Binary
    }
}

impl std::str::FromStr for EncryptAs {
    type Err = ParseError;
    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        match s {
            "binary" => Ok(EncryptAs::Binary),
            "text" => Ok(EncryptAs::Text),
            "mime" => Ok(EncryptAs::MIME),
            _ => Err(ParseError(format!(
                "{}, expected one of {{binary|text|mime}}", s))),
        }
    }
}

impl fmt::Display for EncryptAs {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            EncryptAs::Binary => f.write_str("binary"),
            EncryptAs::Text => f.write_str("text"),
            EncryptAs::MIME => f.write_str("mime"),
        }
    }
}

/// The ASCII Armor Label.
///
/// This is used by [`SOP::armor`] to control the framing that is
/// emitted.  See [`sop armor`].
///
///   [`sop armor`]: https://datatracker.ietf.org/doc/html/draft-dkg-openpgp-stateless-cli-02#section-3.8
#[derive(Clone, Copy, Debug, Hash, PartialEq, Eq)]
pub enum ArmorLabel {
    Auto,
    Sig,
    Key,
    Cert,
    Message,
}

impl Default for ArmorLabel {
    fn default() -> Self {
        ArmorLabel::Auto
    }
}

impl std::str::FromStr for ArmorLabel {
    type Err = ParseError;
    fn from_str(s: &str) -> std::result::Result<Self, Self::Err> {
        match s {
            "auto" => Ok(ArmorLabel::Auto),
            "sig" => Ok(ArmorLabel::Sig),
            "key" => Ok(ArmorLabel::Key),
            "cert" => Ok(ArmorLabel::Cert),
            "message" => Ok(ArmorLabel::Message),
            _ => Err(ParseError(format!(
                "{:?}, expected one of \
                 {{auto|sig|key|cert|message}}", s))),
        }
    }
}

impl fmt::Display for ArmorLabel {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            ArmorLabel::Auto => f.write_str("auto"),
            ArmorLabel::Sig => f.write_str("sig"),
            ArmorLabel::Key => f.write_str("key"),
            ArmorLabel::Cert => f.write_str("cert"),
            ArmorLabel::Message => f.write_str("message"),
        }
    }
}

/// Indicates the cryptographic digest used when making a signature.
///
/// It is useful specifically when generating signed PGP/MIME objects,
/// which want a `micalg=` parameter for the `multipart/signed`
/// content type as described in section 5 of [RFC3156].
///
/// [RFC3156]: https://datatracker.ietf.org/doc/html/rfc3156
#[derive(Clone, Debug, Hash, PartialEq, Eq)]
pub enum Micalg {
    /// Rivest et.al. message digest 5 (deprecated).
    MD5,
    /// NIST Secure Hash Algorithm (deprecated).
    SHA1,
    /// RIPEMD-160 (deprecated).
    RipeMD,
    /// 256-bit version of SHA2.
    SHA256,
    /// 384-bit version of SHA2.
    SHA384,
    /// 512-bit version of SHA2.
    SHA512,
    /// 224-bit version of SHA2.
    SHA224,
    /// Unknown hash algorithm.
    Unknown(String),
}

impl From<u8> for Micalg {
    fn from(o: u8) -> Self {
        match o {
            1 => Micalg::MD5,
            2 => Micalg::SHA1,
            3 => Micalg::RipeMD,
            8 => Micalg::SHA256,
            9 => Micalg::SHA384,
            10 => Micalg::SHA512,
            11 => Micalg::SHA224,
            u => Micalg::Unknown(format!("unknown-algo-{}", u)),
        }
    }
}

impl fmt::Display for Micalg {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        f.write_str("pgp-")?;
        match self {
            Micalg::MD5 => f.write_str("md5"),
            Micalg::SHA1 => f.write_str("sha1"),
            Micalg::RipeMD => f.write_str("ripemd160"),
            Micalg::SHA256 => f.write_str("sha256"),
            Micalg::SHA384 => f.write_str("sha384"),
            Micalg::SHA512 => f.write_str("sha512"),
            Micalg::SHA224 => f.write_str("sha224"),
            Micalg::Unknown(a) => f.write_str(&a.to_lowercase()),
        }
    }
}

/// Result specialization.
pub type Result<T> = std::result::Result<T, Error>;

/// SOP errors.
///
/// These are the errors [defined] by the Stateless OpenPGP Protocol.
///
///   [defined]: https://datatracker.ietf.org/doc/html/draft-dkg-openpgp-stateless-cli-02#section-6
#[derive(thiserror::Error, Debug)]
pub enum Error {
    /// No acceptable signatures found ("sop verify").
    #[error("No acceptable signatures found")]
    NoSignature,

    /// Asymmetric algorithm unsupported ("sop encrypt").
    #[error("Asymmetric algorithm unsupported")]
    UnsupportedAsymmetricAlgo,

    /// Certificate not encryption-capable (e.g., expired, revoked,
    /// unacceptable usage flags) ("sop encrypt").
    #[error("Certificate not encryption-capable")]
    CertCannotEncrypt,

    /// Key not signature-capable (e.g., expired, revoked,
    /// unacceptable usage flags) (`sop sign` and `sop encrypt` with
    /// `--sign-with`).
    #[error("Key not signing-capable")]
    KeyCannotSign,

    /// Missing required argument.
    #[error("Missing required argument")]
    MissingArg,

    /// Incomplete verification instructions ("sop decrypt").
    #[error("Incomplete verification instructions")]
    IncompleteVerification,

    /// Unable to decrypt ("sop decrypt").
    #[error("Unable to decrypt")]
    CannotDecrypt,

    /// Non-"UTF-8" or otherwise unreliable password ("sop encrypt").
    #[error("Non-UTF-8 or otherwise unreliable password")]
    PasswordNotHumanReadable,

    /// Unsupported option.
    #[error("Unsupported option")]
    UnsupportedOption,

    /// Invalid data type (no secret key where "KEY" expected, etc).
    #[error("Invalid data type")]
    BadData,

    /// Non-text input where text expected.
    #[error("Non-text input where text expected")]
    ExpectedText,

    /// Output file already exists.
    #[error("Output file already exists")]
    OutputExists,

    /// Input file does not exist.
    #[error("Input file does not exist")]
    MissingInput,

    /// A "KEY" input is protected (locked) with a password, and "sop" cannot
    /// unlock it.
    #[error("A KEY input is protected with a password")]
    KeyIsProtected,

    /// A indirect input parameter is a special designator (it starts with
    /// "@"), and a filename matching the designator is actually present.
    #[error("A indirect input parameter is a special designator matches file")]
    AmbiguousInput,

    /// Operation not implemented.
    #[error("Operation not implemented")]
    NotImplemented,

    /// An IO error occurred.
    #[error("IO error")]
    IoError(#[from] std::io::Error),
}

/// Errors during parsing of SOP string representations.
///
/// For types with a defined string representation, we implement
/// [`std::str::FromStr`] for parsing.  This type is used to report
/// errors during parsing.
#[derive(thiserror::Error, Debug)]
#[error("Invalid argument: {}", _0)]
pub struct ParseError(String);

/// Convenience alias.
type ParseResult<T> = std::result::Result<T, ParseError>;

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn session_key_roundtrip() -> Result<()> {
        for algo in &[9, 13] {
            let sk = SessionKey::new(
                *algo,
                &[0xE1, 0x48, 0x97, 0x81, 0xAA, 0x22, 0xE1, 0xBF,
                  0x6E, 0x3E, 0x61, 0x74, 0x8C, 0x8D, 0x3F, 0x35,
                  0x50, 0x7C, 0x80, 0x9E, 0x95, 0x64, 0x86, 0x87,
                  0xC7, 0xE4, 0xB9, 0xAF, 0x86, 0x17, 0xD3, 0xAE])?;
            let sk_s = sk.to_string();
            let sk_p: SessionKey = sk_s.parse().unwrap();
            assert_eq!(sk.algorithm(), sk_p.algorithm());
            assert_eq!(sk.key(), sk_p.key());
        }
        Ok(())
    }

    #[test]
    fn sign_as_roundtrip() -> Result<()> {
        use SignAs::*;
        for a in &[Text, Binary] {
            let s = a.to_string();
            let b: SignAs = s.parse().unwrap();
            assert_eq!(a, &b);
        }
        Ok(())
    }

    #[test]
    fn encrypt_as_roundtrip() -> Result<()> {
        use EncryptAs::*;
        for a in &[Text, Binary, MIME] {
            let s = a.to_string();
            let b: EncryptAs = s.parse().unwrap();
            assert_eq!(a, &b);
        }
        Ok(())
    }

    #[test]
    fn armor_label_roundtrip() -> Result<()> {
        use ArmorLabel::*;
        for a in &[Auto, Sig, Key, Cert, Message] {
            let s = a.to_string();
            let b: ArmorLabel = s.parse().unwrap();
            assert_eq!(a, &b);
        }
        Ok(())
    }
}
