//! CardInfo and related data structures.
//!
//! This module defines the [`CardInfo`] family of data structures.
//! It is returned by [`Agent::card_info`](crate::Agent::card_info).
use std::str::FromStr;

use sequoia_openpgp as openpgp;
use openpgp::Fingerprint;

use sequoia_ipc as ipc;
use ipc::Keygrip;

use crate::Result;

/// KeyInfo-related errors.
#[derive(thiserror::Error, Debug)]
#[non_exhaustive]
pub enum Error {
    #[error("Error parsing card info data: {0}")]
    ParseError(String),
}

/// Information about a key.
///
/// Returned by `Agent::card_info`.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CardInfo {
    raw: Vec<(String, String)>,
}

impl CardInfo {
    /// Returns the raw information.
    ///
    /// The first string is the keyword, e.g., `KEY-FPR` and the
    /// second string is the value `3
    /// 2DC50AB55BE2F3B04C2D2CF8A3506AFB820ABD08`.  The same keyword
    /// may be returned multiple times.
    pub fn raw(&self) -> impl Iterator<Item=&(String, String)> {
        self.raw.iter()
    }

    /// Returns the available keys.
    pub fn keys<'a>(&'a self) -> impl Iterator<Item=Fingerprint> + 'a {
        self.raw()
            .filter_map(|(keyword, value)| {
                if keyword == "KEY-FPR" {
                    if let Some(fpr) = value.split(" ").nth(1) {
                        return Fingerprint::from_str(fpr).ok();
                    }
                }

                None
            })
    }

    /// Returns the available keys by their keygrip.
    pub fn keys_keygrips<'a>(&'a self) -> impl Iterator<Item=Keygrip> + 'a {
        self.raw()
            .filter_map(|(keyword, value)| {
                if keyword == "KEYPAIRINFO" {
                    if let Some(fpr) = value.split(" ").nth(0) {
                        return Keygrip::from_str(fpr).ok();
                    }
                }

                None
            })
    }

    /// Parses the status lines returned by `learn --sendinfo`.
    ///
    /// The output looks like:
    ///
    /// ```text
    /// S KDF �%01%00
    /// S SIG-COUNTER 793
    /// S CHV-STATUS +1+127+127+127+3+0+3
    /// S KEY-TIME 3 1428399828
    /// S KEY-TIME 2 1428399800
    /// S KEY-TIME 1 1428399766
    /// S KEY-FPR 3 2DC50AB55BE2F3B04C2D2CF8A3506AFB820ABD08
    /// S KEY-FPR 2 50E6D924308DBF223CFB510AC2B819056C652598
    /// S KEY-FPR 1 C03FA6411B03AE12576461187223B56678E02528
    /// S DISP-SEX 9
    /// S MANUFACTURER 6 Yubico
    /// S EXTCAP gc=1+ki=1+fc=1+pd=1+mcl3=2048+aac=1+sm=0+si=5+dec=0+bt=1+kdf=1
    /// S APPTYPE openpgp
    /// S SERIALNO D2760001240103040006181329630000
    /// S READER 1050:0407:X:0
    /// S KEYPAIRINFO 9483454871CC1239D4C2A1416F2742D39A14DB14 OPENPGP.3
    /// S KEYPAIRINFO 9873FD355DE470DDC151CD9919AC9785C3C2FDDE OPENPGP.2
    /// S KEYPAIRINFO BE2FE8C8793141322AC30E3EAFD1E4F9D8DACCC4 OPENPGP.1
    /// ```
    pub(crate) fn parse(raw: Vec<(String, String)>) -> Result<Self> {
        Ok(Self {
            raw,
        })
    }
}

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

    #[test]
    fn parse_cardinfo() -> Result<()> {
        let lines = &[
            ("KDF", "�%01%00"),
            ("SIG-COUNTER", "793"),
            ("CHV-STATUS", "+1+127+127+127+3+0+3"),
            ("KEY-TIME", "3 1428399828"),
            ("KEY-TIME", "2 1428399800"),
            ("KEY-TIME", "1 1428399766"),
            ("KEY-FPR", "3 2DC50AB55BE2F3B04C2D2CF8A3506AFB820ABD08"),
            ("KEY-FPR", "2 50E6D924308DBF223CFB510AC2B819056C652598"),
            ("KEY-FPR", "1 C03FA6411B03AE12576461187223B56678E02528"),
            ("DISP-SEX", "9"),
            ("MANUFACTURER", "6 Yubico"),
            ("EXTCAP", "gc=1+ki=1+fc=1+pd=1+mcl3=2048+aac=1+sm=0+si=5+dec=0+bt=1+kdf=1"),
            ("APPTYPE", "openpgp"),
            ("SERIALNO", "D2760001240103040006181329630000"),
            ("READER", "1050:0407:X:0"),
            ("KEYPAIRINFO", "9483454871CC1239D4C2A1416F2742D39A14DB14 OPENPGP.3"),
            ("KEYPAIRINFO", "9873FD355DE470DDC151CD9919AC9785C3C2FDDE OPENPGP.2"),
            ("KEYPAIRINFO", "BE2FE8C8793141322AC30E3EAFD1E4F9D8DACCC4 OPENPGP.1"),
        ][..];

        let info = CardInfo::parse(
            lines.into_iter()
                .map(|(k, m)| {
                    (k.to_string(), m.to_string())
                })
                .collect::<Vec<_>>())
            .expect("valid");

        let fprs = info.keys().collect::<Vec<Fingerprint>>();
        assert_eq!(fprs.len(), 3);
        assert_eq!(
            fprs[0],
            "2DC50AB55BE2F3B04C2D2CF8A3506AFB820ABD08".parse::<Fingerprint>().expect("ok"));
        assert_eq!(
            fprs[1],
            "50E6D924308DBF223CFB510AC2B819056C652598".parse::<Fingerprint>().expect("ok"));
        assert_eq!(
            fprs[2],
            "C03FA6411B03AE12576461187223B56678E02528".parse::<Fingerprint>().expect("ok"));

        let keygrips = info.keys_keygrips().collect::<Vec<Keygrip>>();
        assert_eq!(keygrips.len(), 3);
        assert_eq!(
            keygrips[0],
            "9483454871CC1239D4C2A1416F2742D39A14DB14".parse::<Keygrip>().expect("ok"));
        assert_eq!(
            keygrips[1],
            "9873FD355DE470DDC151CD9919AC9785C3C2FDDE".parse::<Keygrip>().expect("ok"));
        assert_eq!(
            keygrips[2],
            "BE2FE8C8793141322AC30E3EAFD1E4F9D8DACCC4".parse::<Keygrip>().expect("ok"));

        Ok(())
    }
}
