use std::borrow::Borrow;
use std::collections::btree_map::Entry;
use std::collections::BTreeMap;

use sequoia_openpgp as openpgp;
use openpgp::Fingerprint;
use openpgp::regex::RegexSet;

use crate::Certification;
use crate::Depth;
use crate::Path;
use crate::TRACE;

// A mechanism to filter certifications.
//
// This is particularly useful when evaluating a residual network,
// i.e., a network minus the capacity used by a particular path.
pub trait CertificationFilter {
    /// Filter the certification's parameters.
    ///
    /// The current effective values are in the `depth`, `amount` and
    /// `regexs` parameters.  This function should tweak those values
    /// in place.  This approach allows chaining multiple filters.
    ///
    /// Be careful when specifying a depth of 255.  This is not
    /// automatically converted to `Depth::Unconstrained`.
    ///
    /// Since rewriting the regular expressions may be expensive, if
    /// regexs is `None`, then that means the caller doesn't actually
    /// care about the regular expressions.
    ///
    /// If the function returns `false`, then that means the
    /// certification should be skipped.
    fn cost(&self,
            _c: &Certification,
            _depth: &mut Depth,
            _amount: &mut usize,
            _ignores_regex: bool,
            _regexs: &mut Option<RegexSet>)
        -> bool
    {
        true
    }
}

/// A no-op filter.
///
/// This filter passes certifications through as is.
pub struct IdempotentCertificationFilter {
}

#[allow(dead_code)]
impl IdempotentCertificationFilter {
    pub(crate) fn new() -> Self {
        Self {}
    }
}

impl CertificationFilter for IdempotentCertificationFilter {}

/// A filter that chains multiple filters together.
///
/// The filters are called in the other that they are added.  If a
/// filter returns `false`, then this filter immediately returns
/// false.
pub struct ChainFilter<'a> {
    filters: Vec<Box<dyn CertificationFilter + 'a>>,
}

impl CertificationFilter for ChainFilter<'_> {
    fn cost(&self,
            c: &Certification,
            depth: &mut Depth,
            amount: &mut usize,
            ignore_regex: bool,
            regexs: &mut Option<RegexSet>)
        -> bool
    {
        tracer!(TRACE, "ChainFilter::cost");

        for (i, f) in self.filters.iter().enumerate() {
            let old_depth = depth.clone();
            let old_amount = *amount;

            if ! f.cost(c, depth, amount, ignore_regex, regexs) {
                return false;
            }
            t!("{}.: {}/{} -> {}/{}",
               i, old_depth, old_amount, depth, amount);
        }
        return true;
    }
}

impl<'a> ChainFilter<'a> {
    pub fn new() -> Self {
        Self {
            filters: Vec::new(),
        }
    }

    pub fn push<F>(&mut self, filter: F)
    where F: 'a + CertificationFilter
    {
        self.filters.push(Box::new(filter));
    }
}

/// A filter that suppresses some capacity of some certifications.
pub struct SuppressCertificationFilter {
    // A certification's trust amount will be suppressed by this
    // amount.
    amount: BTreeMap<(Fingerprint, Fingerprint), usize>,
}

impl CertificationFilter for SuppressCertificationFilter {
    fn cost(&self,
            c: &Certification,
            _depth: &mut Depth,
            amount: &mut usize,
            _ignore_regexs: bool,
            _regexs: &mut Option<RegexSet>)
        -> bool
    {
        tracer!(TRACE, "SuppressCertificationFilter::cost");
        if let Some(&delta) = self.amount.get(
            &(c.issuer().fingerprint(), c.target().fingerprint()))
        {
            // Be careful to not underflow.
            let new_amount = std::cmp::max(*amount, delta) - delta;
            t!("Suppressing trust amount: {} -> {}", amount, new_amount);
            *amount = new_amount;
        }

        return true;
    }
}

impl SuppressCertificationFilter {
    /// Returns a new, empty `SuppressCertificationFilter`.
    pub fn new() -> Self {
        Self {
            amount: BTreeMap::new(),
        }
    }

    /// Add suppression rules for all certifications along the specified
    /// path.
    ///
    /// Each edge is suppressed by amount.
    pub fn suppress_path(&mut self, path: &Path, amount_to_suppress: usize) {
        if amount_to_suppress == 0 {
            return;
        }
        assert!(amount_to_suppress <= 120);

        for c in path.certifications() {
            match self.amount.entry(
                (c.issuer().fingerprint(), c.target().fingerprint()))
            {
                Entry::Occupied(mut oe) => {
                    let amount = oe.get_mut();
                    *amount += amount_to_suppress;
                    assert!(*amount <= 120);
                }
                e @ Entry::Vacant(_) => {
                    e.or_insert(amount_to_suppress);
                }
            }
        }
    }
}

/// A filter that suppresses some capacity of an issuer.
pub struct SuppressIssuerFilter {
    // A certification's trust amount will be suppressed by this
    // amount.
    amount: BTreeMap<Fingerprint, usize>,
}

impl CertificationFilter for SuppressIssuerFilter {
    fn cost(&self,
            c: &Certification,
            _depth: &mut Depth,
            amount: &mut usize,
            _ignore_regexs: bool,
            _regexs: &mut Option<RegexSet>)
        -> bool
    {
        tracer!(TRACE, "SuppressIssuerFilter::cost");
        if let Some(&delta) = self.amount.get(&c.issuer().fingerprint()) {
            // Be careful to not underflow.
            let new_amount = std::cmp::max(*amount, delta) - delta;
            t!("Suppressing trust amount: {} -> {}", amount, new_amount);
            *amount = new_amount;
        }

        return true;
    }
}

impl SuppressIssuerFilter {
    /// Returns a new, empty `SuppressIssuerFilter`.
    pub fn new() -> Self {
        Self {
            amount: BTreeMap::new(),
        }
    }

    /// Add suppression rules for the certificate.
    ///
    /// Any certifications that the certificate makes are suppressed
    /// (decreased) by that amount.
    pub fn suppress_issuer<F>(&mut self, fingerprint: F,
                              amount_to_suppress: usize)
        where F: Borrow<Fingerprint>
    {
        let fingerprint = fingerprint.borrow();

        if amount_to_suppress == 0 {
            return;
        }
        assert!(amount_to_suppress <= 120);

        match self.amount.entry(fingerprint.clone()) {
            Entry::Occupied(mut oe) => {
                let amount = oe.get_mut();
                *amount += amount_to_suppress;
                assert!(*amount <= 120);
            }
            e @ Entry::Vacant(_) => {
                e.or_insert(amount_to_suppress);
            }
        }
    }
}

/// A filter that caps (limits) the capacity of some certificates.
pub struct CapCertificateFilter {
    // A certificate's trust amount will be limited to this amount.
    cap: BTreeMap<Fingerprint, usize>,
}

impl CertificationFilter for CapCertificateFilter {
    fn cost(&self,
            c: &Certification,
            _depth: &mut Depth,
            amount: &mut usize,
            _ignore_regexs: bool,
            _regexs: &mut Option<RegexSet>)
        -> bool
    {
        tracer!(TRACE, "CapCertificateFilter::cost");
        if let Some(&cap) = self.cap.get(&c.issuer().fingerprint()) {
            // Be careful to not underflow.
            let new_amount = std::cmp::min(*amount, cap);
            t!("Capping trust amount: {} -> {}", amount, new_amount);
            *amount = new_amount;
        }

        return true;
    }
}

impl CapCertificateFilter {
    /// Returns a new, empty `CapCertificateFilter`.
    pub fn new() -> Self {
        Self {
            cap: BTreeMap::new(),
        }
    }

    /// Add rules for the certificate.
    ///
    /// Any certifications issued by the certificate have their trust
    /// amount limited to `cap`.  If a ceritifcate is capped multiple
    /// times, then the minimum cap is used.
    pub fn cap(&mut self, cert: Fingerprint, cap: usize) {
        match self.cap.entry(cert) {
            Entry::Occupied(mut oe) => {
                let current_cap = oe.get_mut();
                *current_cap = std::cmp::min(*current_cap, cap);
            }
            e @ Entry::Vacant(_) => {
                e.or_insert(cap);
            }
        }
    }
}

/// A filter that treats every signature as a tsig with depth 255, and
/// no regular expressions.  Note: this doesn't change the trust
/// amount.
pub struct TrustedIntroducerFilter {
}

impl CertificationFilter for TrustedIntroducerFilter {
    fn cost(&self,
            _c: &Certification,
            depth: &mut Depth,
            _amount: &mut usize,
            ignore_regexs: bool,
            regexs: &mut Option<RegexSet>)
        -> bool
    {
        tracer!(TRACE, "TrustedIntroducerFilter::cost");

        *depth = Depth::Unconstrained;
        if ! ignore_regexs {
            *regexs = None;
        }

        return true;
    }
}

impl TrustedIntroducerFilter {
    /// Returns a new `TrustedIntroducerFilter`.
    pub fn new() -> Self {
        Self {
        }
    }
}

/// A filter that caps (limits) the depth of all trust signatures,
/// effectively capping the length of the discovered chains.
pub struct CapDepthFilter {
    /// The maximum depth.
    cap: Depth,
}

impl CertificationFilter for CapDepthFilter {
    fn cost(&self,
            _c: &Certification,
            depth: &mut Depth,
            _amount: &mut usize,
            _ignore_regexs: bool,
            _regexs: &mut Option<RegexSet>)
        -> bool
    {
        *depth = (*depth).min(self.cap);
        true
    }
}

impl CapDepthFilter {
    /// Returns a new `CapDepthFilter` limiting to the given depth.
    pub fn new(cap: usize) -> Self {
        Self {
            cap: Depth::Limit(cap),
        }
    }
}
