use std::collections::{HashMap, HashSet};

use generational_arena::Arena;

use phreak_facts::Facts;

use crate::actions::AlphaNodeId;
use crate::memory::{Memory, WmeMatch};
use crate::nodes::alphanode::AlphaNode;

/// A ClassNode binds AlphaNodes to classes
#[derive(Debug)]
pub(crate) struct ClassNode {
    pub(crate) alphanodes: HashMap<WmeMatch, AlphaNodeId>,
}

impl ClassNode {
    pub(crate) fn new() -> ClassNode {
        ClassNode { alphanodes: HashMap::new() }
    }

    /// Insert facts into the workqueues of the alphanodes and report
    /// which nodes have been updated
    pub(crate) fn add_facts(&self, facts: &Facts, alphanodes: &Arena<AlphaNode>, mut priority: usize) -> (usize, HashSet<AlphaNodeId>) {
        let mut mutated = HashSet::new();

        for fact in facts.iter_attrs() {
            let mut wme = WmeMatch::new(Memory::Any, Memory::Value(fact.1.to_string()), Memory::Any);  // *, v, *
            self.alphanodes.get(&wme).and_then(
                |index| alphanodes.get(*index).and_then(
                    |node| {
                        node.add_fact(priority, fact);
                        mutated.insert(*index);
                        Some(())
                    }
                )
            );
            wme.f2 = Memory::Any; // *, *, *
            self.alphanodes.get(&wme).and_then(
                |index| alphanodes.get(*index).and_then(
                    |node| {
                        node.add_fact(priority, fact);
                        mutated.insert(*index);
                        Some(())
                    }
                )
            );
            wme.f1 = Memory::Value(fact.0.to_string()); // v, *, *
            self.alphanodes.get(&wme).and_then(
                |index| alphanodes.get(*index).and_then(
                    |node| {
                        node.add_fact(priority, fact);
                        mutated.insert(*index);
                        Some(())
                    }
                )
            );
            wme.f2 = Memory::Value(fact.1.to_string()); // v, v, *
            self.alphanodes.get(&wme).and_then(
                |index| alphanodes.get(*index).and_then(
                    |node| {
                        node.add_fact(priority, fact);
                        mutated.insert(*index);
                        Some(())
                    }
                )
            );
            wme.f3 = Memory::Value(fact.2.to_string()); // v, v, v
            self.alphanodes.get(&wme).and_then(
                |index| alphanodes.get(*index).and_then(
                    |node| {
                        node.add_fact(priority, fact);
                        mutated.insert(*index);
                        Some(())
                    }
                )
            );
            wme.f1 = Memory::Any; // *, v, v
            self.alphanodes.get(&wme).and_then(
                |index| alphanodes.get(*index).and_then(
                    |node| {
                        node.add_fact(priority, fact);
                        mutated.insert(*index);
                        Some(())
                    }
                )
            );
            wme.f2 = Memory::Any;// *, *, v
            self.alphanodes.get(&wme).and_then(
                |index| alphanodes.get(*index).and_then(
                    |node| {
                        node.add_fact(priority, fact);
                        mutated.insert(*index);
                        Some(())
                    }
                )
            );
            wme.f1 = Memory::Value(fact.0.to_string()); // v, *, v
            self.alphanodes.get(&wme).and_then(
                |index| alphanodes.get(*index).and_then(
                    |node| {
                        node.add_fact(priority, fact);
                        mutated.insert(*index);
                        Some(())
                    }
                )
            );

        }
        priority = priority + 1;
        (priority, mutated)
    }

    /// Remove a previously inserted fact
    pub(crate) fn remove_facts(&self, facts: &Facts, alphanodes: &Arena<AlphaNode>, mut priority: usize) -> (usize, HashSet<AlphaNodeId>) {
        let mut mutated = HashSet::new();

        for  fact in facts.iter_attrs() {
            let mut wme = WmeMatch::new(Memory::Any, Memory::Value(fact.1.to_string()), Memory::Any);  // *, v, *
            self.alphanodes.get(&wme).and_then(
                |index| alphanodes.get(*index).and_then(
                    |node| {
                        node.remove_fact(priority, fact);
                        mutated.insert(*index);
                        Some(())
                    }
                )
            );
            wme.f2 = Memory::Any; // *, *, *
            self.alphanodes.get(&wme).and_then(
                |index| alphanodes.get(*index).and_then(
                    |node| {
                        node.remove_fact(priority, fact);
                        mutated.insert(*index);
                        Some(())
                    }
                )
            );
            wme.f1 = Memory::Value(fact.0.to_string()); // v, *, *
            self.alphanodes.get(&wme).and_then(
                |index| alphanodes.get(*index).and_then(
                    |node| {
                        node.remove_fact(priority, fact);
                        mutated.insert(*index);
                        Some(())
                    }
                )
            );
            wme.f2 = Memory::Value(fact.1.to_string()); // v, v, *
            self.alphanodes.get(&wme).and_then(
                |index| alphanodes.get(*index).and_then(
                    |node| {
                        node.remove_fact(priority, fact);
                        mutated.insert(*index);
                        Some(())
                    }
                )
            );
            wme.f3 = Memory::Value(fact.2.to_string()); // v, v, v
            self.alphanodes.get(&wme).and_then(
                |index| alphanodes.get(*index).and_then(
                    |node| {
                        node.remove_fact(priority, fact);
                        mutated.insert(*index);
                        Some(())
                    }
                )
            );
            wme.f1 = Memory::Any; // *, v, v
            self.alphanodes.get(&wme).and_then(
                |index| alphanodes.get(*index).and_then(
                    |node| {
                        node.remove_fact(priority, fact);
                        mutated.insert(*index);
                        Some(())
                    }
                )
            );
            wme.f2 = Memory::Any;// *, *, v
            self.alphanodes.get(&wme).and_then(
                |index| alphanodes.get(*index).and_then(
                    |node| {
                        node.remove_fact(priority, fact);
                        mutated.insert(*index);
                        Some(())
                    }
                )
            );
            wme.f1 = Memory::Value(fact.0.to_string()); // v, *, v
            self.alphanodes.get(&wme).and_then(
                |index| alphanodes.get(*index).and_then(
                    |node| {
                        node.remove_fact(priority, fact);
                        mutated.insert(*index);
                        Some(())
                    }
                )
            );

        }
        priority = priority + 1;
        (priority, mutated)
    }
}
