//! The core module contains the main api of the phreak algorithm, as well as the
//! central data storage.

use std::borrow::BorrowMut;
use std::cell::RefCell;
use std::collections::{HashMap, HashSet};
use std::fmt::{Debug, Error, Formatter};
use std::ops::{Index, IndexMut};
use std::rc::Rc;

use compact::CString;
use either;
use generational_arena::Arena;
use generational_indextree::Arena as TreeArena;

use phreak_facts::{FactCore, Facts, Store};
use phreak_rules::ProductionCallback;

use crate::actions::{AlphaNodeId, BetaNodeId, SegmentId};
use crate::betanode::*;
use crate::condition::{Condition, NccCondition};
use crate::jointest::{get_join_tests_from_condition, JoinNodeTest};
use crate::memory::WmeMatch;
use crate::nodes::alphanode::AlphaNode;
use crate::nodes::classnode::ClassNode;
use crate::segment::Segment;
use crate::token::*;

/// The Phreak class embeds the matching network
///
/// This structure embeds the data for the phreak algorithm and provides the public API for
/// the network. The public API is quite simple and belies the complexity that lies behind.
///
/// Once we have an empty Phreak network, we can add production rules. Now we have a meaningful
/// network, and we can add facts. When a combination of facts is found that matches a rule, the
/// callback for that production is activated.
///
/// Facts can be removed again, possibly activating or deactivating a production rule. Every
/// activation or deactivation is returned through a callback.
///
/// Rules can also be removed again. Rule additions and removals can also trigger activations and
/// deactivations.
/// ```
/// # use phreak_engine::Phreak;
/// # use tracing::info;
/// # simple_logger::init().unwrap();
///
/// let phreak = Phreak::new();
///
/// println!("{:?}", phreak);
/// ```
pub struct Phreak {
    /// class name to node mapping
    classes: HashMap<String, ClassNode>,
    /// storage area for alphanodes
    alphanodes: Arena<AlphaNode>,
    /// storage area for betanodes
    betanodes: TreeArena<BetaNode>,
    /// storage area for segments
    segments: TreeArena<Segment>,
    /// root of the betanode network
    root_node: BetaNodeId,
    /// Storage for facts
    facts_store: Store,
    /// Storage of tokens
    tokens: RefCell<TreeArena<Token>>,
    /// Stack based recursion on the network
    processing_stack: RefCell<Vec<StackAction>>,
    /// Fact indicating the abscence of a matching fact
    nil_fact: Rc<FactCore>,
}

// ------------------------------------------------------------------------------------------------
// Public API
// ------------------------------------------------------------------------------------------------

impl Phreak {
    /// Create a new phreak network for matching rules
    pub fn new() -> Self {
        let classes = Default::default();
        let alphanodes = Arena::new();
        let mut betanodes = TreeArena::new();
        let mut segments = TreeArena::new();
        let mut tokens = TreeArena::new();
        let nil_fact = Rc::new(FactCore(CString::new(), CString::new(), CString::new()));


        // create a single node in a single segment which both will be the roots of our trees.
        let root_segment_id = segments.new_node(Segment::new());
        let root_node = betanodes.new_node(BetaNode::new_rootnode(root_segment_id));
        let root_token = tokens.new_node(Token::new(nil_fact.clone(), root_node));
        betanodes[root_node].borrow_mut().get_mut().set_root_token(root_token);

        let root_segment_mut = segments.get_mut(root_segment_id).unwrap().get_mut();
        root_segment_mut.push_node(root_node, true);
        root_segment_mut.node_linked.borrow_mut().set(0, true);

        let facts_store = Store::new();

        Phreak {
            classes,
            alphanodes,
            betanodes,
            segments,
            root_node,
            facts_store,
            tokens: RefCell::new(tokens),
            processing_stack: RefCell::new(Vec::new()),
            nil_fact,
        }
    }

    /// Add a new matching rule to the engine
    pub fn add_production() { unimplemented!() }

    /// Remove a matching rule from the engine
    pub fn remove_production() { unimplemented!() }

    /// Insert new facts into the network
    pub fn add_facts(&self, facts_array: &[Facts]) {
        let mut mutated = HashSet::new();

        let mut position = 0;
        for facts in facts_array {
            println!("-=-=-=-=-=-=-=-=-=-=- add_facts {:?}", facts);
            match self.classes.get(facts.get_name()) {
                None => {}
                Some(cl) => {
                    let (newposition, returnset) = cl.add_facts(facts, &self.alphanodes, position);
                    for idx in returnset {
                        mutated.insert(idx);
                    }
                    position = newposition;
                }
            }
        }
        self.process(mutated);
    }

    /// Remove existing facts from the network
    pub fn remove_facts(&self, facts_array: &[Facts]) {
        let mut mutated = HashSet::new();

        let mut position = 0;
        for facts in facts_array {
            println!("-=-=-=-=-=-=-=-=-=-=- remove_facts {:?}", facts);
            match self.classes.get(facts.get_name()) {
                None => {}
                Some(cl) => {
                    let (newposition, returnset) = cl.remove_facts(facts, &self.alphanodes, position);
                    for idx in returnset {
                        mutated.insert(idx);
                    }
                    position = newposition;
                }
            }
        }
        self.process(mutated);
    }

    fn process_tree_structure(&self) {
        self.process(HashSet::new());
    }

    // Process pending actions on the network
    //
    // Processes all pending Alphanetwork actions, then processes feasible Betanetwork actions
    fn process(&self, mutated: HashSet<AlphaNodeId>) {
        {
            let stack = &mut *self.processing_stack.borrow_mut();

            stack.push(StackAction::ProcessSegment(self.get_rootsegment_id()));

            for alpha in mutated.iter() {
                stack.push(StackAction::ProcessAlphaNode(*alpha));

                self.alphanodes.get(*alpha).unwrap().process_pending(&self.betanodes, &self.segments);
            }
        }

        self.process_actions();
    }

    /// Returns the root of the betanode network
    pub(crate) fn get_root_node(&self) -> &BetaNode {
        self.betanodes.get(self.root_node).unwrap().get()
    }

    // Implementation for the stack based processing
    fn process_actions(&self) {
        let stack = &mut *self.processing_stack.borrow_mut();

        while let Some(action) = stack.pop() {
            println!("=== Stack.pop( {:?} )", &action);
            match action {
                StackAction::ProcessClassNode(_) => {}
                StackAction::ProcessAlphaNode(alpha_id) => {
                    self.alphanodes.get(alpha_id).unwrap()
                        .process_pending(&self.betanodes, &self.segments);
                }
                StackAction::ProcessBetaNode(beta_id) => {
                    self.betanodes.get(beta_id).unwrap().get()
                        .process_actions(beta_id,
                                         &self.alphanodes,
                                         &self.betanodes,
                                         &self.segments,
                                         &mut self.tokens.borrow_mut(),
                                         &self.nil_fact,
                        );
                }
                StackAction::ProcessSegment(segment_id) => {
                    let segment = self.segments.get(segment_id).unwrap().get();

                    // we always will consider the segments that come after the current segment
                    for child_id in segment_id.reverse_children(&self.segments) {
                        stack.push(StackAction::ProcessSegment(child_id));
                    }

// disable optimizations for now
//                    if !*segment.needs_processing.borrow() {
//                        continue;
//                    }

                    let mut reversed = segment.betanodes.clone();
                    reversed.reverse();

                    //  we will process the nodes in this segment in reverse order (root to leaf)
                    for node_id in reversed.iter() {
                        stack.push(StackAction::ProcessBetaNode(*node_id));
                    }

                    *segment.needs_processing.borrow_mut() = false;
                }
            }
        }
    }
}

// ------------------------------------------------------------------------------------------------
// Simple Display function for debug purposes
// ------------------------------------------------------------------------------------------------
impl Phreak {
    /// Dumps the content of the phreak network betanetwork for debugging
    pub(crate) fn dump_structure(&self) {
        println!("Phreak {{");
        let mut stack = Vec::new();
        stack.push(("".to_owned(), self.root_node));

        while !stack.is_empty() {
            let (tabs, node) = stack.pop().unwrap();
            for child in node.reverse_children(&self.betanodes) {
                let mut x = String::from("\t");
                x.push_str(&tabs);
                stack.push((x, child));
            }


            println!("{} <{}> {:?}", tabs, node, self.betanodes.index(node).get());
        }
        println!("}}");
    }

    /// Dumps the content of the phreak network betanetwork for debugging
    pub(crate) fn dump_segment_structure(&self) {
        println!("Phreak {{ segments: ");
        let mut stack = Vec::new();
        let rootnode = &self.betanodes[self.root_node];
        match rootnode.get() {
            BetaNode::RootNode(rn) => {
                stack.push(("".to_owned(), rn.segment));
            }
            _ => { unreachable!(); }
        }


        while !stack.is_empty() {
            let (tabs, segment_id) = stack.pop().unwrap();
            for child in segment_id.reverse_children(&self.segments) {
                let mut x = String::from("\t");
                x.push_str(&tabs);
                stack.push((x, child));
            }


            println!("{} <{}> {:?}", tabs, segment_id, &self.segments[segment_id]);
        }
        println!("}}");
    }
}

impl Debug for Phreak {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
        write!(f, "Phreak{{ {:?} }}", self.tokens.borrow())
    }
}
// ------------------------------------------------------------------------------------------------
// Private methods
// ------------------------------------------------------------------------------------------------

// Segment related methods
impl Phreak {
    fn new_segment(&mut self, parent_id: SegmentId) -> SegmentId {
        let s = self.segments.new_node(Segment::new());
        parent_id.append(s, &mut self.segments);
        s
    }

    fn get_segment_id(&self, node: BetaNodeId) -> SegmentId {
        self.betanodes.index(node).get().get_segment()
    }

    fn get_segment(&self, node: BetaNodeId) -> &Segment {
        self.segments.index(self.get_segment_id(node)).get()
    }

    fn get_segment_mut(&mut self, node: BetaNodeId) -> &mut Segment {
        self.segments.index_mut(self.get_segment_id(node)).get_mut()
    }

    fn move_segment_members_at(&mut self, origin: SegmentId, destination: SegmentId, node: BetaNodeId) {
        let index;
        println!("+before {:?} {:?}", &self.segments[origin].get().betanodes, &self.segments[destination].get().betanodes);

        let origin_segment = self.segments[origin].get_mut();

        if let Some(r) = origin_segment.betanodes.iter().position(
            |item| node.eq(item)) {
            index = r;
        } else {
            panic!("node {:?} should be in origin {:?} {:?}", node, origin, origin_segment);
        }

        let mut r = origin_segment.betanodes.split_off(index);
        let mut r1 = origin_segment.node_linked.borrow_mut().split_off(index);
        let mut r2 = origin_segment.node_modified.borrow_mut().split_off(index);

        let segment_mut = self.segments[destination].get_mut();
        segment_mut.betanodes.append(&mut r);
        segment_mut.node_linked.borrow_mut().append(&mut r1);
        segment_mut.node_modified.borrow_mut().append(&mut r2);

        for node_id in self.segments[destination].get().betanodes.iter() {
            self.betanodes[*node_id].get_mut().set_segment(destination);
        }

        println!("+after {:?} {:?}", &self.segments[origin].get().betanodes, &self.segments[destination].get().betanodes);
    }

    fn get_rootsegment_id(&self) -> SegmentId {
        self.get_segment_id(self.root_node)
    }

    // checks the need to split the segment in order to append a new node to the given
    // parent segment. Returns the id of the segment where the new node should be placed.
    // uses the count of the children of parent as an optimization
    fn split_segment_after_parentnode_if_required(&mut self, count: usize, parent_id: BetaNodeId) -> SegmentId {
        let parent_segment = self.get_segment_id(parent_id);

        if count == 1 {
            // we will now introduce a split, so the parent and the children can't share the
            // same segment anymore: we split between parent and current child
            let child_segment = self.new_segment(parent_segment);
            // both sides of the split MUST have the same rules after the split
            //self.copy_rules(parent_segment, child_segment);

            let mut children = parent_id.children(&self.betanodes);
            let child: BetaNodeId = children.next().unwrap();

            self.move_segment_members_at(
                parent_segment,
                child_segment,
                child,
            );

            // the new node will be in a fresh segment
            self.new_segment(parent_segment)
        } else if count > 1 {
            // segment is already split, just need a new segment for the new node
            // the new node will be in a fresh segment
            self.new_segment(parent_segment)
        } else {
            // no split, just a linear segment
            parent_segment
        }
    }
}

// Node building

impl Phreak {
    fn build_or_share_classnode(&mut self, class: &str) -> &mut ClassNode {
        self.classes.entry(class.to_owned()).or_insert_with(
            || ClassNode::new()
        )
    }

    fn build_or_share_alphanode<'a>(&mut self, class: &str, wme: WmeMatch) -> (bool, AlphaNodeId) {
        let cl = self.classes.get_mut(&class.to_owned()).expect("class should already be created");

        if let Some(node) = cl.alphanodes.get(&wme) {
            return (false, *node);
        }

        let node_id = self.alphanodes.insert(AlphaNode::new());
        cl.alphanodes.insert(wme, node_id);
        (true, node_id)
    }

    // returns either the matching betamemory id, or if not found, it returns the number of
    // children the parent has, which we can use to optimize the segment creation
    fn node_share_betamemory(&self, parent_id: BetaNodeId) -> either::Either<BetaNodeId, usize> {
        let mut count = 0;
        for child_id in parent_id.children(&self.betanodes) {
            if self.betanodes.index(child_id).get().is_memorynode() {
                return either::Left(child_id);
            }
            count += 1
        }
        either::Right(count)
    }

    fn node_new_production(&mut self, parent_id: BetaNodeId, segment_id: SegmentId, callback: Box<dyn ProductionCallback>) -> BetaNodeId {
        let production_id = self.betanodes.new_node(BetaNode::new_productionnode(segment_id, callback));

        // update node tree, segment tree
        parent_id.prepend(production_id, &mut self.betanodes);
        let segment =
            self.segments.index_mut(segment_id).get_mut();
        segment.push_node(production_id, true);

        BetaNode::init_new_node(production_id, &mut self.betanodes, &self.alphanodes, &mut self.tokens.borrow_mut(), &self.nil_fact);

        production_id
    }

    fn node_new_betamemory(&mut self, parent_id: BetaNodeId, segment_id: SegmentId) -> BetaNodeId {
        let betamemory_id = self.betanodes.new_node(BetaNode::new_memorynode(segment_id));

        // update node tree, segment tree
        parent_id.prepend(betamemory_id, &mut self.betanodes);
        self.segments.index_mut(segment_id).get_mut().push_node(betamemory_id, true);

        BetaNode::init_new_node(betamemory_id, &mut self.betanodes, &self.alphanodes, &mut self.tokens.borrow_mut(), &self.nil_fact);

        betamemory_id
    }

    fn build_or_share_betamemory(&mut self, parent_id: BetaNodeId) -> BetaNodeId {
        match self.node_share_betamemory(parent_id) {
            either::Left(child_id) => child_id,
            either::Right(count) => {
                let leaf_segment = self.split_segment_after_parentnode_if_required(count, parent_id);

                println!("creating a new betamemory in segment {:?}, {:?}", leaf_segment, self.segments.index(leaf_segment));

                self.node_new_betamemory(parent_id, leaf_segment)
            }
        }
    }

    fn node_share_joinnode(&self, parent_id: BetaNodeId,
                           amem_id: AlphaNodeId,
                           tests: &Vec<JoinNodeTest>,
    ) -> either::Either<BetaNodeId, usize> {
        let mut count = 0;
        for child_id in parent_id.children(&self.betanodes) {
            let child = self.betanodes.get(child_id).unwrap().get();
            if let BetaNode::JoinNode(jn) = child {
                if (jn.amem == amem_id) & jn.tests.eq(tests) {
                    println!("shared joinnode {:?}", child_id);
                    return either::Left(child_id);
                }
            }
            count = count + 1;
        }
        either::Right(count)
    }

    fn node_new_joinnode(&mut self, parent_id: BetaNodeId,
                         amem_id: AlphaNodeId,
                         tests: Vec<JoinNodeTest>,
                         segment_id: SegmentId,
    ) -> BetaNodeId {
        let joinnode_id = self.betanodes.new_node(BetaNode::new_joinnode(amem_id, tests, segment_id));
        self.segments.get_mut(segment_id).unwrap().get_mut().push_node(joinnode_id,
                                                                       !self.alphanodes[amem_id].cell.borrow().items.is_empty(),
        );

        parent_id.prepend(joinnode_id, &mut self.betanodes);
        let a = self.alphanodes.get_mut(amem_id).unwrap();
        a.children.push_front(joinnode_id);

        BetaNode::init_new_node(joinnode_id, &mut self.betanodes, &self.alphanodes, &mut self.tokens.borrow_mut(), &self.nil_fact);

        joinnode_id
    }

    fn build_or_share_join_node(&mut self, parent_id: BetaNodeId,
                                amem_id: AlphaNodeId,
                                tests: Vec<JoinNodeTest>,
    ) -> BetaNodeId {
        match self.node_share_joinnode(parent_id, amem_id, &tests) {
            either::Left(child_id) => child_id,
            either::Right(count) => {
                let leaf_segment = self.split_segment_after_parentnode_if_required(count, parent_id);
                println!("creating a new joinnode in segment {}, {:?}", leaf_segment, self.segments.get(leaf_segment));

                self.node_new_joinnode(parent_id, amem_id, tests, leaf_segment)
            }
        }
    }

    fn node_new_negativenode(&mut self, parent_id: BetaNodeId,
                             amem_id: AlphaNodeId,
                             tests: Vec<JoinNodeTest>,
                             segment_id: SegmentId,
    ) -> BetaNodeId {
        let negativenode_id = self.betanodes.new_node(BetaNode::new_negativenode(amem_id, tests, segment_id));
        self.segments.get_mut(segment_id).unwrap().get_mut().push_node(
            negativenode_id, !self.alphanodes[amem_id].cell.borrow().items.is_empty(),
        );

        parent_id.prepend(negativenode_id, &mut self.betanodes);
        let a = self.alphanodes.get_mut(amem_id).unwrap();
        a.children.push_front(negativenode_id);

        BetaNode::init_new_node(negativenode_id, &mut self.betanodes, &self.alphanodes, &mut self.tokens.borrow_mut(), &self.nil_fact);

        negativenode_id
    }

    fn build_or_share_negativenode(&mut self, parent_id: BetaNodeId,
                                   amem_id: AlphaNodeId,
                                   tests: Vec<JoinNodeTest>,
    ) -> BetaNodeId {
        let mut count = 0;
        for _ in parent_id.children(&self.betanodes) { count = count + 1; }
        let leaf_segment = self.split_segment_after_parentnode_if_required(count, parent_id);
        println!("creating a new negativenode in segment {}, {:?}", leaf_segment, self.segments.get(leaf_segment));

        self.node_new_negativenode(parent_id, amem_id, tests, leaf_segment)
    }

    fn build_production(&mut self, parent_id: BetaNodeId, callback: Box<dyn ProductionCallback>) -> BetaNodeId {
        let leaf_segment = self.split_segment_after_parentnode_if_required(
            parent_id.children(&self.betanodes).count(),
            parent_id);
        self.node_new_production(parent_id, leaf_segment, callback)
    }

    fn node_new_ncc_pair(
        &mut self,
        number_of_conjuncts: usize,
        segment_id_for_ncc: SegmentId,
        segment_id_for_ncp: SegmentId,
    ) -> (BetaNodeId, BetaNodeId) {

        // after a split, the segments must not be equal
        debug_assert_ne!(segment_id_for_ncc, segment_id_for_ncp);

        // todo: split segments
        let ncc = self.betanodes.new_node(BetaNode::new_nccnode(self.root_node, segment_id_for_ncc));
        let ncp = self.betanodes.new_node(BetaNode::new_nccpartnernode(ncc, segment_id_for_ncp, number_of_conjuncts));

        self.segments[segment_id_for_ncc].get_mut().betanodes.push(ncc);
        self.segments[segment_id_for_ncp].get_mut().betanodes.push(ncp);

        if let BetaNode::NccNode(data) = self.betanodes[ncc].get_mut() {
            data.ncp = ncp;
        }
        (ncc, ncp)
    }

    fn build_or_share_ncc_nodes(
        &mut self,
        parent_id: BetaNodeId,
        cond: &NccCondition,
        earlier: &[&Condition],
    ) -> BetaNodeId {
        let number_of_conjuncts = cond.children.len();

        let bottom_of_subnetwork_id =
            self.build_or_share_network_for_conditions(parent_id, &cond.children, earlier);


        let mut count = 0;
        {
            let parent = self.betanodes[parent_id].get();

            for child in parent_id.children(&self.betanodes) {
                count = count + 1;
                if let BetaNode::NccNode(ncc) = parent {
                    if ncc.ncp == bottom_of_subnetwork_id {
                        return child;
                    }
                }
            }
        }

        let ncc_segment = self.split_segment_after_parentnode_if_required(count, parent_id);

        let leaf_segment_id = self.split_segment_after_parentnode_if_required(
            bottom_of_subnetwork_id.children(&self.betanodes).count(),
            bottom_of_subnetwork_id,
        );

        let (ncc_id, partner_id) = self.node_new_ncc_pair(number_of_conjuncts, ncc_segment, leaf_segment_id);

        parent_id.append(ncc_id, &mut self.betanodes);
        bottom_of_subnetwork_id.prepend(partner_id, &mut self.betanodes);

        ncc_id
    }

    fn build_or_share_network_for_conditions(
        &mut self,
        parent_id: BetaNodeId,
        conditions: &[Condition],
        earlier: &[&Condition],
    ) -> BetaNodeId {
        let mut current_node: BetaNodeId = parent_id;
        let mut conditions_higher_up = Vec::from(earlier);

        for condition in conditions {
            match condition {
                Condition::Positive(cond) => {
                    if current_node != self.root_node {
                        current_node = self.build_or_share_betamemory(current_node);
                    }

                    let tests = get_join_tests_from_condition(&condition, &conditions_higher_up);
                    let wme = WmeMatch::from(cond);
                    let am = self.build_or_share_alphanode(&cond.class, wme);
                    current_node = self.build_or_share_join_node(current_node, am.1, tests);
                }
                Condition::Negative(cond) => {
                    let tests = get_join_tests_from_condition(&condition, &conditions_higher_up);
                    let wme = WmeMatch::from(cond);
                    let am = self.build_or_share_alphanode(&cond.class, wme);
                    current_node = self.build_or_share_negativenode(current_node, am.1, tests);
                }
                Condition::Ncc(cond) => {
                    current_node = self.build_or_share_ncc_nodes(current_node, cond, &conditions_higher_up)
                }
            }
            conditions_higher_up.push(condition);
        }

        current_node
    }
}

#[cfg(test)]
mod tests {
    use std::cell::Cell;
    use std::sync::Arc;

    use phreak_facts::FactsBuilder;
    use phreak_rules::{PrintCallBack, ProductionCallback};
    use test_case::test_case;

    use crate::jointest::JoinTestField;
    use crate::memory::Memory;
    use crate::traits::Actionable;

    use super::*;

    #[derive(Debug)]
    struct CountCallback { counter: Arc<Cell<usize>> }

    impl ProductionCallback for CountCallback {
        fn activate(&self) {
            self.counter.replace(self.counter.take() + 1);
        }

        fn deactivate(&self) {
            self.counter.replace(self.counter.take() - 1);
        }
    }

    fn ball(id: &str, color: &str) -> Facts {
        FactsBuilder::new("Ball".to_owned())
            .add_attributes(id.to_owned(), "object".to_owned(), "ball".to_owned())
            .add_attributes(id.to_owned(), "color".to_owned(), color.to_owned())
            .build()
    }

    // network that matches a join of two facts on a single class
    //
    // We are looking for black spitfires.
    #[allow(unused_variables)]
    fn helper_build_network_join_two_facts(p: &mut Phreak, callback: Box<dyn ProductionCallback>) {
        let cl = p.build_or_share_classnode("Plane");
        let a1 = p.build_or_share_alphanode("Plane", WmeMatch::new(
            Memory::Any,
            Memory::Value("name".to_owned()),
            Memory::Value("spitfire".to_owned()),
        ));
        let a2 = p.build_or_share_alphanode("Plane", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("black".to_owned()),
        ));

        let cl = p.classes.get("Plane").unwrap();

        // join, when fact matches Plane(x, "name", "spitfire")
        let j1 = p.build_or_share_join_node(p.root_node, a1.1, vec!());
        let m = p.build_or_share_betamemory(j1);
        // join, when fact matches Plane(y, "color", "black")
        // AND x == y
        let j2 = p.build_or_share_join_node(m, a2.1,
                                            vec!(JoinNodeTest {
                                                f1: JoinTestField::Field1,
                                                condition_number_of_arg2: 1,
                                                f2: JoinTestField::Field1,
                                            }));
        let p1 = p.build_production(j2, callback);
    }

    // Network that matches balls by color and size
    // rule 1 matches blue balls
    // rule 2 matches medium sized red balls
    #[allow(unused_variables)]
    fn helper_build_network_balls(p: &mut Phreak, callbacks: [Box<dyn ProductionCallback>; 2]) {
        let [callback1, callback2] = callbacks;
        let cl = p.build_or_share_classnode("Ball");

        // for rule 1
        let a1 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("blue".to_owned()),
        ));

        // for rule 2
        let a2 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("size".to_owned()),
            Memory::Value("medium".to_owned()),
        ));
        let a3 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("red".to_owned()),
        ));

        let cl = p.classes.get("Ball").unwrap();

        // rule 1
        let j1 = p.build_or_share_join_node(p.root_node, a1.1, vec!());

        // rule 2
        let j2 = p.build_or_share_join_node(p.root_node, a2.1, vec!());
        let m = p.build_or_share_betamemory(j2);
        let j3 = p.build_or_share_join_node(m, a3.1,
                                            vec!(JoinNodeTest {
                                                f1: JoinTestField::Field1,
                                                condition_number_of_arg2: 1,
                                                f2: JoinTestField::Field1,
                                            }));

        let p1 = p.build_production(j1, callback1);
        let p2 = p.build_production(j3, callback2);
    }

    // network that matches a join of two facts on a single class
    //
    // We are looking for black messerschmitt.
    #[allow(unused_variables)]
    fn helper_build_network_join_two_related_facts1(p: &mut Phreak, callback: Box<dyn ProductionCallback>) {
        let cl = p.build_or_share_classnode("Plane");
        let a1 = p.build_or_share_alphanode("Plane", WmeMatch::new(
            Memory::Any,
            Memory::Value("name".to_owned()),
            Memory::Value("messerschmitt".to_owned()),
        ));
        let a2 = p.build_or_share_alphanode("Plane", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("black".to_owned()),
        ));

        let cl = p.classes.get("Plane").unwrap();

        // join, when fact matches Plane(x, "name", "spitfire")
        let j1 = p.build_or_share_join_node(p.root_node, a1.1, vec!());
        let m = p.build_or_share_betamemory(j1);
        // join, when fact matches Plane(y, "color", "black")
        // AND x == y
        let j2 = p.build_or_share_join_node(m, a2.1,
                                            vec!(JoinNodeTest {
                                                f1: JoinTestField::Field1,
                                                condition_number_of_arg2: 1,
                                                f2: JoinTestField::Field1,
                                            }));
        let p1 = p.build_production(j2, callback);
    }

    // network that matches a join of two facts on a single class
    //
    // We are looking for red spitfires.
    #[allow(unused_variables)]
    fn helper_build_network_join_two_related_facts2(p: &mut Phreak, callback: Box<dyn ProductionCallback>) {
        let cl = p.build_or_share_classnode("Plane");
        let a1 = p.build_or_share_alphanode("Plane", WmeMatch::new(
            Memory::Any,
            Memory::Value("name".to_owned()),
            Memory::Value("spitfire".to_owned()),
        ));
        let a2 = p.build_or_share_alphanode("Plane", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("red".to_owned()),
        ));

        let cl = p.classes.get("Plane").unwrap();

        // join, when fact matches Plane(x, "name", "spitfire")
        let j1 = p.build_or_share_join_node(p.root_node, a1.1, vec!());
        let m = p.build_or_share_betamemory(j1);
        // join, when fact matches Plane(y, "color", "black")
        // AND x == y
        let j2 = p.build_or_share_join_node(m, a2.1,
                                            vec!(JoinNodeTest {
                                                f1: JoinTestField::Field1,
                                                condition_number_of_arg2: 1,
                                                f2: JoinTestField::Field1,
                                            }));
        let p1 = p.build_production(j2, callback);
    }

    #[test]
    fn test_build_network() {
        let mut p = Phreak::new();

        // creates a network containing one rule to match 2 facts: A /\ B, where A and B
        // share a common identifier
        // rootnode, joinnode, memorynode, joinnode, productionnode
        // no splits, so only 1 segment
        helper_build_network_join_two_facts(&mut p, Box::new(PrintCallBack {}));

        assert_eq!(p.classes.len(), 1);
        assert_eq!(p.alphanodes.len(), 2);
        assert_eq!(p.betanodes.count(), 5);

        // there is 1 segment, containing all 5 betanodes
        assert_eq!(p.segments.count(), 1);
        let segment = p.segments[p.get_rootsegment_id()].get();
        assert_eq!(segment.betanodes.len(), 5);
        assert_eq!(segment.node_linked.borrow().len(), segment.betanodes.len()); // invariant
        assert_eq!(segment.node_modified.borrow().len(), segment.betanodes.len()); // invariant

        p.dump_structure();
        p.dump_segment_structure();
        p.process_tree_structure();
//        println!("Phreak: {:?}", p);
    }

    // one join
    #[test_case(
    vec ! (
    FactsBuilder::new("Plane".to_owned())
    .add_attributes("1".to_owned(), "name".to_owned(), "spitfire".to_owned())
    .add_attributes("1".to_owned(), "color".to_owned(), "black".to_owned())
    .build()).as_slice() => 1; "one join"
    )]
    // two at once
    #[test_case(
    vec ! (
    FactsBuilder::new("Plane".to_owned())
    .add_attributes("1".to_owned(), "name".to_owned(), "spitfire".to_owned())
    .add_attributes("1".to_owned(), "color".to_owned(), "black".to_owned())
    .build(),
    FactsBuilder::new("Plane".to_owned())
    .add_attributes("2".to_owned(), "name".to_owned(), "spitfire".to_owned())
    .add_attributes("2".to_owned(), "color".to_owned(), "black".to_owned())
    .build(),
    ).as_slice() => 2; "two at once"
    )]
    // one not to join for field1
    #[test_case(
    vec ! (
    FactsBuilder::new("Plane".to_owned())
    .add_attributes("1".to_owned(), "name".to_owned(), "messerschmitt".to_owned())
    .add_attributes("1".to_owned(), "color".to_owned(), "black".to_owned())
    .build()).as_slice() => 0; "one not to join for field1"
    )]
    // one not to join for field2
    #[test_case(
    vec ! (
    FactsBuilder::new("Plane".to_owned())
    .add_attributes("1".to_owned(), "name".to_owned(), "spitfire".to_owned())
    .add_attributes("1".to_owned(), "color".to_owned(), "red".to_owned())
    .build()).as_slice() => 0; "one not to join for field2"
    )]
    // mixed
    #[test_case(
    vec ! (
    FactsBuilder::new("Plane".to_owned())
    .add_attributes("1".to_owned(), "name".to_owned(), "spitfire".to_owned())
    .add_attributes("1".to_owned(), "color".to_owned(), "black".to_owned())
    .build(),
    FactsBuilder::new("Plane".to_owned())
    .add_attributes("2".to_owned(), "name".to_owned(), "spitfire".to_owned())
    .add_attributes("2".to_owned(), "color".to_owned(), "red".to_owned())
    .build(),
    FactsBuilder::new("Plane".to_owned())
    .add_attributes("3".to_owned(), "name".to_owned(), "spitfire".to_owned())
    .add_attributes("3".to_owned(), "color".to_owned(), "black".to_owned())
    .build(),
    FactsBuilder::new("Plane".to_owned())
    .add_attributes("4".to_owned(), "name".to_owned(), "messerschmitt".to_owned())
    .add_attributes("4".to_owned(), "color".to_owned(), "black".to_owned())
    .build(),
    ).as_slice() => 2; "mixed"
    )]
    fn test_facts_join(arr: &[Facts]) -> usize {
        let mut p = Phreak::new();
        let counter1: Arc<Cell<usize>> = Arc::new(Cell::new(0));

        helper_build_network_join_two_facts(&mut p, Box::new(CountCallback { counter: counter1.clone() }));
        p.process_tree_structure();

        assert_eq!(counter1.get(), 0);
        p.add_facts(arr);
        counter1.get()
    }

    #[test_case(
    vec ! (
    FactsBuilder::new("Plane".to_owned())
    .add_attributes("1".to_owned(), "name".to_owned(), "spitfire".to_owned())
    .add_attributes("1".to_owned(), "color".to_owned(), "black".to_owned())
    .build()).as_slice() => (1, 0, 0)
    )]
    #[test_case(
    vec ! (
    FactsBuilder::new("Plane".to_owned())
    .add_attributes("1".to_owned(), "name".to_owned(), "spitfire".to_owned())
    .add_attributes("1".to_owned(), "color".to_owned(), "red".to_owned())
    .build()).as_slice() => (0, 0, 1)
    )]
    #[test_case(
    vec ! (
    FactsBuilder::new("Plane".to_owned())
    .add_attributes("1".to_owned(), "name".to_owned(), "spitfire".to_owned())
    .add_attributes("1".to_owned(), "color".to_owned(), "red".to_owned())
    .build(),
    FactsBuilder::new("Plane".to_owned())
    .add_attributes("2".to_owned(), "name".to_owned(), "spitfire".to_owned())
    .add_attributes("2".to_owned(), "color".to_owned(), "green".to_owned())
    .build(),
    FactsBuilder::new("Plane".to_owned())
    .add_attributes("3".to_owned(), "name".to_owned(), "spitfire".to_owned())
    .add_attributes("3".to_owned(), "color".to_owned(), "black".to_owned())
    .build(),
    FactsBuilder::new("Plane".to_owned())
    .add_attributes("4".to_owned(), "name".to_owned(), "spitfire".to_owned())
    .add_attributes("4".to_owned(), "color".to_owned(), "red".to_owned())
    .build(),
    FactsBuilder::new("Plane".to_owned())
    .add_attributes("5".to_owned(), "name".to_owned(), "messerschmitt".to_owned())
    .add_attributes("5".to_owned(), "color".to_owned(), "black".to_owned())
    .build(),
    ).as_slice() => (1, 1, 2)
    )]
    fn test_facts_join_complex(arr: &[Facts]) -> (usize, usize, usize) {
        let mut p = Phreak::new();
        let counter1: Arc<Cell<usize>> = Arc::new(Cell::new(0));
        let counter2: Arc<Cell<usize>> = Arc::new(Cell::new(0));
        let counter3: Arc<Cell<usize>> = Arc::new(Cell::new(0));

        helper_build_network_join_two_facts(&mut p, Box::new(CountCallback { counter: counter1.clone() }));
        helper_build_network_join_two_related_facts1(&mut p, Box::new(CountCallback { counter: counter2.clone() }));
        helper_build_network_join_two_related_facts2(&mut p, Box::new(CountCallback { counter: counter3.clone() }));
        p.process_tree_structure();

        assert_eq!(counter1.get(), 0);
        p.add_facts(arr);
        (counter1.get(), counter2.get(), counter3.get())
    }

    #[test_case(
    vec ! (
    FactsBuilder::new("Plane".to_owned())
    .add_attributes("1".to_owned(), "name".to_owned(), "spitfire".to_owned())
    .build(),
    FactsBuilder::new("Plane".to_owned())
    .add_attributes("1".to_owned(), "color".to_owned(), "black".to_owned())
    .build(),
    ).as_slice() => vec ! (0, 1); "incremental processing"
    )]
    fn test_incremental(arr: &[Facts]) -> Vec<usize> {
        let mut p = Phreak::new();
        let counter1: Arc<Cell<usize>> = Arc::new(Cell::new(0));
        helper_build_network_join_two_facts(&mut p, Box::new(CountCallback { counter: counter1.clone() }));
        p.process_tree_structure();

        let mut answers = vec!();

        for fact in arr {
            println!("+++ iteration started");
            p.add_facts(&[fact.clone()]);
            println!("+++ iteration complete");
            answers.push(counter1.get());
        }

        answers
    }


    // simplest add and remove fact testcase
    #[test_case(
    vec ! (
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("3".to_owned(), "color".to_owned(), "blue".to_owned())
    .add_attributes("3".to_owned(), "size".to_owned(), "medium".to_owned())
    .build(),
    ).as_slice() => vec ! (
    (1, 0), (0, 0),
    ); "mirrored add and remove single"
    )]
    // complex add and remove fact testcase
    #[test_case(
    vec ! (
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("1".to_owned(), "color".to_owned(), "green".to_owned())
    .add_attributes("1".to_owned(), "size".to_owned(), "large".to_owned())
    .build(),
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("2".to_owned(), "color".to_owned(), "yellow".to_owned())
    .add_attributes("2".to_owned(), "size".to_owned(), "small".to_owned())
    .build(),
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("3".to_owned(), "color".to_owned(), "red".to_owned())
    .add_attributes("3".to_owned(), "size".to_owned(), "medium".to_owned())
    .build(),
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("4".to_owned(), "color".to_owned(), "blue".to_owned())
    .add_attributes("4".to_owned(), "size".to_owned(), "tiny".to_owned())
    .build(),
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("5".to_owned(), "color".to_owned(), "green".to_owned())
    .add_attributes("5".to_owned(), "size".to_owned(), "small".to_owned())
    .build(),
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("6".to_owned(), "color".to_owned(), "red".to_owned())
    .add_attributes("6".to_owned(), "size".to_owned(), "medium".to_owned())
    .build(),
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("7".to_owned(), "color".to_owned(), "blue".to_owned())
    .add_attributes("7".to_owned(), "size".to_owned(), "medium".to_owned())
    .build(),
    ).as_slice() => vec ! (
    (0, 0), (0, 0), (0, 1), (1, 1), (1, 1), (1, 2), (2, 2),
    (1, 2), (1, 1), (1, 1), (0, 1), (0, 0), (0, 0), (0, 0),
    ); "mirrored add and remove sequence"
    )]
    // add two identical facts matching one condition and remove them one by one
    #[test_case(
    vec ! (
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("1".to_owned(), "color".to_owned(), "blue".to_owned())
    .add_attributes("1".to_owned(), "size".to_owned(), "medium".to_owned())
    .build(),
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("2".to_owned(), "color".to_owned(), "blue".to_owned())
    .add_attributes("2".to_owned(), "size".to_owned(), "medium".to_owned())
    .build(),
    ).as_slice() => vec ! (
    (1, 0), (2, 0),
    (1, 0), (0, 0),
    ); "mirrored add and remove duplicate single condition match"
    )]
    // add two identical facts matching two conditions and remove them one by one
    #[test_case(
    vec ! (
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("1".to_owned(), "color".to_owned(), "red".to_owned())
    .add_attributes("1".to_owned(), "size".to_owned(), "medium".to_owned())
    .build(),
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("2".to_owned(), "color".to_owned(), "red".to_owned())
    .add_attributes("2".to_owned(), "size".to_owned(), "medium".to_owned())
    .build(),
    ).as_slice() => vec ! (
    (0, 1), (0, 2),
    (0, 1), (0, 0),
    ); "mirrored add and remove duplicate double condition match"
    )]
    // add two identical facts matching two conditions and remove them one by one
    #[test_case(
    vec ! (
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("1".to_owned(), "size".to_owned(), "medium".to_owned())
    .add_attributes("1".to_owned(), "color".to_owned(), "red".to_owned())
    .build(),
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("2".to_owned(), "size".to_owned(), "medium".to_owned())
    .add_attributes("2".to_owned(), "color".to_owned(), "red".to_owned())
    .build(),
    ).as_slice() => vec ! (
    (0, 1), (0, 2),
    (0, 1), (0, 0),
    ); "mirrored add and remove duplicate double condition match, other fact order"
    )]
    fn test_add_and_remove_facts(arr: &[Facts]) -> Vec<(usize, usize)> {
        let mut p = Phreak::new();
        let counter1: Arc<Cell<usize>> = Arc::new(Cell::new(0));
        let counter2: Arc<Cell<usize>> = Arc::new(Cell::new(0));

        helper_build_network_balls(&mut p, [
            Box::new(CountCallback { counter: counter1.clone() }),
            Box::new(CountCallback { counter: counter2.clone() }),
        ]);
        p.process_tree_structure();
        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);

        let mut answers = vec!();

        for fact in arr {
            p.add_facts(&[fact.clone()]);
            print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
            answers.push((counter1.get(), counter2.get()));
        }

        for fact in arr.iter().rev() {
            p.remove_facts(&[fact.clone()]);
            print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
            answers.push((counter1.get(), counter2.get()));
        }

        answers
    }

    // ------------------------------------------------------------------------------------------------
    // NegativeNode
    // ------------------------------------------------------------------------------------------------

    // network that matches the absence of a certain fact
    //
    // We are looking for red balls.
    #[allow(unused_variables)]
    fn helper_build_network_single_negative_node(p: &mut Phreak, callback: Box<dyn ProductionCallback>) {
        let cl = p.build_or_share_classnode("Ball");
        let a1 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("red".to_owned()),
        ));
        let cl = p.classes.get("Ball").unwrap();

        let n1 = p.build_or_share_negativenode(p.root_node, a1.1, vec!());
        let p1 = p.build_production(n1, callback);
    }

    // network that matches the absence of two facts
    // not(A) or not(B)
    // We are looking for not(red) or not(blue) balls.
    // we need a join node as first node to create an anchor for the jointests, since we can
    // only test vs tokens in memorynodes
    // This rule will activate when a ball has a color (0) that is neither red (1) nor blue (2)
    #[allow(unused_variables)]
    fn helper_build_network_two_negative_nodes(p: &mut Phreak, callback: Box<dyn ProductionCallback>) {
        let cl = p.build_or_share_classnode("Ball");
        let a0 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Any,
        ));
        let a1 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("red".to_owned()),
        ));
        let a2 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("blue".to_owned()),
        ));
        let cl = p.classes.get("Ball").unwrap();

        let n0 = p.build_or_share_join_node(p.root_node, a0.1, vec!());
        let mem = p.build_or_share_betamemory(n0);
        let n1 = p.build_or_share_negativenode(mem, a1.1, vec!(
            JoinNodeTest { // current field1 must match field1 of token in the memorynode, 1 step back
                f1: JoinTestField::Field1,
                condition_number_of_arg2: 1,
                f2: JoinTestField::Field1,
            }
        ));
        let n2 = p.build_or_share_negativenode(
            n1, a2.1,
            vec!(JoinNodeTest { // current field1 must match field1 of token in the memorynode, 2 steps back
                f1: JoinTestField::Field1,
                condition_number_of_arg2: 2,
                f2: JoinTestField::Field1,
            }),
        );

        let p1 = p.build_production(n2, callback);
    }


    // negative conjuncts
    #[test_case(
    vec ! (
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("1".to_owned(), "color".to_owned(), "red".to_owned())
    .add_attributes("1".to_owned(), "size".to_owned(), "medium".to_owned())
    .build(),
    ).as_slice() => vec ! (
    1, 0, 1
    ); "single_negative_conjunct_add_one_fact"
    )]
    #[test_case(
    vec ! (
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("1".to_owned(), "color".to_owned(), "yellow".to_owned())
    .add_attributes("1".to_owned(), "size".to_owned(), "medium".to_owned())
    .build(),
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("2".to_owned(), "color".to_owned(), "red".to_owned())
    .add_attributes("2".to_owned(), "size".to_owned(), "small".to_owned())
    .build(),
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("3".to_owned(), "color".to_owned(), "blue".to_owned())
    .add_attributes("3".to_owned(), "size".to_owned(), "large".to_owned())
    .build(),
    ).as_slice() => vec ! (
    1, 1, 0, 0, 0, 1, 1
    ); "single_negative_conjunct_add_multiple_fact"
    )]
    fn test_single_negative_conjunct_add_fact(arr: &[Facts]) -> Vec<usize> {
        let mut p = Phreak::new();
        let counter1: Arc<Cell<usize>> = Arc::new(Cell::new(0));

        helper_build_network_single_negative_node(
            &mut p,
            Box::new(CountCallback { counter: counter1.clone() }),
        );

        p.process_tree_structure();
        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);

        let mut answers = vec!();
        answers.push(counter1.get()); //before

        for fact in arr {
            p.add_facts(&[fact.clone()]);
            print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
            answers.push(counter1.get());
        }

       for fact in arr.iter().rev() {
           p.remove_facts(&[fact.clone()]);
           answers.push(counter1.get());
       }

        answers
    }


    // negative conjuncts
    #[test_case(
    vec ! (
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("1".to_owned(), "color".to_owned(), "red".to_owned())
    .add_attributes("1".to_owned(), "size".to_owned(), "medium".to_owned())
    .build(),
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("2".to_owned(), "color".to_owned(), "blue".to_owned())
    .add_attributes("2".to_owned(), "size".to_owned(), "small".to_owned())
    .build(),
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("3".to_owned(), "color".to_owned(), "green".to_owned())
    .add_attributes("3".to_owned(), "size".to_owned(), "large".to_owned())
    .build(),
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("4".to_owned(), "color".to_owned(), "blue".to_owned())
    .add_attributes("4".to_owned(), "size".to_owned(), "medium".to_owned())
    .build(),
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("5".to_owned(), "color".to_owned(), "yellow".to_owned())
    .add_attributes("5".to_owned(), "size".to_owned(), "small".to_owned())
    .build(),
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("6".to_owned(), "color".to_owned(), "red".to_owned())
    .add_attributes("6".to_owned(), "size".to_owned(), "small".to_owned())
    .build(),
    ).as_slice() => vec ! (
    0, 0, 0, 1, 1, 2, 2
    ); "two_negative_conjuncts_add_facts"
    )]
    fn test_two_negative_conjunct_add_fact(arr: &[Facts]) -> Vec<usize> {
        let mut p = Phreak::new();
        let counter1: Arc<Cell<usize>> = Arc::new(Cell::new(0));

        // This rule will activate when a ball has a color that is neither red nor blue
        helper_build_network_two_negative_nodes(
            &mut p,
            Box::new(CountCallback { counter: counter1.clone() }),
        );

        p.process_tree_structure();

        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        let mut answers = vec!();
        answers.push(counter1.get()); //before

        for fact in arr {
            println!("ADD FACT {:?}", fact);
            p.add_facts(&[fact.clone()]);
            print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
            answers.push(counter1.get());
        }

        answers
    }

    // this test case only tests removal of the fact from the join node, the code for
    // negative nodes is not covered
    #[test_case(
    vec ! (
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("1".to_owned(), "color".to_owned(), "red".to_owned())
    .add_attributes("1".to_owned(), "size".to_owned(), "medium".to_owned())
    .build(),
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("2".to_owned(), "color".to_owned(), "blue".to_owned())
    .add_attributes("2".to_owned(), "size".to_owned(), "small".to_owned())
    .build(),
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("3".to_owned(), "color".to_owned(), "green".to_owned())
    .add_attributes("3".to_owned(), "size".to_owned(), "large".to_owned())
    .build(),
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("4".to_owned(), "color".to_owned(), "blue".to_owned())
    .add_attributes("4".to_owned(), "size".to_owned(), "medium".to_owned())
    .build(),
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("5".to_owned(), "color".to_owned(), "yellow".to_owned())
    .add_attributes("5".to_owned(), "size".to_owned(), "small".to_owned())
    .build(),
    FactsBuilder::new("Ball".to_owned())
    .add_attributes("6".to_owned(), "color".to_owned(), "red".to_owned())
    .add_attributes("6".to_owned(), "size".to_owned(), "small".to_owned())
    .build(),
    ).as_slice() => vec ! (
    0, 0, 0, 1, 1, 2, 2, 2, 1, 1, 0, 0, 0
    ); "two_negative_conjuncts_add_remove_facts"
    )]
    fn test_two_negative_conjunct_add_and_remove_fact(arr: &[Facts]) -> Vec<usize> {
        let mut p = Phreak::new();
        let counter1: Arc<Cell<usize>> = Arc::new(Cell::new(0));

        // This rule will activate when a ball has a color that is neither red nor blue
        helper_build_network_two_negative_nodes(
            &mut p,
            Box::new(CountCallback { counter: counter1.clone() }),
        );

        p.process_tree_structure();

        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        let mut answers = vec!();
        answers.push(counter1.get()); //before

        for fact in arr {
            println!("ADD FACT {:?}", fact);
            p.add_facts(&[fact.clone()]);
            print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
            answers.push(counter1.get());
        }

        for fact in arr.iter().rev() {
            println!("REMOVE FACT {:?}", fact);
            p.remove_facts(&[fact.clone()]);
            print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
            answers.push(counter1.get());
        }

        answers
    }


    // Block 1 (color red) (size medium)
    // Block 2 not(color green) (size small)
    // 2 on 1

    #[test]
    fn test_two_negative_conjunct_remove_fact() {
        let mut p = Phreak::new();
        let counter1: Arc<Cell<usize>> = Arc::new(Cell::new(0));

        // This rule will activate when a ball has a color that is neither red nor blue
        helper_build_network_two_negative_nodes(
            &mut p,
            Box::new(CountCallback { counter: counter1.clone() }),
        );

        p.process_tree_structure();

        let yellow =
            FactsBuilder::new("Ball".to_owned())
                .add_attributes("1".to_owned(), "color".to_owned(), "yellow".to_owned())
                .build();
        let red = FactsBuilder::new("Ball".to_owned())
            .add_attributes("1".to_owned(), "color".to_owned(), "red".to_owned())
            .build();
        let blue = FactsBuilder::new("Ball".to_owned())
            .add_attributes("1".to_owned(), "color".to_owned(), "blue".to_owned())
            .build();

        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0); //before
        p.add_facts(&[yellow.clone()]);
        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);
        // we add 'red' to the ball
        p.add_facts(&[red.clone()]);
        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        // we add 'blue' to the ball
        p.add_facts(&[blue.clone()]);
        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        // we remove red again
        p.remove_facts(&[red.clone()]);
        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        // we remove blue, so ball is neither red nor blue
        p.remove_facts(&[blue.clone()]);
        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);
        // we remove yellow, we no longer have a colored ball
        p.remove_facts(&[yellow.clone()]);
        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
    }

    #[allow(unused_variables)]
    fn helper_build_network_two_negative_conjuncts(p: &mut Phreak, callback: Box<dyn ProductionCallback>) {
        let cl = p.build_or_share_classnode("Ball");
        let a0 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Any,
        ));
        let a1 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("red".to_owned()),
        ));
        let a2 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("blue".to_owned()),
        ));
        let cl = p.classes.get("Ball").unwrap();

        // lets create a network with the following layout
        // root -> n0 -> mem            -> ncc1             -> ncc2 -> P
        //               |                  |
        //               +-> n1 -> ncp1     +-> n2 -> ncp2
        // where ncp1 and ncc1 are paired, and ncp2 and ncc2 are paired
        //  when ncp1 has a token, the linked token in ncc1 is removed, if no token is present, ncc1 has a token

        let n0 = p.build_or_share_join_node(p.root_node, a0.1, vec!());
        let mem = p.build_or_share_betamemory(n0);

        let n1 = p.build_or_share_join_node(mem, a1.1, vec!(
            JoinNodeTest { // current field1 must match field1 of token in the memorynode, 1 step back
                f1: JoinTestField::Field1,
                condition_number_of_arg2: 1,
                f2: JoinTestField::Field1,
            }
        ));

        println!("-- SEGMENTS {:?}", p.segments);
        let ncc1_segment_id = p.split_segment_after_parentnode_if_required(1, mem);
        let n1_segment_id = p.betanodes[n1].get().get_segment();
        println!("-- SEGMENTS {:?}", p.segments);

        assert_ne!(n1_segment_id, ncc1_segment_id);
        assert_ne!(n1_segment_id, p.get_rootsegment_id());

        println!("-- ncc1_segment_id {:?}", ncc1_segment_id);
        println!("-- n1_segment_id {:?}", n1_segment_id);

        let (ncc1, ncp1) = p.node_new_ncc_pair(1, ncc1_segment_id, n1_segment_id);
        n1.prepend(ncp1, &mut p.betanodes);
        mem.append(ncc1, &mut p.betanodes);
        println!("-- SEGMENTS {:?}", p.segments);

        let n2 = p.build_or_share_join_node(
            ncc1, a2.1,
            vec!(JoinNodeTest { // current field1 must match field1 of token in the memorynode, 2 steps back
                f1: JoinTestField::Field1,
                condition_number_of_arg2: 2,
                f2: JoinTestField::Field1,
            }),
        );
        println!("-- SEGMENTS {:?}", p.segments);
        let ncc2_segment_id = p.split_segment_after_parentnode_if_required(1, ncc1);
        let n2_segment_id = p.betanodes[n2].get().get_segment();

        let (ncc2, ncp2) = p.node_new_ncc_pair(1, ncc2_segment_id, n2_segment_id);
        n2.prepend(ncp2, &mut p.betanodes);
        ncc1.append(ncc2, &mut p.betanodes);

        let p1 = p.build_production(ncc2, callback);
    }

    #[test]
    fn test_two_negative_multi_conjunct_remove_fact() {
        let mut p = Phreak::new();
        let counter1: Arc<Cell<usize>> = Arc::new(Cell::new(0));

        // This rule will activate when a ball has a color that is neither red nor blue
        helper_build_network_two_negative_conjuncts(
            &mut p,
            Box::new(CountCallback { counter: counter1.clone() }),
        );

        p.process_tree_structure();

        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0); //before
        p.add_facts(&[ball("1", "yellow")]);
        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);
        // we add 'red' to the ball
        p.add_facts(&[ball("1", "red")]);
        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        // we add 'blue' to the ball
        p.add_facts(&[ball("1", "blue")]);
        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        // we remove red again
        p.remove_facts(&[ball("1", "red")]);
        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        // we remove blue, so ball is neither red nor blue
        p.remove_facts(&[ball("1", "blue")]);
        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);
        // we remove yellow, we no longer have a colored ball
        p.remove_facts(&[ball("1", "yellow")]);
        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
    }

    #[test]
    fn test_two_negative_multi_conjunct_add_and_remove_one_fact() {
        let mut p = Phreak::new();
        let counter1: Arc<Cell<usize>> = Arc::new(Cell::new(0));

        // This rule will activate when a ball has a color that is neither red nor blue
        helper_build_network_two_negative_conjuncts(
            &mut p,
            Box::new(CountCallback { counter: counter1.clone() }),
        );

        p.process_tree_structure();

        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0); //before
        p.add_facts(&[ball("1", "red")]);
        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        p.remove_facts(&[ball("1", "red")]);
        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        p.add_facts(&[ball("1", "yellow")]);
        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);
        p.remove_facts(&[ball("1", "yellow")]);
        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        p.add_facts(&[ball("1", "blue")]);
        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        p.remove_facts(&[ball("1", "blue")]);
        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
    }

    // root node followed by negative node
    #[test]
    fn test_root_negative() {
        let mut p = Phreak::new();
        let counter1: Arc<Cell<usize>> = Arc::new(Cell::new(0));


        let cl = p.build_or_share_classnode("Ball");
        let a0 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("black".to_owned()),
        ));
        let cl = p.classes.get("Ball").unwrap();
        let n1 = p.build_or_share_negativenode(p.root_node, a0.1, vec!()); // true if no black colored ball exists
        let p1 = p.build_production(n1, Box::new(CountCallback { counter: counter1.clone() }));

        p.process_tree_structure();
        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);
        p.add_facts(&[ball("1", "red")]);
        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);
        p.add_facts(&[ball("2", "black")]);
        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        p.remove_facts(&[ball("1", "red")]);
        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        p.remove_facts(&[ball("2", "black")]);
        print_betanetwork_tree(p.root_node, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);
    }

    #[test]
    fn test_matrix_1() {
        let mut p = Phreak::new();
        let counter1: Arc<Cell<usize>> = Arc::new(Cell::new(0));

        let r = p.root_node;
        let p1 = p.build_production(r, Box::new(CountCallback { counter: counter1.clone() }));
        p.process_tree_structure();

        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);
    }

    #[test]
    fn test_matrix_2() {
        let mut p = Phreak::new();
        let counter1: Arc<Cell<usize>> = Arc::new(Cell::new(0));

        let r = p.root_node;
        let cl = p.build_or_share_classnode("Ball");
        let a0 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("black".to_owned()),
        ));
        let cl = p.classes.get("Ball").unwrap();
        let j1 = p.build_or_share_join_node(r, a0.1, vec!());

        let p1 = p.build_production(j1, Box::new(CountCallback { counter: counter1.clone() }));
        p.process_tree_structure();

        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        p.add_facts(&[ball("1", "red")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        p.add_facts(&[ball("2", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);
    }

    #[test]
    fn test_matrix_3() {
        let mut p = Phreak::new();
        let counter1: Arc<Cell<usize>> = Arc::new(Cell::new(0));

        let r = p.root_node;
        let cl = p.build_or_share_classnode("Ball");
        let a0 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("black".to_owned()),
        ));
        let a1 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("object".to_owned()),
            Memory::Value("ball".to_owned()),
        ));
        let cl = p.classes.get("Ball").unwrap();
        let j1 = p.build_or_share_join_node(r, a0.1, vec!());
        let m1 = p.build_or_share_betamemory(j1);
        let j2 = p.build_or_share_join_node(m1, a1.1, vec!(
            JoinNodeTest { f1: JoinTestField::Field1, condition_number_of_arg2: 1, f2: JoinTestField::Field1 }
        ));

        let p1 = p.build_production(j2, Box::new(CountCallback { counter: counter1.clone() }));
        p.process_tree_structure();

        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        p.add_facts(&[ball("1", "red")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        p.add_facts(&[ball("2", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);
        p.add_facts(&[ball("3", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 2);

        p.remove_facts(&[ball("2", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);
        p.remove_facts(&[ball("1", "red")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);
        p.remove_facts(&[ball("3", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
    }

    #[test]
    fn test_matrix_4() {
        let mut p = Phreak::new();
        let counter1: Arc<Cell<usize>> = Arc::new(Cell::new(0));

        let r = p.root_node;
        let cl = p.build_or_share_classnode("Ball");
        let a0 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("black".to_owned()),
        ));
        let cl = p.classes.get("Ball").unwrap();
        let n1 = p.build_or_share_negativenode(r, a0.1, vec!());
        let p1 = p.build_production(n1, Box::new(CountCallback { counter: counter1.clone() }));
        p.process_tree_structure();

        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);
        p.add_facts(&[ball("1", "red")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);
        p.add_facts(&[ball("2", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        p.add_facts(&[ball("3", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);

        p.remove_facts(&[ball("1", "red")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        p.remove_facts(&[ball("2", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        p.remove_facts(&[ball("3", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);
    }

    #[test]
    fn test_matrix_5() {
        let mut p = Phreak::new();
        let counter1: Arc<Cell<usize>> = Arc::new(Cell::new(0));

        let r = p.root_node;
        let cl = p.build_or_share_classnode("Ball");
        let a0 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("black".to_owned()),
        ));
        let a1 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("yellow".to_owned()),
        ));
        let a2 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("blue".to_owned()),
        ));
        let cl = p.classes.get("Ball").unwrap();
        let j1 = p.build_or_share_join_node(r, a0.1, vec!());
        let m1 = p.build_or_share_betamemory(j1);
        let n1 = p.build_or_share_negativenode(m1, a1.1, vec!());
        let j2 = p.build_or_share_join_node(n1, a2.1, vec!());
        let p1 = p.build_production(j2, Box::new(CountCallback { counter: counter1.clone() }));
        p.process_tree_structure();

        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        p.add_facts(&[ball("1", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        p.add_facts(&[ball("2", "yellow")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        p.add_facts(&[ball("3", "blue")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);

        p.remove_facts(&[ball("2", "yellow")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);
        p.remove_facts(&[ball("3", "blue")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        p.remove_facts(&[ball("1", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
    }

    #[test]
    fn test_matrix_6() {
        let mut p = Phreak::new();
        let counter1: Arc<Cell<usize>> = Arc::new(Cell::new(0));

        let r = p.root_node;
        let cl = p.build_or_share_classnode("Ball");
        let a0 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("black".to_owned()),
        ));
        let a1 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("yellow".to_owned()),
        ));
        let cl = p.classes.get("Ball").unwrap();
        let n1 = p.build_or_share_negativenode(r, a0.1, vec!());
        let n2 = p.build_or_share_negativenode(n1, a1.1, vec!());
        let p1 = p.build_production(n2, Box::new(CountCallback { counter: counter1.clone() }));
        p.process_tree_structure();

        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);
        p.add_facts(&[ball("1", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        p.add_facts(&[ball("2", "yellow")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);

        p.remove_facts(&[ball("2", "yellow")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);

        p.remove_facts(&[ball("1", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);

        p.add_facts(&[ball("2", "yellow")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        p.add_facts(&[ball("1", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
    }

    // helper function to initialize ncc and ncp nodes
    fn init_new_node_for_test(node_id: BetaNodeId, p: &mut Phreak) {
        let node = &p.betanodes[node_id];
        match node.get() {
            BetaNode::NcpNode(data) => {
                data.init_new_node(node_id, node, p.tokens.get_mut(), &p.betanodes, &p.alphanodes, &p.nil_fact)
            }
            BetaNode::NccNode(data) => {
                data.init_new_node(node_id, node, p.tokens.get_mut(), &p.betanodes, &p.alphanodes, &p.nil_fact)
            }
            _ => { unimplemented!() }
        }
    }

    #[test]
    fn test_matrix_7() {
        let mut p = Phreak::new();
        let counter1: Arc<Cell<usize>> = Arc::new(Cell::new(0));

        let r = p.root_node;
        let cl = p.build_or_share_classnode("Ball");
        let a0 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("black".to_owned()),
        ));
        let cl = p.classes.get("Ball").unwrap();

        let j1 = p.build_or_share_join_node(r, a0.1, vec!());
        let n1_segment_id = p.split_segment_after_parentnode_if_required(1, r);
        let j1_segment_id = p.betanodes[j1].get().get_segment();
        let (n1, ncp1) = p.node_new_ncc_pair(1, n1_segment_id, j1_segment_id);
        j1.prepend(ncp1, &mut p.betanodes);
        r.append(n1, &mut p.betanodes);

        init_new_node_for_test(ncp1, &mut p);
        init_new_node_for_test(n1, &mut p);

        let p1 = p.build_production(n1, Box::new(CountCallback { counter: counter1.clone() }));
        p.process_tree_structure();

        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);
        p.add_facts(&[ball("1", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);

        p.remove_facts(&[ball("1", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);
    }

    #[test]
    fn test_matrix_8() {
        let mut p = Phreak::new();
        let counter1: Arc<Cell<usize>> = Arc::new(Cell::new(0));

        let r = p.root_node;
        let cl = p.build_or_share_classnode("Ball");
        let a0 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("black".to_owned()),
        ));
        let a1 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("yellow".to_owned()),
        ));
        let cl = p.classes.get("Ball").unwrap();

        let j1 = p.build_or_share_join_node(r, a0.1, vec!());
        let n1_segment_id = p.split_segment_after_parentnode_if_required(1, r);
        let j1_segment_id = p.betanodes[j1].get().get_segment();
        let (n1, ncp1) = p.node_new_ncc_pair(1, n1_segment_id, j1_segment_id);
        j1.prepend(ncp1, &mut p.betanodes);
        r.append(n1, &mut p.betanodes);

        init_new_node_for_test(ncp1, &mut p);
        init_new_node_for_test(n1, &mut p);

        let j2 = p.build_or_share_join_node(n1, a1.1, vec!());
        let n2_segment_id = p.split_segment_after_parentnode_if_required(1, n1);
        let j2_segment_id = p.betanodes[j2].get().get_segment();
        let (n2, ncp2) = p.node_new_ncc_pair(1, n2_segment_id, j2_segment_id);
        j2.prepend(ncp2, &mut p.betanodes);
        n1.append(n2, &mut p.betanodes);

        init_new_node_for_test(ncp2, &mut p);
        init_new_node_for_test(n2, &mut p);

        let p1 = p.build_production(n2, Box::new(CountCallback { counter: counter1.clone() }));

        p.process_tree_structure();

        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);
        p.add_facts(&[ball("1", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        p.add_facts(&[ball("2", "yellow")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);

        p.remove_facts(&[ball("2", "yellow")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);

        p.remove_facts(&[ball("1", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);

        p.add_facts(&[ball("2", "yellow")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        p.add_facts(&[ball("1", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
    }

    #[test]
    fn test_matrix_9() {
        let mut p = Phreak::new();
        let counter1: Arc<Cell<usize>> = Arc::new(Cell::new(0));

        let r = p.root_node;
        let cl = p.build_or_share_classnode("Ball");
        let a0 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("black".to_owned()),
        ));
        let a1 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("yellow".to_owned()),
        ));
        let cl = p.classes.get("Ball").unwrap();

        let n1 = p.build_or_share_negativenode(r, a0.1, vec!());

        let j2 = p.build_or_share_join_node(n1, a1.1, vec!());
        let n2_segment_id = p.split_segment_after_parentnode_if_required(1, n1);
        let j2_segment_id = p.betanodes[j2].get().get_segment();
        let (n2, ncp2) = p.node_new_ncc_pair(1, n2_segment_id, j2_segment_id);
        j2.prepend(ncp2, &mut p.betanodes);
        n1.append(n2, &mut p.betanodes);

        init_new_node_for_test(ncp2, &mut p);
        init_new_node_for_test(n2, &mut p);

        let p1 = p.build_production(n2, Box::new(CountCallback { counter: counter1.clone() }));

        p.process_tree_structure();

        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);
        p.add_facts(&[ball("1", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        p.add_facts(&[ball("2", "yellow")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);

        p.remove_facts(&[ball("2", "yellow")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);

        p.remove_facts(&[ball("1", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);

        p.add_facts(&[ball("2", "yellow")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        p.add_facts(&[ball("1", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
    }

    #[test]
    fn test_matrix_10() {
        // not( not( color black ))

        let mut p = Phreak::new();
        let counter1: Arc<Cell<usize>> = Arc::new(Cell::new(0));

        let r = p.root_node;
        let cl = p.build_or_share_classnode("Ball");
        let a0 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("black".to_owned()),
        ));
        let cl = p.classes.get("Ball").unwrap();

        let n1 = p.build_or_share_negativenode(r, a0.1, vec!());

        let n2_segment_id = p.split_segment_after_parentnode_if_required(1, r);

        let j2_segment_id = p.betanodes[n1].get().get_segment();
        let (n2, ncp2) = p.node_new_ncc_pair(1, n2_segment_id, j2_segment_id);

        n1.prepend(ncp2, &mut p.betanodes);
        r.append(n2, &mut p.betanodes);

        init_new_node_for_test(ncp2, &mut p);
        init_new_node_for_test(n2, &mut p);

        let p1 = p.build_production(n2, Box::new(CountCallback { counter: counter1.clone() }));

        p.process_tree_structure();

        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);

        p.add_facts(&[ball("1", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);

        p.remove_facts(&[ball("1", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
    }

    #[test]
    fn test_matrix_11() {
        let mut p = Phreak::new();
        let counter1: Arc<Cell<usize>> = Arc::new(Cell::new(0));

        let r = p.root_node;
        let cl = p.build_or_share_classnode("Ball");
        let a0 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("black".to_owned()),
        ));
        let a1 = p.build_or_share_alphanode("Ball", WmeMatch::new(
            Memory::Any,
            Memory::Value("color".to_owned()),
            Memory::Value("yellow".to_owned()),
        ));
        let cl = p.classes.get("Ball").unwrap();

        let j1 = p.build_or_share_join_node(r, a0.1, vec!());
        let n1_segment_id = p.split_segment_after_parentnode_if_required(1, r);
        let j1_segment_id = p.betanodes[j1].get().get_segment();
        let (n1, ncp1) = p.node_new_ncc_pair(1, n1_segment_id, j1_segment_id);
        j1.prepend(ncp1, &mut p.betanodes);
        r.append(n1, &mut p.betanodes);

        init_new_node_for_test(ncp1, &mut p);
        init_new_node_for_test(n1, &mut p);

        let n2 = p.build_or_share_negativenode(n1, a1.1, vec!());

        let p1 = p.build_production(n2, Box::new(CountCallback { counter: counter1.clone() }));

        p.process_tree_structure();

        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);
        p.add_facts(&[ball("1", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        p.add_facts(&[ball("2", "yellow")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);

        p.remove_facts(&[ball("2", "yellow")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);

        p.remove_facts(&[ball("1", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 1);

        p.add_facts(&[ball("2", "yellow")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
        p.add_facts(&[ball("1", "black")]);
        print_betanetwork_tree(r, &p.tokens.borrow(), &p.betanodes);
        assert_eq!(counter1.get(), 0);
    }
}
