use std::collections::BTreeMap;
use std::collections::btree_map::Entry;
use std::sync::Arc;
use std::time::SystemTime;

use sequoia_openpgp as openpgp;
use openpgp::Fingerprint;
use openpgp::KeyID;
use openpgp::KeyHandle;
use openpgp::Result;

use sequoia_cert_store as cert_store;
use cert_store::store::StoreError;

use crate::CertificationSet;
use crate::Certification;
use crate::CertSynopsis;
use crate::Depth;
use crate::store::Store;

use crate::TRACE;

/// Manages a slice of `&SynopsisSlice`.
///
/// `SynopsisSlice` implements [`Store`] (but not [`Backend`]) and
/// thus can be used as a certificate store by a [`Network`].
///
/// [`Backend`]: crate::store::Backend
/// [`Network`]: crate::Network
pub struct SynopsisSlice<'a> {
    // The list of certificates.
    certs: BTreeMap<KeyID, Vec<&'a CertSynopsis>>,

    // Certifications on a certificate.
    //
    // Example:
    //
    //   C = 0xA certifies <Bob, 0xB>.
    //
    // Whereas `edges` contains the entry 0xA with a CertificationSet
    // containing the certificate C, redges contains an entry for 0xB
    // with a CertificateSet containing C.
    redges: BTreeMap<Fingerprint, Arc<Vec<CertificationSet>>>,

    reference_time: SystemTime,
}

impl<'a> SynopsisSlice<'a> {
    /// Returns a new `SynopsisSlice`.
    ///
    /// The certificates must be deduped.  If there are multiple
    /// instances of a single certificate (i.e., two certificates with
    /// the same fingerprint), they WILL NOT be merged; the first one
    /// will be used, and additional ones will be silently ignored.
    pub fn new(cert_synopses: &'a [CertSynopsis],
               certifications: &'a [Certification],
               reference_time: SystemTime)
        -> Result<Self>
    {
        tracer!(TRACE, "SynopsisSlice::new");

        let mut certs: BTreeMap<KeyID, Vec<&'a CertSynopsis>>
            = BTreeMap::new();
        let mut redges: BTreeMap<Fingerprint, Vec<CertificationSet>>
            = BTreeMap::new();

        let mut insert_cert = |cert: &'a CertSynopsis| {
            let keyid = KeyID::from(&cert.fingerprint());
            match certs.entry(keyid) {
                Entry::Occupied(mut oe) => {
                    if oe.get().iter()
                        .find(|c| {
                            c.fingerprint() == cert.fingerprint()
                        })
                        .is_some()
                    {
                        // A certificate with the same fingerprint is
                        // already there.  Do nothing.
                    } else {
                        oe.get_mut().push(cert);
                    }
                }
                e @ Entry::Vacant(_) => {
                    e.or_insert(vec![ cert ]);
                }
            }
        };

        for cert in cert_synopses.into_iter() {
            insert_cert(cert);
        }

        for c in certifications.into_iter() {
            let c: &Certification = c.into();

            let issuer = c.issuer();
            let target = c.target();

            insert_cert(issuer);
            insert_cert(target);

            match redges.entry(target.fingerprint()) {
                e @ Entry::Occupied(_) => {
                    // We merge below.
                    e.and_modify(|e| {
                        e.push(CertificationSet::from_certification(
                            c.clone(), reference_time))
                    });
                }
                e @ Entry::Vacant(_) => {
                    e.or_insert(
                        vec![
                            CertificationSet::from_certification(
                                c.clone(), reference_time)
                        ]);
                }
            }
        }

        t!("Merging certifications.");

        // Merge the CertificationSets.  A certification is from a
        // certificate and over a certification and User ID pair.  We
        // want one CertificateSet for each pair of certificates.
        for (_, cs) in redges.iter_mut() {
            cs.sort_by(|a, b| {
                a.issuer().fingerprint().cmp(&b.issuer().fingerprint())
            });

            // Now merge certifications from the same certificate.
            *cs = cs.drain(..).fold(
                Vec::new(),
                |mut v: Vec<CertificationSet>, cs: CertificationSet|
                    -> Vec<CertificationSet>
                {
                    let len = v.len();
                    if len > 0 {
                        let l = &mut v[len-1];
                        if l.issuer().fingerprint()
                            == cs.issuer().fingerprint()
                        {
                            l.merge(cs);
                        } else {
                            v.push(cs);
                        }
                    } else {
                        v.push(cs);
                    }

                    v
                });
        }

        t!("Done.");

        let n = SynopsisSlice {
            certs,
            redges: BTreeMap::from_iter(redges.into_iter().map(|(k, v)| {
                (k, Arc::new(v))
            })),
            reference_time,
        };
        Ok(n)
    }
}

impl<'a> Store for SynopsisSlice<'a> {
    fn reference_time(&self) -> SystemTime {
        self.reference_time
    }

    fn iter_fingerprints<'b>(&'b self) -> Box<dyn Iterator<Item=Fingerprint> + 'b> {
        Box::new(
            self.certs
                .values()
                .flat_map(|v| {
                    v.iter().map(|c| c.fingerprint())
                }))
    }

    fn lookup_synopses(&self, kh: &KeyHandle) -> Result<Vec<CertSynopsis>>
    {
        let certs = self.certs.get(&KeyID::from(kh))
            .ok_or(StoreError::NotFound(kh.clone()))?;
        let certs = if let KeyHandle::Fingerprint(fpr) = kh {
            certs
                .into_iter()
                .filter_map(|&c| {
                    if &c.fingerprint() == fpr {
                        Some(c.clone())
                    } else {
                        None
                    }
                })
                .collect()
        } else {
            certs
                .into_iter()
                .map(|&c| c.clone())
                .collect()
        };

        Ok(certs)
    }

    fn certifications_of(&self, target: &Fingerprint, _min_depth: Depth)
        -> Result<Arc<Vec<CertificationSet>>>
    {
        Ok(self.redges.get(target)
           .map(|cs| Arc::clone(cs))
           .ok_or(StoreError::NotFound(KeyHandle::from(target.clone())))?)
    }
}
