//! Tokens are used to store the intermediate state of a matching rule. A rule is processed
//! left-to-right, and each token represents a combination of facts resulting in a positive
//! result upto that point.

use std::borrow::Borrow;
use std::collections::{HashMap, HashSet};
use std::fmt::{Debug, Error, Formatter};
use std::rc::Rc;

use generational_indextree::Arena as TreeArena;

use phreak_facts::FactCore;

use crate::actions::{BetaNodeId, TokenId};
use crate::betanode::BetaNode;
use crate::multiset::MultiSet;
use crate::nodes::negativenode::NJR;

pub(crate) struct Token {
    pub(crate) fact: Rc<FactCore>,
    pub(crate) node: BetaNodeId,
    pub(crate) negativenode_join_results: HashMap<BetaNodeId, MultiSet<NJR>>,
    pub(crate) nccnode_results: HashSet<TokenId>,
    pub(crate) owner: Option<TokenId>,
}

impl Token {
    pub(crate) fn new(fact: Rc<FactCore>, node: BetaNodeId) -> Self {
        Token { fact, node, negativenode_join_results: HashMap::new(), nccnode_results: HashSet::new(), owner: None }
    }
}

impl Debug for Token {
    fn fmt(&self, f: &mut Formatter<'_>) -> Result<(), Error> {
        let fact: &FactCore = self.fact.borrow();
        write!(f, "TokenData{{ <{}, {}, {}> -- node: {:?} owner: {:?} -- {:?} {:?} }}",
               fact.0.to_string(), fact.1.to_string(), fact.2.to_string(),
               self.node,
               self.owner,
               self.negativenode_join_results,
               self.nccnode_results
        )
    }
}

/// Recursive print function for debuging
pub(crate) fn print_token(token_id: TokenId, tokens: &TreeArena<Token>) -> String {
    let mut data = String::new();

    let mut current = tokens.get(token_id);
    let mut glue = "Token( ";
    while let Some(token) = current {
        let facts: &FactCore = token.get().fact.borrow();
        data.push_str(glue);
        data.push_str("<");
        data.push_str(&*facts.0);
        data.push_str(",");
        data.push_str(&*facts.1);
        data.push_str(",");
        data.push_str(&*facts.2);
        data.push_str(">");

        glue = ", ";
        current = token.parent().map(|x| tokens.get(x).unwrap());
    }
    data.push_str(" )");
    data
}

/// Print the node tree in mutiline graphical view
/// Includes the number of tokens in the node, and for NCC nodes includes the number of 'active'
/// tokens vs all tokens. Active tokens have no negative match in the NCP, and must be propagated.
pub(crate) fn print_betanetwork_tree(rootnode_id: BetaNodeId, tokens: &TreeArena<Token>, betanodes: &TreeArena<BetaNode>) {
    println!("{:?}", tokens);
    recursive_print_betanetwork("", "   ", rootnode_id, tokens, betanodes)
}

fn recursive_print_betanetwork(current_prefix: &str, nextline_prefix: &str, node_id: BetaNodeId, tokens: &TreeArena<Token>, betanodes: &TreeArena<BetaNode>) {
    print!("{}", current_prefix);

    match betanodes[node_id].get() {
        BetaNode::RootNode(_) => { println!(" +-RN <1>"); }
        BetaNode::MemoryNode(d) => { println!("MN <{}>", d.cell.borrow().tokens.len()); }
        BetaNode::JoinNode(_) => { println!("JN"); }
        BetaNode::NegativeNode(d) => { println!("NEG <{}>", d.cell.borrow().tokens.len()); }
        BetaNode::NccNode(d) => {
            let count = d.cell.borrow().tokens.iter()
                .filter(|id| tokens[**id].get().nccnode_results.is_empty())
                .count();
            println!("NCC <{}/{}>", count, d.cell.borrow().tokens.len());
        }
        BetaNode::NcpNode(d) => { println!("NCP <{}>", d.cell.borrow().tokens.len()); }
        BetaNode::ProductionNode(d) => { println!("P <{}>", d.cell.borrow().tokens.len()); }
        BetaNode::QueryNode() => { println!("QN"); }
    }

    let cur_prefix = String::from(nextline_prefix) + " +-";
    let mut nxt_prefix = String::from(nextline_prefix) + " | ";

    for child_id in node_id.children(betanodes) {
        if betanodes[child_id].next_sibling() == None {
            nxt_prefix = String::from(nextline_prefix) + "   ";
        }
        recursive_print_betanetwork(cur_prefix.as_str(), nxt_prefix.as_str(), child_id, tokens, betanodes);
    }
}
