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::{BetaNodeId, ScheduledAction, SegmentId, TokenId};
use crate::betanode::{BetaNode, recursive_delete_tokens, insert_scheduled_action};
use crate::nodes::alphanode::AlphaNode;
use crate::segment::Segment;
use crate::token::*;
use crate::traits::*;

#[derive(Debug)]
pub(crate) struct NcpNodeData {
    pub(crate) segment: SegmentId,
    pub(crate) ncc: BetaNodeId,
    pub(crate) number_of_conjuncts: usize,
    pub(crate) cell: RefCell<NcpNodeCell>,
}

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

/// Mutable part of the betanode
#[derive(Debug)]
pub(crate) struct NcpNodeCell {
    pub(crate) scheduled_actions: Vec<(usize, ScheduledAction)>,
    pub(crate) tokens: VecDeque<TokenId>, // can we turn this into a hash for lookup somehow, preserving the order??
    //pub(crate) new_result_buffer: VecDeque<TokenId>, // use tokens instead
}

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

impl ScheduledExecutionCell for NcpNodeCell {
    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<NcpNodeCell> for NcpNodeData {
    fn print_debug(&self,
                   action: &ScheduledAction,
                   self_id: BetaNodeId,
                   betanodes: &TreeArena<BetaNode>,
    ) {
        println!("BetaNode::NcpNode {:?}", action);
        println!("BetaNode::NcpNode {:?}", self_id);
        println!("BetaNode::NcpNode {:?}", betanodes[self_id]);
    }

    fn get_cell(&self) -> RefMut<NcpNodeCell> {
        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>,
    ) {
        unreachable!("ncp has no linked alphamemory")
    }

    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>,
                    _pending: &[(usize, ScheduledAction)],
                    _nil_fact: &Rc<FactCore>,
    ) {
        unreachable!("ncp has no linked alphamemory")
    }

    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>,
    ) {
        if let BetaNode::NccNode(ncc) = betanodes[self.ncc].get() {
            let child_id = tokens.new_node(
                Token::new(nil_fact.clone(), self_id));
            token_id.prepend(child_id, tokens);

            println!(">->-> {}", print_token(child_id, &tokens));

            // self.insert_token(priority, child_id, self_id, celltokens, betanodes, segments, tokens, pending);

            let mut to_delete = None;
            println!("Finding split token");
            let mut split_token_id = child_id;
            // 1 more step in number_of_conjuncts, because we introduce a dummy token here
            for _ in 0..self.number_of_conjuncts+1 {
                split_token_id = tokens[split_token_id].parent().expect("invalid number_of_conjuncts");
            }
            println!("Found split token {:?}", split_token_id);

            // finding item in ncc with same split token
            for item_id in ncc.cell.borrow().tokens.iter() {
                println!("candidate in ncc: {:?}", item_id);

                let item = &mut tokens[*item_id];
                if Some(split_token_id) == item.parent() {
                    println!("item in ncc: {:?}", item.get());

                    item.get_mut().nccnode_results.insert(child_id);
                    tokens[child_id].get_mut().owner = Some(*item_id);

                    // delete all tokens descending from the item_id we found
                    to_delete = Some(*item_id);

                    break;
                }
            }

            if let Some(item_id) = to_delete {
                ncc.schedule_action(priority, ScheduledAction::DeleteChildTokens(item_id));
            } else {
                println!("put token in ncp {:?} {:?} split_id: {:?}", self, child_id, split_token_id);
                // register splitting token as tmp owner
                tokens[child_id].get_mut().owner = Some(split_token_id);
                celltokens.push_back(child_id);
            }
        }
    }

    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>,
    ) {
        println!(">->-> {}", print_token(token_id, &tokens));

        {
            debug_assert!(celltokens.contains(&token_id));
            celltokens.remove(celltokens.iter().position(|x| *x == token_id).unwrap());
        }
        // delete token
        token_id.remove(tokens);
    }

    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>,
    ) {
        if let BetaNode::NccNode(ncc) = betanodes[self.ncc].get() {
            println!(">->-> {}", print_token(token_id, &tokens));

            let mut to_delete = None;
            println!("Finding split token");
            let mut split_token_id = token_id;
            for _ in 0..self.number_of_conjuncts {
                split_token_id = tokens[split_token_id].parent().expect("invalid number_of_conjuncts");
            }
            println!("Found split token {:?}", split_token_id);

            // finding item in ncc with same split token
            for item_id in ncc.cell.borrow().tokens.iter() {
                println!("candidate in ncc: {:?}", item_id);

                let item = &mut tokens[*item_id];
                if Some(split_token_id) == item.parent() {
                    println!("item in ncc: {:?}", item.get());

                    item.get_mut().nccnode_results.insert(token_id);
                    tokens[token_id].get_mut().owner = Some(*item_id);

                    // delete all tokens descending from the item_id we found
                    to_delete = Some(*item_id);


                    break;
                }
            }

            if let Some(item_id) = to_delete {
                ncc.schedule_action(priority, ScheduledAction::DeleteChildTokens(item_id));
            } else {
                println!("put token in ncp {:?} {:?} split_id: {:?}", self, token_id, split_token_id);
                // register splitting token as tmp owner
                tokens[token_id].get_mut().owner = Some(split_token_id);
                celltokens.push_back(token_id);
            }
        }
    }

    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>,
    ) {
        println!(">->-> {}, {}", sub_id, print_token(sub_id, &tokens));

        // ncp may have dangling tokens: tokens are stored in ncc node's tokens instead of in celltokens
        if let Some(position) = celltokens.iter().position(|x| *x == sub_id) {
            celltokens.remove(position);
        }

        if let Some(owner_id) = tokens[sub_id].get().owner {
            debug_assert!(betanodes[tokens[owner_id].get().node].get().is_nccnode());
            println!("NCC handling owner_id {}", owner_id);

            let not_parent_in_delete_chain = owner_id.ancestors(tokens).position(|id| id == parent_id).is_none();
            let owner_token = tokens[owner_id].get_mut();
            owner_token.nccnode_results.remove(&sub_id);
            if owner_token.nccnode_results.is_empty() { // became a match

                println!("NCC_results became empty");

                // only if no pending delete is incoming from other branch
                if not_parent_in_delete_chain {
                    println!("activating children");

                    let bn = betanodes.get(self.ncc).unwrap().get();
                    bn.schedule_action(priority, ScheduledAction::ActivateToken(owner_id));

                }
            } else {
                println!("NCCNODE_RESULTS {:?}", &owner_token.nccnode_results);
            }
        } else {
            panic!("owner is required");
        }

        recursive_delete_tokens(parent_id, sub_id, tokens)
    }

    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>,
    ) {
        if let BetaNode::NccNode(ncc) = betanodes[self.ncc].get() {
            let mut to_delete = vec!();
            for sub_id in token_id.children(tokens) {
                if tokens[sub_id].get().node != self_id {
                    continue;
                }

                if let Some(position) = celltokens.iter().position(|id| id.eq(&sub_id)) {
                    // not yet in ncc
                    celltokens.remove(position);
                }

                debug_assert_ne!(tokens[sub_id].get().owner, None);

                if let Some(owner_id) = tokens[sub_id].get().owner {
                    debug_assert!(betanodes[tokens[owner_id].get().node].get().is_nccnode());
                    println!("NCC handling owner_id {}", owner_id);

                    to_delete.push((sub_id, owner_id));
                }
            }

            for (sub_id, owner_id) in to_delete {
                let owner_token = tokens[owner_id].get_mut();
                owner_token.nccnode_results.remove(&sub_id);

                if owner_token.nccnode_results.is_empty() { // became a match
                    println!("NCC_results became empty");

                    for child_id in owner_token.node.children(betanodes) {
                        let bn = betanodes.get(child_id).unwrap().get();
                        bn.schedule_action(priority, ScheduledAction::ParentTokenInserted(owner_id));
                    }
                } else {
                    println!("NCCNODE_RESULTS {:?}", &owner_token.nccnode_results);
                }
                sub_id.remove(tokens);
            }
        }
    }

    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 parent_id = node.parent().unwrap();
        match betanodes.get(parent_id).unwrap().get() {
            BetaNode::JoinNode(_) => {}
            BetaNode::NegativeNode(d) => {
                for token_id in d.cell.borrow().tokens.iter() {
                    self.schedule_action(priority, ScheduledAction::ParentTokenInserted(*token_id));
                }
            }
            BetaNode::NccNode(d) => {
                for token_id in d.cell.borrow().tokens.iter() {
                    if tokens[*token_id].get().nccnode_results.is_empty() {
                        self.schedule_action(priority, ScheduledAction::ParentTokenInserted(*token_id));
                    }
                }
            }
            _ => { unreachable!() }
        }
    }
}