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

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

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

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

#[derive(Debug)]
pub(crate) struct ProductionNodeData {
    pub(crate)  segment: SegmentId,
    pub(crate)  callback: Box<dyn ProductionCallback>,
    pub(crate)  cell: RefCell<ProductionNodeCell>,
}

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

#[derive(Debug)]
pub(crate) struct ProductionNodeCell {
    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??
}

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

impl ScheduledExecutionCell for ProductionNodeCell {
    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)
    }
}


#[derive(Debug)]
pub struct PrintCallBack {}

impl ProductionCallback for PrintCallBack {
    fn activate(&self) {
        println!("1");
    }

    fn deactivate(&self) {
        println!("1");
    }
}


impl Default for Box<PrintCallBack> {
    fn default() -> Self {
        Box::new(PrintCallBack {})
    }
}

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

    fn get_cell(&self) -> RefMut<ProductionNodeCell> {
        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!()
    }

    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!()
    }

    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>,
    ) {
        let child = tokens.new_node(
            Token::new(nil_fact.clone(), self_id));
        token_id.prepend(child, tokens);
        celltokens.push_back(child);

        println!("Production ACTIVATED {:?}", self);
        println!(">>>> {}", print_token(token_id, &tokens));
        self.callback.activate();
    }

    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!("Production DEACTIVATED {:?}", self);
        println!(">>>> {}", print_token(token_id, &tokens));
        self.callback.deactivate();

        println!("{}", print_token(token_id, tokens));

        let mut tmp = Vec::new();
        for child_id in token_id.children(&tokens) {
            if let Some(child_pos) = celltokens.iter().position(|x| *x == child_id) {
                celltokens.remove(child_pos);
                tmp.push(child_id);
            }
        }
        let _: Vec<()> = tmp.iter().map(|child_id| child_id.remove(tokens)).collect();
    }

    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>,
    ) {
        celltokens.push_back(token_id);

        println!("Production ACTIVATED {:?}", self);
        println!(">>>> {}", print_token(token_id, &tokens));
        self.callback.activate();
    }

    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!("Production DEACTIVATED {:?}", self);
        println!(">>>> {}", print_token(sub_id, &tokens));
        self.callback.deactivate();

        println!("{}", print_token(sub_id, tokens));

        debug_assert!(celltokens.contains(&sub_id));
        celltokens.remove(celltokens.iter().position(|x| *x == sub_id).unwrap());

        // 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 production
            }
            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_some() || !current.get().nccnode_results.is_empty() {
            return; // will delete from other production
        }
        parent_id.remove(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>,
    ) {
        let mut to_delete = vec!();
        for sub_id in token_id.children(tokens) {
            if tokens[sub_id].get().node != self_id {
                continue;
            }

            println!("Production DEACTIVATED {:?}", self);
            println!(">>>> {}", print_token(sub_id, &tokens));
            self.callback.deactivate();

            debug_assert!(celltokens.contains(&sub_id));
            celltokens.remove(celltokens.iter().position(|id| id.eq(&sub_id)).unwrap());
            to_delete.push(sub_id);
        }

        for sub_id in to_delete {
            sub_id.remove(tokens);
        }

        let current = tokens.get(token_id).unwrap();
        if current.first_child().is_some() || !current.get().nccnode_results.is_empty() {
            return; // will delete from other production
        }
        token_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 cellref = &mut *self.cell.borrow_mut();
        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.unwrap()));
            }
            BetaNode::MemoryNode(cd) => {
                for token_id in cd.cell.borrow().tokens.iter() {
                    cellref.schedule_action(priority, ScheduledAction::ParentTokenInserted(*token_id));
                }
            }
            BetaNode::JoinNode(d) => {
                match betanodes[betanodes[parent_id].parent().unwrap()].get() {
                    BetaNode::RootNode(_) => {}
                    BetaNode::MemoryNode(mn) => {
                        for token in mn.cell.borrow().tokens.iter() {
                            for item in alphanodes[d.amem].cell.borrow().items.iter() {
                                let mut success = true;
                                for test in d.tests.iter() {
                                    success &= execute_join_test(token, test, item.fact.borrow(), tokens);
                                    if !success { break; }
                                }

                                if success {
                                    cellref.schedule_action(
                                        priority,
                                        ScheduledAction::InsertToken(
                                            tokens.new_node(
                                                Token::new(item.fact.clone(), self_id)
                                            )
                                        ),
                                    );
                                }
                            }
                        }
                    }
                    BetaNode::JoinNode(_) => {}
                    BetaNode::NegativeNode(_) => {}
                    BetaNode::NccNode(_) => {}
                    BetaNode::NcpNode(_) => {}
                    BetaNode::ProductionNode(_) => {}
                    BetaNode::QueryNode() => {}
                }
            }
            BetaNode::NegativeNode(cd) => {
                for token_id in cd.cell.borrow().tokens.iter() {
                    cellref.schedule_action(priority, ScheduledAction::ParentTokenInserted(*token_id));
                }
            }
            BetaNode::NccNode(cd) => {
                for token_id in cd.cell.borrow().tokens.iter() {
                    cellref.schedule_action(priority, ScheduledAction::ParentTokenInserted(*token_id));
                }
            }
            BetaNode::NcpNode(_) => { unreachable!() }
            BetaNode::ProductionNode(_) => { unreachable!() }
            BetaNode::QueryNode() => { unreachable!() }
        }
    }
}