use std::borrow::Borrow;
use std::cell::{RefCell, RefMut};
use std::collections::VecDeque;
use std::fmt::{Debug, Error, Formatter};
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, execute_join_test, insert_scheduled_action, pending_contains_token};
use crate::jointest::JoinNodeTest;
use crate::multiset::MultiSet;
use crate::nodes::alphanode::AlphaNode;
use crate::segment::Segment;
use crate::token::*;
use crate::traits::*;

#[derive(Debug)]
pub(crate) struct NegativeNodeData {
    pub(crate) segment: SegmentId,
    pub(crate) amem: AlphaNodeId,
    pub(crate) tests: Vec<JoinNodeTest>,
    pub(crate) cell: RefCell<NegativeNodeCell>,
}

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

#[derive(Debug)]
pub(crate) struct NegativeNodeCell {
    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 NegativeNodeCell {
    fn default() -> Self {
        NegativeNodeCell {
            scheduled_actions: vec![],
            tokens: VecDeque::new(),
        }
    }
}

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

    fn get_cell(&self) -> RefMut<NegativeNodeCell> {
        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>,
    ) {
        println!("--- {:?}", &celltokens);
        let mut parent_tokens_to_remove_children: Vec<TokenId> = vec![];

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

        match betanodes[parent_id].get() {
            BetaNode::RootNode(rn) => {
                for token_id in rn.root_token.iter() {
                    if pending_contains_token(token_id, pending) {
                        continue;
                    }
                    match_negative_node_fact_against_parent_token(*token_id, rc, self_id, &[], tokens, &mut parent_tokens_to_remove_children);
                }
            }
            BetaNode::MemoryNode(mn) => {
                for token_id in mn.cell.borrow().tokens.iter() {
                    if pending_contains_token(token_id, pending) {
                        continue;
                    }
                    match_negative_node_fact_against_parent_token(*token_id, rc, self_id, &self.tests, tokens, &mut parent_tokens_to_remove_children);
                }
            }
            BetaNode::NegativeNode(nc) => {
                for token_id in nc.cell.borrow().tokens.iter() {
                    if pending_contains_token(token_id, pending) {
                        continue;
                    }
                    match_negative_node_fact_against_parent_token(*token_id, rc, self_id, &self.tests, tokens, &mut parent_tokens_to_remove_children);
                }
            }
            BetaNode::NccNode(ncc) => {
                for token_id in ncc.cell.borrow().tokens.iter() {
                    if pending_contains_token(token_id, pending) {
                        continue;
                    }
                    if tokens[*token_id].get().nccnode_results.is_empty() {
                        match_negative_node_fact_against_parent_token(*token_id, rc, self_id, &self.tests, tokens, &mut parent_tokens_to_remove_children);
                    }
                }
            }
            _ => { unimplemented!() }
        };

        for parent_token_id in parent_tokens_to_remove_children {
            debug_assert_eq!(tokens[parent_token_id].get().node, parent_id);

            // child token belongs to this node

            let mut to_detach = vec!();
            // we have a match against token, we need to retract all child-tokens in our memory
            for child_token_id in parent_token_id.children(&tokens) {
                let child_token = tokens.get(child_token_id).unwrap().get();
                println!("cd {:?} -- {:?}", child_token_id, child_token);

                if child_token.node == self_id {
                    println!("CHILD in NEGATIVENODE {}", print_token(child_token_id, tokens));

                    for subchild_token in child_token_id.children(&tokens) {
                        let subchild = tokens.get(subchild_token).unwrap().get();
                        let bn = betanodes.get(subchild.node).unwrap().get();
                        println!("bn {:?}", bn);
                        bn.schedule_action(priority, ScheduledAction::DeleteTokenChain(child_token_id, subchild_token));
                    }

                    debug_assert!(celltokens.contains(&child_token_id));
                    celltokens.remove(celltokens.iter().position(
                        |x| *x == child_token_id
                    ).unwrap());
                    to_detach.push(child_token_id);
                }
            }

            for child_token_id in &to_detach {
                child_token_id.detach(tokens);
            }
        }
    }

    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>,
    ) {
        let parent_id = betanodes[self_id].parent().unwrap();

        match betanodes[parent_id].get() {
            BetaNode::RootNode(rn) => {
                let token_id = rn.root_token.unwrap();
                let njr = NJR::new(rc.clone());
                println!("TESTING {:?} for {:?}", tokens[token_id].get(), &njr);

                if tokens[token_id].get().negativenode_join_results
                    .get(&self_id)
                    .map_or_else(|| false, |map| map.contains(&njr))
                {
                    println!("Token {:?} in NJR", &njr);
                    tokens[token_id].get_mut().negativenode_join_results
                        .entry(self_id)
                        .and_modify(|map| { map.remove(&njr); });

                    println!("Token {:?}", tokens[token_id].get());
                    if tokens[token_id].get().negativenode_join_results
                        .get(&self_id)
                        .map_or_else(|| true, |map| map.is_empty())
                    {
                        // it became empty, so we match!
                        println!("empty");

                        let child_id = tokens.new_node(Token::new(nil_fact.clone(), self_id));
                        token_id.prepend(child_id, tokens);
                        celltokens.push_front(child_id);

                        let bn = betanodes.get(self_id).unwrap().get();
                        segments[bn.get_segment()].get().set_modified(self_id, true);

                        for child_node in self_id.children(betanodes) {
                            betanodes.get(child_node).unwrap().get().schedule_action(priority, ScheduledAction::ParentTokenInserted(child_id));
                        }
                    }
                }
            }
            BetaNode::MemoryNode(mn) => {
                for token_id in mn.cell.borrow().tokens.iter() {
                    if pending_contains_token(token_id, pending) {
                        continue;
                    }
                    println!("Token found: {}", print_token(*token_id, tokens));
                    let njr = NJR::new(rc.clone()); // dummy entry for searching in hashed set
                    println!("Matching NJR {:?}", &njr);
                    if tokens[*token_id].get().negativenode_join_results
                        .get(&self_id)
                        .map_or_else(|| false, |map| map.contains(&njr))
                    {
                        println!("Token {:?} in NJR", &njr);
                        tokens[*token_id].get_mut().negativenode_join_results
                            .entry(self_id)
                            .and_modify(|map| { map.remove(&njr); });
                        if tokens[*token_id].get().negativenode_join_results
                            .get(&self_id)
                            .map_or_else(|| true, |map| map.is_empty())
                        // empty or none-existing
                        {
                            // it became empty, so we match!

                            let child_id = tokens.new_node(Token::new(rc.clone(), self_id));
                            token_id.prepend(child_id, tokens);
                            celltokens.push_front(child_id);

                            let bn = betanodes.get(self_id).unwrap().get();
                            segments[bn.get_segment()].get().set_modified(self_id, true);

                            for child_node in self_id.children(betanodes) {
                                betanodes.get(child_node).unwrap().get().schedule_action(priority, ScheduledAction::ParentTokenInserted(child_id));
                            }
                        }
                    }
                }
            }
            BetaNode::NegativeNode(nn) => {
                for token_id in nn.cell.borrow().tokens.iter() {
                    if pending_contains_token(token_id, pending) {
                        continue;
                    }
                    println!("Token found: {}", print_token(*token_id, tokens));
                    let njr = NJR::new(rc.clone()); // dummy entry for searching in hashed set
                    println!("Matching NJR {:?}", &njr);
                    if tokens[*token_id].get().negativenode_join_results
                        .get(&self_id)
                        .map_or_else(|| false, |map| map.contains(&njr))
                    {
                        println!("Token {:?} in NJR", &njr);
                        tokens[*token_id].get_mut().negativenode_join_results
                            .entry(self_id)
                            .and_modify(|map| { map.remove(&njr); });
                        if tokens[*token_id].get().negativenode_join_results
                            .get(&self_id)
                            .map_or_else(|| true, |map| map.is_empty())
                        {
                            // it became empty, so we match!

                            let child_id = tokens.new_node(Token::new(rc.clone(), self_id));
                            token_id.prepend(child_id, tokens);
                            celltokens.push_front(child_id);

                            let bn = betanodes.get(self_id).unwrap().get();
                            segments[bn.get_segment()].get().set_modified(self_id, true);

                            for child_node in self_id.children(betanodes) {
                                betanodes.get(child_node).unwrap().get().schedule_action(priority, ScheduledAction::ParentTokenInserted(child_id));
                            }
                        }
                    }
                }
            }
            BetaNode::NccNode(ncc) => {
                for token_id in ncc.cell.borrow().tokens.iter() {
                    if pending_contains_token(token_id, pending) {
                        continue;
                    }
                    if !tokens[*token_id].get().nccnode_results.is_empty() {
                        continue;
                    }
                    println!("Token found: {}", print_token(*token_id, tokens));
                    let njr = NJR::new(rc.clone()); // dummy entry for searching in hashed set
                    println!("Matching NJR {:?}", &njr);
                    if tokens[*token_id].get().negativenode_join_results
                        .get(&self_id)
                        .map_or_else(|| false, |map| map.contains(&njr))
                    {
                        println!("Token {:?} in NJR", &njr);
                        tokens[*token_id].get_mut().negativenode_join_results
                            .entry(self_id)
                            .and_modify(|map| { map.remove(&njr); });
                        if tokens[*token_id].get().negativenode_join_results
                            .get(&self_id)
                            .map_or_else(|| true, |map| map.is_empty())
                        // empty or none-existing
                        {
                            // it became empty, so we match!

                            let child_id = tokens.new_node(Token::new(rc.clone(), self_id));
                            token_id.prepend(child_id, tokens);
                            celltokens.push_front(child_id);

                            let bn = betanodes.get(self_id).unwrap().get();
                            segments[bn.get_segment()].get().set_modified(self_id, true);

                            for child_node in self_id.children(betanodes) {
                                betanodes.get(child_node).unwrap().get().schedule_action(priority, ScheduledAction::ParentTokenInserted(child_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 alphanode = &alphanodes[self.amem];
        let cell = alphanode.cell.borrow();

        let mut matched = false;

        for item in cell.items.iter() {
            let mut success = true;

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


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

                matched = true;
                // register match on token

                let token = tokens[token_id].get_mut();
                token.negativenode_join_results
                    .entry(self_id)
                    .or_insert(MultiSet::<NJR>::new())
                    .insert(NJR::new(item.fact.clone()));
            }
        }

        // if no match: propagate token with dummy fact
        if !matched {
            println!("NOT MATCHED - PROPAGATE");

            // register in local memory
            let child_token_id = tokens.new_node(
                Token::new(
                    nil_fact.clone(),
                    self_id)
            );
            token_id.prepend(child_token_id, tokens);

            celltokens.push_front(child_token_id);
            debug_assert_eq!(tokens[child_token_id].get().node, self_id);

            // and schedule inserts into children
            for child in self_id.children(betanodes) {
                let bn = betanodes.get(child).unwrap().get();
                bn.schedule_action(priority, ScheduledAction::ParentTokenInserted(child_token_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>,
    ) {
        // unlink from current node
        let pos = celltokens.iter()
            .position(|element| *element == token_id)
            .expect("Token was expected in this list");
        celltokens.remove(pos);

        // and propagate delete
        for child in token_id.children(tokens) {
            let child_token = tokens.get(child).unwrap().get();
            let bn = betanodes.get(child_token.node).unwrap().get();
            bn.schedule_action(priority, ScheduledAction::DeleteTokenChain(token_id, child));
        }
    }

    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>,
    ) {
        // unlink from current node
        let pos = celltokens.iter()
            .position(|element| *element == sub_id)
            .expect("Token was expected in this list");
        celltokens.remove(pos);

        // and propagate delete
        for child in sub_id.children(tokens) {
            let child_token = tokens.get(child).unwrap().get();
            let bn = betanodes.get(child_token.node).unwrap().get();
            bn.schedule_action(priority, ScheduledAction::DeleteTokenChain(parent_id, child));
        }
    }

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

    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) => {
                let root_token = cd.root_token.borrow().unwrap();
                cellref.schedule_action(
                    priority,
                    ScheduledAction::ParentTokenInserted(root_token),
                );
                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, print_token(*token_id, tokens));
                }
            }
            BetaNode::JoinNode(_) => { unreachable!() }
            BetaNode::NegativeNode(_) => {}
            BetaNode::NccNode(cd) => {
                for token_id in cd.cell.borrow().tokens.iter() {
                    cellref.schedule_action(priority, ScheduledAction::ParentTokenInserted(*token_id));
                    println!("ADD AFTER INIT {:?} {}", token_id, print_token(*token_id, tokens));
                }
            }
            BetaNode::NcpNode(_) => { unimplemented!() }
            BetaNode::ProductionNode(_) => { unimplemented!() }
            BetaNode::QueryNode() => { unreachable!() }
        }
    }
}

/// Negative Join Result
///
/// There exists a fact such that combined with the owning token it would generate a match, but
/// instead we are interested in the opposite
#[derive(Hash)]
pub(crate) struct NJR {
    fact: Rc<FactCore>,
}

// equality if both are the same object by value
impl PartialEq for NJR {
    fn eq(&self, other: &Self) -> bool {
        self.fact.eq(&other.fact)
    }
}

impl Eq for NJR {}

impl NJR {
    pub(crate) fn new(fact: Rc<FactCore>) -> NJR {
        NJR { fact }
    }
}

impl Debug for NJR {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
        write!(f, "NJR( {:?} )", self.fact)
    }
}

fn match_negative_node_fact_against_parent_token(
    parent_token_id: TokenId, rc: &Rc<FactCore>,
    betanode_id: BetaNodeId, tests: &[JoinNodeTest],
    tokens: &mut TreeArena<Token>, tokens_to_remove_childrem: &mut Vec<TokenId>,
) {
    // check if token is a match for the current fact
    let mut success = true;

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

    if success {
        println!("MATCH {:?} and added fact( {:?} )", print_token(parent_token_id, &tokens), &rc);

        // if no matches yet, retract token
        if tokens[parent_token_id].get().negativenode_join_results
            .get(&betanode_id)
            .map_or_else(|| true, |map| map.is_empty())
        {
            tokens_to_remove_childrem.push(parent_token_id);
        }

        // always register match on token
        tokens.get_mut(parent_token_id).unwrap().get_mut()
            .negativenode_join_results
            .entry(betanode_id)
            .or_insert(MultiSet::<NJR>::new())
            .insert(NJR::new(rc.clone()));
    }
}
