use std::borrow::Borrow;
use std::cell::{RefCell, RefMut};
use std::collections::VecDeque;
use std::rc::Rc;

use generational_arena::Arena;
use generational_indextree::{Arena as TreeArena, Node};

use phreak_facts::FactCore;

use crate::actions::{AlphaNodeId, BetaNodeId, ScheduledAction, SegmentId, TokenId};
use crate::betanode::{BetaNode, create_child_token_front, execute_join_test, insert_scheduled_action, pending_contains_token};
use crate::jointest::JoinNodeTest;
use crate::nodes::alphanode::AlphaNode;
use crate::segment::Segment;
use crate::token::*;
use crate::traits::*;

/// In a join-node we join the individual facts captured by an AlphaMemory.
///
/// Facts are joined by comparing fields according to predefined Join conditions,
/// stored in a JoinNodeTest. All JoinNodeTests must succeed in order to pass.
#[derive(Debug)]
pub(crate) struct JoinNodeData {
    pub(crate) segment: SegmentId,
    pub(crate) amem: AlphaNodeId,
    pub(crate) tests: Vec<JoinNodeTest>,
    pub(crate) cell: RefCell<JoinNodeCell>,
}

impl ScheduledExecution for JoinNodeData {
    fn schedule_action(&self, priority: usize, action: ScheduledAction) {
        self.cell.borrow_mut().schedule_action(priority, action);
    }
}

#[derive(Debug)]
pub(crate) struct JoinNodeCell {
    pub(crate) scheduled_actions: Vec<(usize, ScheduledAction)>,
    tokens: VecDeque<TokenId>, // dummy
}

impl Default for JoinNodeCell {
    fn default() -> Self {
        JoinNodeCell {
            scheduled_actions: vec![],
            tokens: VecDeque::new(),
        }
    }
}

impl ScheduledExecutionCell for JoinNodeCell {
    fn schedule_action(&mut self, priority: usize, action: ScheduledAction) {
        insert_scheduled_action(priority, action, &mut self.scheduled_actions);
    }

    fn clear_scheduled_actions(&mut self) {
        self.scheduled_actions.clear();
    }

    fn get_fields_mut(&mut self) -> (&[(usize, ScheduledAction)], &mut VecDeque<TokenId>) {
        (&self.scheduled_actions, &mut self.tokens)
    }
}

impl Actionable<JoinNodeCell> for JoinNodeData {
    fn print_debug(&self,
                   action: &ScheduledAction,
                   self_id: BetaNodeId,
                   betanodes: &TreeArena<BetaNode>,
    ) {
        println!("BetaNode::JoinNode {:?}", action);
        println!("BetaNode::JoinNode {:?}", self_id);
        println!("BetaNode::JoinNode {:?}", betanodes[self_id]);
    }

    fn get_cell(&self) -> RefMut<JoinNodeCell> {
        self.cell.borrow_mut()
    }

    fn fact_inserted(&self,
                     priority: usize,
                     rc: &Rc<FactCore>,
                     self_id: BetaNodeId,
                     celltokens: &mut VecDeque<TokenId>,
                     betanodes: &TreeArena<BetaNode>,
                     segments: &TreeArena<Segment>,
                     tokens: &mut TreeArena<Token>,
                     pending: &[(usize, ScheduledAction)],
                     _nil_fact: &Rc<FactCore>,
    ) {
        let fact = &**rc;

        let parent = betanodes[betanodes[self_id].parent().unwrap()].get();

        match parent {
            BetaNode::RootNode(rn) => {
                println!("--- {:?}", tokens[rn.root_token.unwrap()]);
                for child in self_id.children(betanodes) {
                    // root token always matches the current fact
                    create_child_token_front(
                        priority,
                        rn.root_token.unwrap(), rc.clone(), child,
                        tokens, betanodes, segments,
                    );
                }
            }
            BetaNode::MemoryNode(mn) => {
                println!("--- {:?}", &mn.cell.borrow().tokens);
                for token_id in mn.cell.borrow().tokens.iter() {
                    if pending_contains_token(token_id, pending) {
                        continue;
                    }
                    // check if token is a match for the current fact
                    let mut success = true;

                    println!("test token {} against ({}, {}, {})", print_token(*token_id, &tokens), &*fact.0, &*fact.1, &*fact.2);
                    for test in &self.tests {
                        success &= execute_join_test(token_id, test, fact, tokens);
                        if !success { break; }
                    }

                    if success {
                        println!("MATCH {:?}", print_token(*token_id, &tokens));

                        // join fact and token to create new token, and propagate
                        for child in self_id.children(betanodes) {
                            create_child_token_front(
                                priority,
                                *token_id, rc.clone(), child,
                                tokens, betanodes, segments,
                            );
                        }
                    }
                }
            }
            BetaNode::NccNode(ncc) => {
                println!("--- {:?}", &ncc.cell.borrow().tokens);
                for token_id in ncc.cell.borrow().tokens.iter() {
                    if pending_contains_token(token_id, pending) || !tokens[*token_id].get().nccnode_results.is_empty() {
                        continue;
                    }
                    // check if token is a match for the current fact
                    let mut success = true;

                    println!("test token {} against ({}, {}, {})", print_token(*token_id, &tokens), &*fact.0, &*fact.1, &*fact.2);
                    for test in &self.tests {
                        success &= execute_join_test(token_id, test, fact, tokens);
                        if !success { break; }
                    }

                    if success {
                        println!("MATCH {:?}", print_token(*token_id, &tokens));

                        // join fact and token to create new token, and propagate
                        for child in self_id.children(betanodes) {
                            create_child_token_front(
                                priority,
                                *token_id, rc.clone(), child,
                                tokens, betanodes, segments,
                            );
                        }
                    }
                }
            }
            BetaNode::NegativeNode(data) => {
                println!("--- {:?}", &data.cell.borrow().tokens);
                for token_id in data.cell.borrow().tokens.iter() {
                    if pending_contains_token(token_id, pending) {
                        continue;
                    }
                    // check if token is a match for the current fact
                    let mut success = true;

                    println!("test token {} against ({}, {}, {})", print_token(*token_id, &tokens), &*fact.0, &*fact.1, &*fact.2);
                    for test in &self.tests {
                        success &= execute_join_test(token_id, test, fact, tokens);
                        if !success { break; }
                    }

                    if success {
                        println!("MATCH {:?}", print_token(*token_id, &tokens));

                        // join fact and token to create new token, and propagate
                        for child in self_id.children(betanodes) {
                            create_child_token_front(
                                priority,
                                *token_id, rc.clone(), child,
                                tokens, betanodes, segments,
                            );
                        }
                    }
                }
            }
            _ => { unreachable!() }
        };
    }

    fn fact_removed(&self,
                    priority: usize,
                    rc: &Rc<FactCore>,
                    self_id: BetaNodeId,
                    _celltokens: &mut VecDeque<TokenId>,
                    betanodes: &TreeArena<BetaNode>,
                    tokens: &mut TreeArena<Token>,
                    segments: &TreeArena<Segment>,
                    _ignore: &[(usize, ScheduledAction)],
                    _nil_fact: &Rc<FactCore>,
    ) {
        let parent = betanodes[betanodes[self_id].parent().unwrap()].get();

        match parent {
            BetaNode::RootNode(rn) => {
                // match fact against child tokens
                let mut found = None;
                for child_token_id in rn.root_token.unwrap().children(tokens) {
                    let child_token: &Token = tokens[child_token_id].get();
                    println!("trying matches in {}, {:?} matches {:?}", child_token_id, &rc, &child_token);

                    if child_token.fact.eq(rc) {
                        println!("token found");
                        found = Some(child_token_id);
                        break; // only one fact per parent_token
                    }
                }

                if let Some(child_token_id) = found {
                    println!("propagate delete child token from root");
                    betanodes[tokens[child_token_id].get().node].borrow().get()
                        .schedule_action(priority, ScheduledAction::DeleteTokenChain(child_token_id, child_token_id));
                }
            }
            BetaNode::MemoryNode(mn) => {
                // match fact against tokens
                for parent_token_id in mn.cell.borrow().tokens.iter() {
                    println!("matching parent {}", parent_token_id);

                    let mut found = None;
                    for child_token_id in parent_token_id.children(&tokens) {
                        let token: &Token = tokens[child_token_id].get();
                        println!("trying matches in {}, {:?} matches {:?}", child_token_id, &rc, &token);

                        if token.fact.eq(rc) {
                            found = Some(child_token_id);
                            break; // only one fact per parent_token
                        }
                    }

                    if let Some(child_token_id) = found {
                        println!("propagate delete child token from joinnode");
                        betanodes[tokens[child_token_id].get().node].borrow().get()
                            .schedule_action(priority, ScheduledAction::DeleteTokenChain(child_token_id, child_token_id));
                    }
                }
            }
            BetaNode::NccNode(ncc) => {
                // match fact against tokens
                for parent_token in ncc.cell.borrow().tokens.iter() {
                    if !tokens[*parent_token].get().nccnode_results.is_empty() {
                        continue;
                    }
                    println!("matching parent {}", parent_token);

                    let mut found = None;
                    for child_token_id in parent_token.children(&tokens) {
                        let token: &Token = tokens[child_token_id].get();
                        println!("trying matches in {}, {:?} matches {:?}", child_token_id, &rc, &token);

                        if token.fact.eq(rc) {
                            found = Some(child_token_id);
                            break; // only one fact per parent_token
                        }
                    }

                    if let Some(child_token_id) = found {
                        println!("propagate delete child token from ncc");
                        // fact is removed, so child_token must be deleted
                        betanodes[tokens[child_token_id].get().node].borrow().get()
                            .schedule_action(priority, ScheduledAction::DeleteTokenChain(child_token_id, child_token_id));
                    }
                }
            }
            BetaNode::NegativeNode(data) => {
                // match fact against tokens
                for parent_token_id in data.cell.borrow().tokens.iter() {
                    println!("matching parent {}", parent_token_id);

                    let mut found = None;
                    for child_token_id in parent_token_id.children(&tokens) {
                        let token: &Token = tokens[child_token_id].get();
                        println!("trying matches in {}, {:?} matches {:?}", child_token_id, &rc, &token);

                        if token.fact.eq(rc) {
                            found = Some(child_token_id);
                            break; // only one fact per parent_token
                        }
                    }

                    if let Some(child_token_id) = found {
                        println!("propagate delete child token from joinnode");
                        betanodes[tokens[child_token_id].get().node].borrow().get()
                            .schedule_action(priority, ScheduledAction::DeleteTokenChain(child_token_id, child_token_id));
                    }
                }
            }
            _ => { unreachable!(); }
        }
    }


    fn parent_token_inserted(&self,
                             priority: usize,
                             token_id: TokenId,
                             self_id: BetaNodeId,
                             _celltokens: &mut VecDeque<TokenId>,
                             betanodes: &TreeArena<BetaNode>,
                             segments: &TreeArena<Segment>,
                             tokens: &mut TreeArena<Token>,
                             alphanodes: &Arena<AlphaNode>,
                             _pending: &[(usize, ScheduledAction)],
                             _nil_fact: &Rc<FactCore>,
    ) {
        // search alphanode for match
        let amem = &alphanodes[self.amem];
        let cell = amem.cell.borrow();
        for fact in cell.items.iter() {
            let mut success = true;

            let f: &FactCore = &*fact.fact.borrow();
            println!("test token {} against ({}, {}, {})", print_token(token_id, &tokens), &*f.0, &*f.1, &*f.2);
            for test in &self.tests {
                success &= execute_join_test(&token_id, test, fact.fact.borrow(), tokens);
                if !success { break; }
            }

            if success {
                println!("MATCH {:?}", print_token(token_id, &tokens));

                // join fact and token to create new token, and propagate
                for child in self_id.children(betanodes) {
                    create_child_token_front(
                        priority,
                        token_id, fact.fact.clone(), child,
                        tokens, betanodes, segments,
                    );
                }
            }
        }
    }

    fn parent_token_removed(&self,
                            _priority: usize,
                            _token_id: TokenId,
                            _self_id: BetaNodeId,
                            _celltokens: &mut VecDeque<TokenId>,
                            _betanodes: &TreeArena<BetaNode>,
                            _segments: &TreeArena<Segment>,
                            _tokens: &mut TreeArena<Token>,
                            _pending: &[(usize, ScheduledAction)],
                            _nil_fact: &Rc<FactCore>,
    ) {
        unimplemented!()
    }

    fn insert_token(&self,
                    _priority: usize,
                    _token_id: TokenId,
                    _self_id: BetaNodeId,
                    _celltokens: &mut VecDeque<TokenId>,
                    _betanodes: &TreeArena<BetaNode>,
                    _segments: &TreeArena<Segment>,
                    _tokens: &mut TreeArena<Token>,
                    _pending: &[(usize, ScheduledAction)],
                    _nil_fact: &Rc<FactCore>,
    ) {
        unimplemented!()
    }

    fn delete_token_chain(&self,
                          _priority: usize,
                          _parent_id: TokenId,
                          _sub_id: TokenId,
                          _celltokens: &mut VecDeque<TokenId>,
                          _betanodes: &TreeArena<BetaNode>,
                          _tokens: &mut TreeArena<Token>,
                          _pending: &[(usize, ScheduledAction)],
                          _nil_fact: &Rc<FactCore>,
    ) {
        unimplemented!()
    }

    fn delete_child_tokens(&self,
                           _priority: usize,
                           _token_id: TokenId,
                           _self_id: BetaNodeId,
                           _celltokens: &mut VecDeque<TokenId>,
                           _tokens: &mut TreeArena<Token>,
                           _betanodes: &TreeArena<BetaNode>,
                           _pending: &[(usize, ScheduledAction)],
                           _nil_fact: &Rc<FactCore>,
    ) {
        unimplemented!("ncc only message")
    }

    fn init_new_node(&self,
                     _self_id: BetaNodeId,
                     node: &Node<BetaNode>,
                     tokens: &mut TreeArena<Token>,
                     betanodes: &TreeArena<BetaNode>,
                     alphanodes: &Arena<AlphaNode>,
                     _nil_fact: &Rc<FactCore>,
    ) {
        let priority = 0;
        let amem = alphanodes.get(self.amem).unwrap();

        let cellref = &mut *self.cell.borrow_mut();

        for fact in amem.cell.borrow().items.iter() {
            cellref.schedule_action(priority, ScheduledAction::FactInserted(fact.fact.clone()));
            println!("ADD AFTER INIT {:?}", &fact.fact);
        }

        let parent_id = node.parent().unwrap();
        match betanodes.get(parent_id).unwrap().get() {
            BetaNode::RootNode(cd) => {
                cellref.schedule_action(
                    priority, ScheduledAction::ParentTokenInserted(cd.root_token.borrow().unwrap().clone()),
                );
                println!("ADD AFTER INIT {:?}", &cd.root_token);
            }
            BetaNode::MemoryNode(cd) => {
                for token_id in cd.cell.borrow().tokens.iter() {
                    cellref.schedule_action(priority, ScheduledAction::ParentTokenInserted(*token_id));
                    println!("ADD AFTER INIT {:?}", token_id);
                }
            }
            BetaNode::JoinNode(_) => { unreachable!("JoinNodes must be separated by at least a memorynode") }
            BetaNode::NegativeNode(d) => {
                for token_id in d.cell.borrow().tokens.iter() {
                    if tokens[*token_id].get().negativenode_join_results
                        .get(&_self_id)
                        .map_or_else(|| true, |map| map.is_empty())
                    {
                        cellref.schedule_action(priority, ScheduledAction::ParentTokenInserted(*token_id));
                        println!("ADD AFTER INIT {:?}", token_id);
                    }
                }
            }
            BetaNode::NccNode(d) => {
                for token_id in d.cell.borrow().tokens.iter() {
                    cellref.schedule_action(priority, ScheduledAction::ParentTokenInserted(*token_id));
                    println!("ADD AFTER INIT {:?}", token_id);
                }
            }
            BetaNode::NcpNode(_) => { unreachable!("NcpNode is a terminal node and can't be parent") }
            BetaNode::ProductionNode(_) => { unreachable!("ProductionNode is a terminal node and can't be parent") }
            BetaNode::QueryNode() => { unreachable!() }
        }
    }
}
