//! Brute-force pairwise attack computation.
//!
//! For every ordered pair of arguments (A, B) and every sub-argument of B,
//! checks whether A's conclusion is contrary to the sub-argument's
//! conclusion, premise, or rule name. Simple and correct, but O(n² · s)
//! where *n* is the number of arguments and *s* the average sub-argument
//! count.

use crate::framework::argument::{Argument, ArgumentId};
use crate::framework::argument_store::ArgumentStore;
use crate::framework::attack::{Attack, AttackKind};
use crate::pipeline::traits::AttackComputer;
use crate::system::contrariness::{ContrarinessFunction, ContrarinessRelation};
use crate::system::knowledge::PremiseKind;
use crate::system::rules::Rule;

/// O(n² · s) pairwise enumeration attack computer.
///
/// Iterates over all argument pairs and checks for rebut, undermine, and
/// undercut attacks via the contrariness function. Straightforward but
/// slower than [`IndexedAttackComputer`](super::IndexedAttackComputer)
/// for large argument stores.
pub struct PairwiseAttackComputer;

impl AttackComputer for PairwiseAttackComputer {
    fn compute(&self, store: &ArgumentStore, contrariness: &ContrarinessFunction) -> Vec<Attack> {
        let mut attacks = Vec::new();

        let all_args: Vec<(ArgumentId, &Argument)> = store.all_arguments().collect();

        for &(attacker_id, attacker) in &all_args {
            let attacker_conclusion = attacker.conclusion();

            for &(target_id, target) in &all_args {
                let sub_ids = target.sub_argument_ids(store);

                for &sub_id in &sub_ids {
                    if let Some(sub_arg) = store.get(sub_id) {
                        if let Some(top_rule) = sub_arg.top_rule() {
                            if top_rule.is_defeasible() {
                                let sub_conclusion = sub_arg.conclusion();
                                if contrariness.is_contrary(sub_conclusion, attacker_conclusion) {
                                    let contrary = matches!(
                                        contrariness
                                            .relation_between(sub_conclusion, attacker_conclusion),
                                        Some(ContrarinessRelation::Contrary)
                                    );
                                    attacks.push(Attack {
                                        attacker: attacker_id,
                                        target: target_id,
                                        kind: AttackKind::Rebut {
                                            target_sub: sub_id,
                                            contrary,
                                        },
                                    });
                                }
                            }
                        }

                        if let Argument::Premise { premise, .. } = sub_arg {
                            match premise.kind {
                                PremiseKind::Ordinary => {
                                    if contrariness
                                        .is_contrary(&premise.formula, attacker_conclusion)
                                    {
                                        let contrary = matches!(
                                            contrariness.relation_between(
                                                &premise.formula,
                                                attacker_conclusion
                                            ),
                                            Some(ContrarinessRelation::Contrary)
                                        );
                                        attacks.push(Attack {
                                            attacker: attacker_id,
                                            target: target_id,
                                            kind: AttackKind::Undermine {
                                                target_premise: sub_id,
                                                contrary,
                                            },
                                        });
                                    }
                                }
                                PremiseKind::Axiom => {}
                            }
                        }

                        if let Some(Rule::Defeasible(dr)) = sub_arg.top_rule() {
                            if let Some(rule_name) = &dr.name {
                                if contrariness.is_contrary(rule_name, attacker_conclusion) {
                                    attacks.push(Attack {
                                        attacker: attacker_id,
                                        target: target_id,
                                        kind: AttackKind::Undercut {
                                            target_sub: sub_id,
                                            rule_id: dr.id.clone(),
                                        },
                                    });
                                }
                            }
                        }
                    }
                }
            }
        }

        attacks
    }
}
