//! The nodes module contains the Alpha and Beta network nodes, and holds the bulk of
//! the process logic.

use std::borrow::Borrow;
use std::cell::RefCell;
use std::collections::VecDeque;
use std::fmt::Debug;
use std::rc::Rc;

use generational_arena::Arena;
use generational_indextree::Arena as TreeArena;

use phreak_facts::FactCore;
use phreak_rules::ProductionCallback;

use crate::actions::*;
use crate::jointest::*;
use crate::nodes::alphanode::AlphaNode;
use crate::nodes::joinnode::{JoinNodeCell, JoinNodeData};
use crate::nodes::memorynode::{MemoryNodeCell, MemoryNodeData};
use crate::nodes::nccnode::{NccNodeCell, NccNodeData};
use crate::nodes::ncpnode::{NcpNodeCell, NcpNodeData};
use crate::nodes::negativenode::{NegativeNodeCell, NegativeNodeData};
use crate::nodes::productionnode::{ProductionNodeCell, ProductionNodeData};
use crate::nodes::rootnode::RootNodeData;
use crate::segment::Segment;
use crate::token::*;
use crate::traits::*;

/// A stack action is used for stack based evaluation of the network nodes
/// To prevent recursion, we push nodes on the stack and process them in order
#[derive(Debug)]
pub(crate) enum StackAction {
    ProcessClassNode(ClassNodeId),
    ProcessAlphaNode(AlphaNodeId),
    ProcessBetaNode(BetaNodeId),
    ProcessSegment(SegmentId),
}

#[derive(Debug)]
pub(crate) enum BetaNode {
    /// Single root of the node tree
    RootNode(RootNodeData),
    /// Storage for matches
    MemoryNode(MemoryNodeData),
    /// Reusable test node
    JoinNode(JoinNodeData),
    /// Test and store absence
    NegativeNode(NegativeNodeData),
    /// Negate the result of a subnet
    NccNode(NccNodeData),
    /// Collect the result of a subnet
    NcpNode(NcpNodeData),
    /// Handle the final activation
    ProductionNode(ProductionNodeData),
    /// Backward chaining node
    QueryNode(),
}

// constructors
impl BetaNode {
    pub(crate) fn new_rootnode(segment: SegmentId) -> BetaNode {
        let r = BetaNode::RootNode(RootNodeData {
            segment,
            root_token: None,
        });
        r
    }

    pub(crate) fn new_memorynode(segment: SegmentId) -> BetaNode {
        BetaNode::MemoryNode(MemoryNodeData {
            segment,
            cell: RefCell::new(MemoryNodeCell::default()),
        })
    }

    pub(crate) fn new_joinnode(amem: AlphaNodeId, tests: Vec<JoinNodeTest>, segment: SegmentId) -> BetaNode {
        BetaNode::JoinNode(JoinNodeData {
            amem,
            tests,
            segment,
            cell: RefCell::new(JoinNodeCell::default()),
        })
    }

    pub(crate) fn new_negativenode(amem: AlphaNodeId, tests: Vec<JoinNodeTest>, segment: SegmentId) -> BetaNode {
        BetaNode::NegativeNode(NegativeNodeData {
            segment,
            amem,
            tests,
            cell: RefCell::new(NegativeNodeCell::default()),
        })
    }

    pub(crate) fn new_nccnode(ncp: BetaNodeId, segment: SegmentId) -> BetaNode {
        BetaNode::NccNode(NccNodeData {
            segment,
            ncp,
            cell: RefCell::new(NccNodeCell { scheduled_actions: vec![], tokens: Default::default() }),
        })
    }

    pub(crate) fn new_nccpartnernode(ncc: BetaNodeId, segment: SegmentId, number_of_conjuncts: usize) -> BetaNode {
        BetaNode::NcpNode(NcpNodeData {
            segment,
            ncc,
            number_of_conjuncts,
            cell: RefCell::new(NcpNodeCell { scheduled_actions: vec![], tokens: Default::default() }),
        })
    }

    pub(crate) fn new_productionnode(segment: SegmentId, callback: Box<dyn ProductionCallback>) -> BetaNode {
        BetaNode::ProductionNode(ProductionNodeData {
            segment,
            callback,
            cell: RefCell::new(ProductionNodeCell {
                scheduled_actions: vec![],
                tokens: VecDeque::new(),
            }),
        })
    }

    pub(crate) fn new_querynode() -> BetaNode {
        BetaNode::QueryNode()
    }
}

impl BetaNode {
    pub(crate) fn set_root_token(&mut self, root_token: TokenId) {
        match self {
            BetaNode::RootNode(rn) => {
                debug_assert_eq!(rn.root_token, None);
                rn.root_token = Some(root_token);
            }
            _ => { panic!("invalid use of set_root_token"); }
        }
    }
}

impl BetaNode {
    pub(crate) fn get_segment(&self) -> SegmentId {
        match self {
            BetaNode::RootNode(data) => { data.segment }
            BetaNode::MemoryNode(data) => { data.segment }
            BetaNode::JoinNode(data) => { data.segment }
            BetaNode::NegativeNode(data) => { data.segment }
            BetaNode::NccNode(data) => { data.segment }
            BetaNode::NcpNode(data) => { data.segment }
            BetaNode::ProductionNode(data) => { data.segment }
            _ => {
                unimplemented!()
            }
        }
    }

    pub(crate) fn set_segment(&mut self, segment_id: SegmentId) {
        match self {
            BetaNode::RootNode(data) => { data.segment = segment_id; }
            BetaNode::MemoryNode(data) => { data.segment = segment_id; }
            BetaNode::JoinNode(data) => { data.segment = segment_id; }
            BetaNode::NegativeNode(data) => { data.segment = segment_id; }
            BetaNode::NccNode(data) => { data.segment = segment_id; }
            BetaNode::NcpNode(data) => { data.segment = segment_id; }
            BetaNode::ProductionNode(data) => { data.segment = segment_id; }
            _ => {
                unimplemented!()
            }
        }
    }

    pub(crate) fn is_rootnode(&self) -> bool {
        match self {
            BetaNode::RootNode(_) => true,
            _ => false,
        }
    }

    pub(crate) fn is_memorynode(&self) -> bool {
        match self {
            BetaNode::MemoryNode(_) => true,
            _ => false,
        }
    }

    pub(crate) fn is_joinnode(&self) -> bool {
        match self {
            BetaNode::JoinNode(_) => true,
            _ => false,
        }
    }

    pub(crate) fn is_negativenode(&self) -> bool {
        match self {
            BetaNode::NegativeNode(_) => true,
            _ => false,
        }
    }

    pub(crate) fn is_nccnode(&self) -> bool {
        match self {
            BetaNode::NccNode(_) => true,
            _ => false,
        }
    }

    pub(crate) fn is_nccpartnernode(&self) -> bool {
        match self {
            BetaNode::NcpNode(_) => true,
            _ => false,
        }
    }

    pub(crate) fn is_productionnode(&self) -> bool {
        match self {
            BetaNode::ProductionNode(_) => true,
            _ => false,
        }
    }

    pub(crate) fn is_querynode(&self) -> bool {
        match self {
            BetaNode::QueryNode() => true,
            _ => false,
        }
    }
}

impl BetaNode {
    pub(crate) fn schedule_action(&self, priority: usize, action: ScheduledAction) {
        println!("Scheduled: {:?}", &action);
        match self {
            BetaNode::RootNode(_) => { panic!("Cant push action to rootnode, {:?} {:?}", &self, action); }
            BetaNode::MemoryNode(d) => d.schedule_action(priority, action),
            BetaNode::JoinNode(d) => d.schedule_action(priority, action),
            BetaNode::NegativeNode(d) => d.schedule_action(priority, action),
            BetaNode::ProductionNode(d) => d.schedule_action(priority, action),
            BetaNode::NccNode(d) => d.schedule_action(priority, action),
            BetaNode::NcpNode(d) => d.schedule_action(priority, action),
            _ => { unimplemented!() }
        }
    }

    // new node has been created with a parent and possibly an amem. These might contain data that
    // we need to schedule
    pub(crate) fn init_new_node(self_id: BetaNodeId, betanodes: &TreeArena<BetaNode>, alphanodes: &Arena<AlphaNode>, tokens: &mut TreeArena<Token>, nil_fact: &Rc<FactCore>,) {
        let node = betanodes.get(self_id).unwrap();
        println!("init_new_node {:?}", node.get());
        match node.get() {
            BetaNode::RootNode(_) => { /* ignore */ }
            BetaNode::QueryNode() => { unimplemented!() }
            BetaNode::MemoryNode(d) =>
                d.init_new_node(self_id, node, tokens, betanodes, alphanodes, nil_fact),
            BetaNode::JoinNode(d) =>
                d.init_new_node(self_id, node, tokens, betanodes, alphanodes, nil_fact),
            BetaNode::NegativeNode(d) =>
                d.init_new_node(self_id, node, tokens, betanodes, alphanodes, nil_fact),
            BetaNode::NccNode(d) =>
                d.init_new_node(self_id, node, tokens, betanodes, alphanodes, nil_fact),
            BetaNode::NcpNode(d) =>
                d.init_new_node(self_id, node, tokens, betanodes, alphanodes, nil_fact),
            BetaNode::ProductionNode(d) =>
                d.init_new_node(self_id, node, tokens, betanodes, alphanodes, nil_fact),
        }
    }

    pub(crate) fn process_actions(&self,
                                  self_id: BetaNodeId,
                                  alphanodes: &Arena<AlphaNode>,
                                  betanodes: &TreeArena<BetaNode>,
                                  segments: &TreeArena<Segment>,
                                  tokens: &mut TreeArena<Token>,
                                  nil_fact: &Rc<FactCore>
    ) {
        println!("Process actions in node {:?} {:?}", self_id, self);

        match self {
            BetaNode::RootNode(_) => { /* RootNode doesn't have scheduled actions */ }
            BetaNode::JoinNode(d) =>
                scheduled_action(d, self_id, alphanodes, betanodes, segments, tokens, nil_fact),
            BetaNode::MemoryNode(d) =>
                scheduled_action(d, self_id, alphanodes, betanodes, segments, tokens, nil_fact),
            BetaNode::NegativeNode(d) =>
                scheduled_action(d, self_id, alphanodes, betanodes, segments, tokens, nil_fact),
            BetaNode::ProductionNode(d) =>
                scheduled_action(d, self_id, alphanodes, betanodes, segments, tokens, nil_fact),
            BetaNode::NccNode(d) =>
                scheduled_action(d, self_id, alphanodes, betanodes, segments, tokens, nil_fact),
            BetaNode::NcpNode(d) =>
                scheduled_action(d, self_id, alphanodes, betanodes, segments, tokens, nil_fact),

            _ => { unimplemented!() }
        }
    }
}

fn scheduled_action<T: Actionable<C>, C: ScheduledExecutionCell>(data: &T,
                                                                 self_id: BetaNodeId,
                                                                 alphanodes: &Arena<AlphaNode>,
                                                                 betanodes: &TreeArena<BetaNode>,
                                                                 segments: &TreeArena<Segment>,
                                                                 tokens: &mut TreeArena<Token>,
                                                                 nil_fact: &Rc<FactCore>,
) {
    let cellref = &mut *data.get_cell();

    let (iter, celltokens) = cellref.get_fields_mut();
    let mut position = 0;
    while position < iter.len() {
        let (priority, action) = &iter[position];
        let pending = &iter[position + 1..];


        // // create an ignore list for tokens we will not match because an action is pending
        // // for this match and we don't want to match twice
        // let mut ignore = HashSet::new();
        // let (iter, celltokens) = cellref.get_fields_mut();
        // for (priority, action) in iter.clone() {
        //     match action {
        //         ScheduledAction::ParentTokenInserted(token_id) => {
        //             ignore.insert(token_id.clone());
        //         }
        //         _ => {}
        //     }
        // }
        //
        // for (priority, action) in iter {
        data.print_debug(action, self_id, betanodes);
        match action {
            ScheduledAction::FactInserted(rc) => {
                data.fact_inserted(*priority, rc, self_id, celltokens, betanodes, segments, tokens, pending, nil_fact)
            }
            ScheduledAction::FactRemoved(rc) => {
                data.fact_removed(*priority, rc, self_id, celltokens, betanodes, tokens, segments, pending, nil_fact)
            }
            ScheduledAction::ParentTokenInserted(token) => {
                data.parent_token_inserted(*priority, *token, self_id, celltokens, betanodes, segments, tokens, alphanodes, pending, nil_fact);
            }
            ScheduledAction::ParentTokenRemoved(token_id) => {
                data.parent_token_removed(*priority, *token_id, self_id, celltokens, betanodes, segments, tokens, pending, nil_fact);
            }
            ScheduledAction::InsertToken(token_id) => {
                data.insert_token(*priority, *token_id, self_id, celltokens, betanodes, segments, tokens, pending, nil_fact);
            }
            ScheduledAction::DeleteTokenChain(parent_id, sub_id) => {
                data.delete_token_chain(*priority, *parent_id, *sub_id, celltokens, betanodes, tokens, pending, nil_fact);
            }
            ScheduledAction::DeleteChildTokens(token_id) => {
                data.delete_child_tokens(*priority, *token_id, self_id, celltokens, tokens, betanodes, pending, nil_fact);
            }
            ScheduledAction::ActivateToken(token_id) => {
                data.activate_token(*priority, *token_id, self_id, celltokens, tokens, betanodes, pending, nil_fact);
            }
        }
        // }

        position = position + 1;
    }

    // we have handled these
    cellref.clear_scheduled_actions();

    segments[betanodes[self_id].get().get_segment()].get().set_modified(self_id, false);
}

// Helper to see if we can skip processing this node
// Phreak optimizes by delaying processing untill it can result in a rule activation
fn we_can_skip_proccing_this_node(segment_id: SegmentId, segments: &TreeArena<Segment>) -> bool {
    let segment = segments[segment_id].get();
    !*segment.needs_processing.borrow()
}

// This method executes the testing logic for a single pair of fields
pub(crate) fn execute_join_test(token: &TokenId, test: &JoinNodeTest, fact: &FactCore, tokens: &mut TreeArena<Token>) -> bool {
    println!("testing if Fact{{ {},{},{} }} matches Token {:?} by rule {:?}",
             fact.0.to_string(), fact.1.to_string(), fact.2.to_string(),
             print_token(*token, &tokens), test);
    let mut current = *token;

    let n: &FactCore =
        if test.condition_number_of_arg2 == 0 {
            fact
        } else {
            let mut i = test.condition_number_of_arg2 - 1;
            while i > 0 {
                current = tokens[current].parent().unwrap();
                i = i - 1;
            }
            tokens[current].get().fact.borrow()
        };

    let left = match test.f1 {
        JoinTestField::Field1 => &fact.0,
        JoinTestField::Field2 => &fact.1,
        JoinTestField::Field3 => &fact.2,
    };
    let right = match test.f2 {
        JoinTestField::Field1 => &n.0,
        JoinTestField::Field2 => &n.1,
        JoinTestField::Field3 => &n.2,
    };

    println!("Testing {:?} == {:?}", left.to_string(), right.to_string());
    left.to_string() == right.to_string()
}


// create a child token and notify the owner betanode to add it to the memory
pub(crate) fn create_child_token_front(
    priority: usize,
    parent_id: TokenId,
    fact: Rc<FactCore>,
    owner: BetaNodeId,
    tokens: &mut TreeArena<Token>,
    betanodes: &TreeArena<BetaNode>,
    segments: &TreeArena<Segment>,
) -> TokenId {
    let new = tokens.new_node(Token::new(fact, owner));
    parent_id.prepend(new, tokens);
    let bn = betanodes.get(owner).unwrap().get();
    bn.schedule_action(priority, ScheduledAction::InsertToken(new));
    segments[bn.get_segment()].get().set_modified(owner, true);
    new
}

// create a child token and notify the owner betanode to add it to the memory
pub(crate) fn create_child_token_back(
    priority: usize,
    parent_id: TokenId,
    fact: Rc<FactCore>,
    owner: BetaNodeId,
    tokens: &mut TreeArena<Token>,
    betanodes: &TreeArena<BetaNode>,
    segments: &TreeArena<Segment>,
) -> TokenId {
    let new = tokens.new_node(Token::new(fact, owner));
    parent_id.append(new, tokens);
    let bn = betanodes.get(owner).unwrap().get();
    bn.schedule_action(priority, ScheduledAction::InsertToken(new));
    segments[bn.get_segment()].get().set_modified(owner, true);
    new
}


pub(crate) fn recursive_delete_tokens(parent_id: TokenId, sub_id: TokenId, tokens: &mut TreeArena<Token>) -> () {
    println!("recursive_delete_tokens {:?} {:?}", parent_id, sub_id);
    // recursive delete upto parent or upto first shared node in tokentree
    let mut current_id = sub_id;
    while current_id != parent_id {
        let current = tokens.get(current_id).unwrap();
        if current.first_child().is_some() || !current.get().nccnode_results.is_empty() {
            break; // will delete from other terminal node
        }
        let next_id = current.parent().unwrap();
        current_id.remove(tokens);
        current_id = next_id;
    }
    let current = tokens.get(parent_id).unwrap();
    if current.first_child().is_none() {
        parent_id.remove(tokens);
    }
}

pub (crate) fn insert_scheduled_action(priority: usize, action: ScheduledAction, list: &mut Vec<(usize, ScheduledAction)>) {
    // scheduled actions is an ordered listed on priority
    // we assume that any new insertion will be near the end of the list, so we don't use
    // the standard iterator methods which start from the front
    if list.len() > 0 {
        let mut cursor = list.len();
        while cursor > 0 {
            if list[cursor-1].0 <= priority {
                break;
            }
            cursor -= 1;
        }
        list.insert(cursor, (priority, action));
    } else {
        list.push((priority, action))
    }
}


#[cfg(test)]
mod tests {
    use compact::CString;

    use test_case::test_case;

    use super::*;

    // Test the fields inside the current fact, ignoring the token
    //
    // Tests all permutations of fields inside the provided fact
    #[test_case(JoinTestField::Field1, JoinTestField::Field1 => true)]
    #[test_case(JoinTestField::Field2, JoinTestField::Field2 => true)]
    #[test_case(JoinTestField::Field3, JoinTestField::Field3 => true)]
    #[test_case(JoinTestField::Field1, JoinTestField::Field2 => false)]
    #[test_case(JoinTestField::Field1, JoinTestField::Field3 => false)]
    #[test_case(JoinTestField::Field2, JoinTestField::Field1 => false)]
    #[test_case(JoinTestField::Field2, JoinTestField::Field3 => false)]
    #[test_case(JoinTestField::Field3, JoinTestField::Field1 => false)]
    #[test_case(JoinTestField::Field3, JoinTestField::Field2 => false)]
    fn test_joins_self_check(f1: JoinTestField, f2: JoinTestField) -> bool {
        let mut tokens = TreeArena::new();
        let mut betanodes = TreeArena::new();
        let mut segments = TreeArena::new();
        let root_segment_id = segments.new_node(Segment::new());
        let root_node = betanodes.new_node(BetaNode::new_rootnode(root_segment_id));
        let root_fact = Rc::new(FactCore(CString::new(), CString::new(), CString::new()));
        let root = tokens.new_node(Token::new(root_fact, root_node));

        execute_join_test(&root,
                          &JoinNodeTest { f1, condition_number_of_arg2: 0, f2 },
                          &FactCore(
                              CString::from("1".to_owned()),
                              CString::from("2".to_owned()),
                              CString::from("3".to_owned()),
                          ), &mut tokens)
    }

    // Test a token against its parent token
    #[test_case(JoinTestField::Field1, JoinTestField::Field1 => (true, false))]
    #[test_case(JoinTestField::Field2, JoinTestField::Field2 => (true, false))]
    #[test_case(JoinTestField::Field3, JoinTestField::Field3 => (true, false))]
    #[test_case(JoinTestField::Field1, JoinTestField::Field2 => (false, false))]
    #[test_case(JoinTestField::Field1, JoinTestField::Field3 => (false, false))]
    #[test_case(JoinTestField::Field2, JoinTestField::Field1 => (false, false))]
    #[test_case(JoinTestField::Field2, JoinTestField::Field3 => (false, false))]
    #[test_case(JoinTestField::Field3, JoinTestField::Field1 => (false, false))]
    #[test_case(JoinTestField::Field3, JoinTestField::Field2 => (false, false))]
    fn test_joins_token_1_level(f1: JoinTestField, f2: JoinTestField) -> (bool, bool) {
        let mut tokens = TreeArena::new();
        let mut betanodes = TreeArena::new();
        let mut segments = TreeArena::new();
        let root_segment_id = segments.new_node(Segment::new());
        let root_node = betanodes.new_node(BetaNode::new_rootnode(root_segment_id));
        let root_fact = Rc::new(FactCore(CString::new(), CString::new(), CString::new()));
        let root = tokens.new_node(Token::new(root_fact, root_node));
        let child_fact1 = Rc::new(FactCore(CString::from("1".to_owned()),
                                           CString::from("2".to_owned()),
                                           CString::from("3".to_owned())));
        let child1_node = betanodes.new_node(BetaNode::new_memorynode(root_segment_id));
        let child1 = tokens.new_node(Token::new(child_fact1, child1_node));
        root.append(child1, &mut tokens);

        let child_fact2 = Rc::new(FactCore(CString::from("not1".to_owned()),
                                           CString::from("not2".to_owned()),
                                           CString::from("not3".to_owned())));
        let child2_node = betanodes.new_node(BetaNode::new_memorynode(root_segment_id));
        let child2 = tokens.new_node(Token::new(child_fact2, child2_node));
        root.prepend(child2, &mut tokens);

        // current field "1" must match field1 at the top of the child1 token "1"
        (execute_join_test(&child1,
                           &JoinNodeTest { f1, condition_number_of_arg2: 1, f2 },
                           &FactCore(
                               CString::from("1".to_owned()),
                               CString::from("2".to_owned()),
                               CString::from("3".to_owned()),
                           ), &mut tokens),
         // current field "1" must not match field1 at the top of the child2 token "not1"
         execute_join_test(&child2,
                           &JoinNodeTest { f1, condition_number_of_arg2: 1, f2 },
                           &FactCore(
                               CString::from("1".to_owned()),
                               CString::from("2".to_owned()),
                               CString::from("3".to_owned()),
                           ), &mut tokens))
    }
}

// returns true if the list of pending scheduled actions contains an entry that cause interference
// between facts and tokens -> a fact inserted/removed from the alphanode while at the same time a
// new token matching that fact is inserted/removed from the betanode parent.
pub(crate) fn pending_contains_token(token_id: &TokenId, pending: &[(usize, ScheduledAction)]) -> bool {
    let b = pending.iter()
        .map(|(prio, action)| {
            match action {
                ScheduledAction::FactInserted(_) => None,
                ScheduledAction::FactRemoved(_) => None,
                ScheduledAction::ParentTokenInserted(t) => Some(t),
                ScheduledAction::ParentTokenRemoved(t) => Some(t),
                ScheduledAction::InsertToken(t) => Some(t),
                ScheduledAction::DeleteTokenChain(_, t) => Some(t),
                ScheduledAction::DeleteChildTokens(t) => Some(t),
                ScheduledAction::ActivateToken(t) => Some(t),
            }
        })
        .find(|res| res == &Some(token_id))
        .is_some();
    if b { println!("We found a pending action for token {:?} in {:?}", token_id, pending); }
    b
}