package com.opendoorlogistics.core.scripts.formulae.rules;
import java.util.ArrayList;
import java.util.List;
import com.opendoorlogistics.core.tables.ColumnValueProcessor;
import com.opendoorlogistics.core.utils.strings.StandardisedCache;
public class RuleNode {
final public int ruleNb;
final public int colIndx;
final public Object value;
private final StandardisedCache standardisedCache;
ArrayList<RuleNode> children;
RuleNode(int ruleNb, int colIndx, Object value, boolean isRootNode) {
this.ruleNb = ruleNb;
this.colIndx = colIndx;
this.value = value;
this.standardisedCache = isRootNode ? new StandardisedCache():null;
}
boolean isRootNode(){
return standardisedCache!=null;
}
// @Override
// public String toString(){
// StringBuilder b = new StringBuilder();
// toString(b);
// return b.toString();
// }
// private void toString(StringBuilder b){
// if(colIndx>=0){
// b.append(Strings.repeat("\t", colIndx) + selectorFieldNames[colIndx] +" = " + value + (colIndx == nc-1? " (rule #" + ruleNb + ")":"") + System.lineSeparator());
// }
// if(children!=null){
// for(RuleNode n:children){
// n.toString(b);
// }
// }
// }
public static RuleNode buildTree(List<List<Object>> selectorsMatrix) {
return buildTree(selectorsMatrix, false);
}
/**
* Build the tree given the input matrix of rules x select values
* @param selectorsMatrix
* @return
*/
public static RuleNode buildTree(List<List<Object>> selectorsMatrix, boolean buildNodePerRule) {
StandardisedCache standardisedCache = new StandardisedCache();
int n = selectorsMatrix.size();
RuleNode baseNode= new RuleNode(-1,-1,null, true);
baseNode.children = new ArrayList<RuleNode>();
for (int rule = 0; rule < n; rule++) {
RuleNode parent = baseNode;
int nc = selectorsMatrix.get(rule).size();
for (int col = 0; col < nc; col++) {
// get selector value and ensure zero length is treated as null
Object s =selectorsMatrix.get(rule).get(col);
if (s != null && s.toString().length() == 0) {
s = null;
}
// try to find the pre-existing node with this value
int nchildren = parent.children != null ? parent.children.size() : 0;
RuleNode nextParent = null;
for (int k = 0; k < nchildren; k++) {
RuleNode child = parent.children.get(k);
if (ColumnValueProcessor.isEqual(s, child.value, standardisedCache)) {
nextParent = child;
break;
}
}
// make a node if none exists, marking the rule number
boolean isLastColumn= col==nc-1;
if (nextParent == null || (isLastColumn && buildNodePerRule)) {
nextParent = new RuleNode(rule, col, s, false);
// add to parent
if (parent.children == null) {
parent.children = new ArrayList<RuleNode>(3);
}
parent.children.add(nextParent);
}
parent = nextParent;
}
}
return baseNode;
}
/**
* Find matching rule number or -1 if not found
* @param values
* @return
*/
public int findRuleNumber(Object[] values,RuleFilter ruleFilter, Object filterData) {
if(!isRootNode()){
throw new RuntimeException("Only call this method on the root node of the tree");
}
// Find all matching rules
int[] lowestRuleNumber = new int[1];
lowestRuleNumber[0] = Integer.MAX_VALUE;
// Find the lowest matching rule number
recurseMatch(values, this, lowestRuleNumber,ruleFilter,filterData,standardisedCache);
int ruleNb =lowestRuleNumber[0] ;
if(ruleNb == Integer.MAX_VALUE){
return -1;
}
return ruleNb;
}
public int findRuleNumber(Object[] values) {
return findRuleNumber(values, null, null);
}
public static interface RuleFilter{
boolean filter(Object filterData,Object[] values, int ruleNb);
}
private static void recurseMatch(Object[] values, RuleNode node, int[] lowestRuleNumber, RuleFilter ruleFilter, Object filterData,StandardisedCache standardisedCache) {
// record the rule if we're on the last one and its lower than the
// current lowest
int nc = values.length;
if (node.colIndx == nc - 1) {
if(ruleFilter==null || ruleFilter.filter(filterData, values, node.ruleNb)){
lowestRuleNumber[0] = Math.min(lowestRuleNumber[0], node.ruleNb);
}
} else {
// recurse to the next level if we find a null selector (which
// matches all) or we have a match
if (node.children != null) {
int nchildren = node.children.size();
for (int i = 0; i < nchildren; i++) {
RuleNode childNode = node.children.get(i);
boolean recurse = childNode.value == null;
if (!recurse) {
recurse = ColumnValueProcessor.isEqual(values[childNode.colIndx], childNode.value, standardisedCache);
}
if (recurse) {
recurseMatch(values, childNode, lowestRuleNumber, ruleFilter, filterData,standardisedCache);
}
}
}
}
}
}