public class FactListValue<T> extends FactValue{
	
private List<FactValue> listValue;
private FactValue defaultValue;
	
	public FactListValue(List<FactValue> i) {
		setListValue(i);
	}

	public void setListValue(List<FactValue> listValue) {
		this.listValue = listValue;
	}
	
	public void addFactValueToListValue(FactValue fv)
	{
		this.listValue.add(fv);
	}
	

	@Override
	public FactValueType getType() {
		return FactValueType.LIST;
	}
	
	
	@SuppressWarnings("hiding")
	@Override
	public <T> void setDefaultValue(T defaultValue) {
		
		this.defaultValue =  (FactValue)defaultValue;
		
	}

	@SuppressWarnings("unchecked")
	@Override
	public List<FactValue> getValue() {

		return listValue;
	}

	@SuppressWarnings("unchecked")
	@Override
	public FactValue getDefaultValue() {

		return defaultValue;
	}
	
//	
//	public boolean compare(FactValue factValue2) {
//		boolean equal = false;
//		FactValueType factValue2Type = factValue2.getType();
//		if(factValue2Type.equals(FactValueType.LIST))
//		{
//			int factValue2ListSize = ((FactListValue)factValue2).getListValue().size();
//			int trueCount = 0;
//			for(int i = 0; i < factValue2ListSize; i++)
//			{
//				if(this.listValue.get(i).getType().equals(((FactValue) ((FactListValue)factValue2).getListValue().get(i)).getType())
//						&&this.listValue.get(i).equals(factValue2))
//				{
//					trueCount++;
//				}
//			}
//			if(factValue2ListSize == this.listValue.size() && trueCount == factValue2ListSize)
//			{
//				equal = true;
//			}
//			
//		}
//		else
//		{
//			System.out.println("FactListValue comparison is failed");
//		}
//		
//		return equal;
//	}

	

	

} public abstract class FactValue {


	public static FactDefiStringValue parseDefiString(String s) {
		return new FactDefiStringValue(s);
	}
	
	public static FactStringValue parse(String s) {
		return new FactStringValue(s);
	}
	
	public static FactIntegerValue parse(int i) {
		return new FactIntegerValue(i);
	}
	
	public static FactDateValue parse(LocalDate cal) {
		return new FactDateValue(cal);
	}
	
	public static FactDoubleValue parse(double d) {
		return new FactDoubleValue(d);
	}
	
	@SuppressWarnings("rawtypes")
	public static FactBooleanValue<?> parse(boolean b)
	{
		return new FactBooleanValue(b);
	}
	
	@SuppressWarnings({ "unchecked", "rawtypes" })
	public static FactListValue<?> parse(List<FactValue> l)
	{
		return new FactListValue(l);
	}
	
	public static FactURLValue parseURL(String url)
	{
		return new FactURLValue(url);
	}
	
	public static FactHashValue parseHash(String hash)
	{
		return new FactHashValue(hash);
	}
	
	public static FactUUIDValue parseUUID(String uuid)
	{
		return new FactUUIDValue(uuid);
	}

	
	public abstract FactValueType getType() ;
	public abstract <T> void setDefaultValue(T str);
	public abstract <T> T getValue();
	public abstract <T> T getDefaultValue();
	
} public enum FactValueType {
	DEFI_STRING, TEXT, STRING, INTEGER, DOUBLE, NUMBER, DATE, DECIMAL, BOOLEAN, LIST, RULE, RULE_SET, OBJECT, UNKNOWN, URL, HASH, UUID, NULL;

} public class Assessment {
	private Node goalNode;
	private int goalNodeIndex; // the goal rule index in ruleList of ruleSet
	/*
	 * each instance of this object has a variable of ruleToBeAsked due to the following reasons;
	 * 1. a user will be allowed to do assessment on multiple investigation points at the same time;
	 * 2. a user will be allowed to do an assessment within another assessment. 
	 */
	private Node nodeToBeAsked;
	
	/*
	 * this variable is to track next node to be asked within 'IterateLine' type node.
	 * However, better way needs to be found.
	 */
	private Node auxNodeToBeAsked;
	
	public Assessment(NodeSet ns, String goalNodeName)
	{
		goalNode = ns.getNodeMap().get(goalNodeName);
		goalNodeIndex = ns.findNodeIndex(goalNodeName);
		nodeToBeAsked = null;
		auxNodeToBeAsked = null; 
	}
	
	public void setAssessment(NodeSet nodeSet, String goalNodeName)
	{
		goalNode = nodeSet.getNodeMap().get(goalNodeName);
		goalNodeIndex = nodeSet.findNodeIndex(goalNodeName);
		nodeToBeAsked = null; 
	}

	public Node getGoalNode()
	{
		return this.goalNode;
	}
	public int getGoalNodeIndex()
	{
		return this.goalNodeIndex;
	}
	
	public void setNodeToBeAsked(Node nodeToBeAsked)
	{
		this.nodeToBeAsked = nodeToBeAsked;
	}
	public Node getNodeToBeAsked()
	{
		return this.nodeToBeAsked;
	}
	
	public void setAuxNodeToBeAsked(Node auxNodeToBeAsked)
	{
		this.auxNodeToBeAsked = auxNodeToBeAsked;
	}
	public Node getAuxNodeToBeAsked()
	{
		return this.auxNodeToBeAsked;
	}
	

} public class InferenceEngine {
	private NodeSet nodeSet;
	private Node targetNode;
    private AssessmentState ast;
    private Assessment ass;
    private List<Node> nodeFactList;
    private ScriptEngine scriptEngine = new ScriptEngineManager().getEngineByName("nashorn");
 
    
    
   
//    private int ruleIndex = 0;

    public InferenceEngine(NodeSet nodeSet)
    {
	    	this.nodeSet = nodeSet;
	    	ast = newAssessmentState();
	    	HashMap<String, FactValue> tempFactMap = nodeSet.getFactMap();
	    	HashMap<String, FactValue> tempWorkingMemory = ast.getWorkingMemory();
	    	
	    if(!tempFactMap.isEmpty())
	    	{
	    		tempFactMap.keySet().stream().forEach(key-> tempWorkingMemory.put(key, tempFactMap.get(key))); 	
	    	}
	    	nodeFactList = new ArrayList<>(nodeSet.getNodeSortedList().size()*2); // contains all rules set as a fact given by a user from a ruleList
    	
    }

    public void addNodeSet(NodeSet nodeSet2)
    {
    	
    }
    
    public void setNodeSet(NodeSet nodeSet)
    {
	    	this.nodeSet = nodeSet;
	    	ast = newAssessmentState();
	    	HashMap<String, FactValue> tempFactMap = nodeSet.getFactMap();
	    	HashMap<String, FactValue> tempWorkingMemory = ast.getWorkingMemory();
	    	
	    if(!tempFactMap.isEmpty())
	    	{
	    		tempFactMap.keySet().stream().forEach(key-> tempWorkingMemory.put(key, tempFactMap.get(key))); 	
	    	}
	    	nodeFactList = new ArrayList<>(nodeSet.getNodeSortedList().size()*2); // contains all rules set as a fact given by a user from a ruleList
	
    }
    
    public NodeSet getNodeSet()
    {
    		return this.nodeSet;
    }
    public AssessmentState getAssessmentState()
    {
    		return this.ast;
    }
    
    public AssessmentState newAssessmentState()
    {
	    	int initialSize = nodeSet.getNodeSortedList().size() * 2;
	    	AssessmentState ast = new AssessmentState();
	    	List<String> inclusiveList = new ArrayList<>(initialSize);
	    	List<String> summaryList = new ArrayList<>(initialSize);
	    	ast.setInclusiveList(inclusiveList);
	    	ast.setSummaryList(summaryList);
	    	
	    	return ast;
    	
    }
    
    public void setAssessment(Assessment ass)
    {
    		this.ass = ass;
    }
    public Assessment getAssessment()
    {
    		return this.ass;
    }
    public ScriptEngine getScriptEngine()
    {
    		return this.scriptEngine;
    }
    /*
     * this method is to extract all variableName of Nodes, and put them into a List<String>
     * it may be useful to display and ask a user to select which information they do have even before starting Inference process
     */
    public List<String> getListOfVariableNameAndValueOfNodes()
    {
	    	List<String> variableNameList = new ArrayList<>();
	    	nodeSet.getNodeMap().values().stream().forEachOrdered(node -> {
	    		if(nodeSet.getDependencyMatrix().getToChildDependencyList(node.getNodeId()).isEmpty())
	    		{
	    			variableNameList.add(node.getVariableName());
		    		FactValueType nodeFactValueType = node.getFactValue().getType();
		    		
		    		if(nodeFactValueType.equals(FactValueType.STRING) || nodeFactValueType.equals(FactValueType.TEXT))
		    		{
		    			variableNameList.add(node.getFactValue().getValue().toString());
		    		}
	    		}
    		});
	    	
	    	
	    	return variableNameList;
    }
    /*
     * this method allows to store all information via GUI even before starting Inference process. 
     */
    public void addNodeFact(String nodeVariableName, FactValue fv)
    {
	    	nodeSet.getNodeMap().values().stream()
	    								 .forEachOrdered((node) -> {
	    									 if(node.getVariableName().equals(nodeVariableName)||
	    										node.getFactValue().getValue().toString().equals(nodeVariableName))
											 {
	    										 	nodeFactList.add(node);
											 }
	    								});  
		ast.getWorkingMemory().put(nodeVariableName, fv);

    }
    
    /*
     * this method is to find all relevant Nodes(immediate child nodes of the most parent) with given information from a user
     * while finding out all relevant factors, all given information will be stored in AssessmentState.workingMemory
     */
    public List<Node> findRelevantFactors()
    {
	    	List<Node> relevantFactorList = new ArrayList<>();
	    	if(!nodeFactList.isEmpty())
	    	{
	    		nodeFactList.stream().forEachOrdered(node -> {
	    			if(!nodeSet.getDependencyMatrix().getFromParentDependencyList(node.getNodeId()).isEmpty())
	    			{
	    				Node relevantNode = auxFindRelevantFactors(node);
	    				relevantFactorList.add(relevantNode);
	    			}
	    		});
	    	}
	    	return relevantFactorList;
    }
    
    public Node auxFindRelevantFactors(Node node)
    {
	    	Node relevantFactorNode = null;
	    	List<Integer> incomingDependencyList = nodeSet.getDependencyMatrix().getFromParentDependencyList(node.getNodeId()); // it contains all id of parent node where dependency come from
	    	if(!incomingDependencyList.isEmpty())
	    	{
	    		for(int i = 0; i < incomingDependencyList.size(); i++)
	    		{
	    			Node parentNode = nodeSet.getNodeMap().get(nodeSet.getNodeIdMap().get(incomingDependencyList.get(i)));
	    			if(!nodeSet.getDependencyMatrix().getFromParentDependencyList(parentNode.getNodeId()).isEmpty() 
	    					&& !parentNode.getNodeName().equals(nodeSet.getNodeSortedList().get(0).getNodeName()))
	    			{
	    				relevantFactorNode = auxFindRelevantFactors(parentNode);
	
	    			}
	    		}
	    		
	    	}
	    	return relevantFactorNode;
    }
    
    /*
     * this method uses 'BACKWARD-CHAININING', and it will return node to be asked of a given assessment, which has not been determined and 
     * does not have any child nodes if the goal node of the given assessment has still not been determined.
     */
    public Node getNextQuestion(Assessment ass)
    {
	    	if(!ast.getInclusiveList().contains(ass.getGoalNode().getNodeName()))
		{
	    		ast.getInclusiveList().add(ass.getGoalNode().getNodeName());
		}
    	
	    	/*
	    	 * Default goal rule of a rule set which is a parameter of InferenceEngine will be evaluated by forwardChaining when any rule is evaluated within the rule set
	    	 */
	    	if(ast.getWorkingMemory().get(ass.getGoalNode().getNodeName())== null || !ast.allMandatoryNodeDetermined())
	    	{
	    		for (int i = ass.getGoalNodeIndex(); i < nodeSet.getNodeSortedList().size(); i++)
	  	       {
	    				Node node = nodeSet.getNodeSortedList().get(i);
	  	            
	  	
	  	            /*
	  	             * Step1. does the rule currently being been checked have child rules && not yet evaluated && is in the inclusiveList?
	  	             *     if no then ask a user to evaluate the rule, 
	  	             *                and do back propagating with a result of the evaluation (note that this part will be handled in feedAnswer())
	  	             *     if yes then move on to following step
	  	             *     
	  	             * Step2. does the rule currently being been checked have child rules? 
	  	             *     if yes then add the child rules into the inclusiveList
	  	             */
	    				int nodeId = node.getNodeId();
	    				if(i != ass.getGoalNodeIndex())
	    				{
	    					List<Integer> parentDependencyList = nodeSet.getDependencyMatrix().getFromParentDependencyList(nodeId);
	    					if(!parentDependencyList.isEmpty())
	    					{
	    						
	    						parentDependencyList.parallelStream().forEachOrdered(parentId -> {
	    							if((nodeSet.getDependencyMatrix().getDependencyType(parentId, nodeId)&DependencyType.getMandatory()) == DependencyType.getMandatory()
	    									&& !ast.isInclusiveList(node.getNodeName())
	    									&& !isIterateLineChild(node.getNodeId()))
	    							{
	    								ast.addItemToMandatoryList(node.getNodeName());
	    							}
	    						});
	    					}
	    				}
	    				if(nodeId != ass.getGoalNode().getNodeId() && node.getLineType().equals(LineType.ITERATE) && !ast.getWorkingMemory().containsKey(node.getNodeName()))
	    				{	
	    					FactValue givenListNameFv = this.ast.getWorkingMemory().get(((IterateLine)node).getGivenListName());
	    					String givenListName = "";
	    					if(givenListNameFv != null)
	    					{
	    						givenListName = givenListNameFv.toString().trim();
	    					}
	    					if(givenListName.length() > 0)
	    					{
	    						((IterateLine)node).iterateFeedAnswers(givenListName, this.nodeSet, this.ast, ass);
	    					}
	    					else
	    					{
	    						if(!this.ast.getWorkingMemory().containsKey(node.getNodeName()) && !this.ast.getExclusiveList().contains(node.getNodeName()))
		    					{
		    						ass.setNodeToBeAsked(node);
			    					int indexOfRuleToBeAsked = i;
				  	            	System.out.println("indexOfRuleToBeAsked : "+indexOfRuleToBeAsked);

				  	          	Node nextQuestionFromIterateNode = ((IterateLine)node).getIterateNextQuestion(this.nodeSet, this.ast);
				  	  			ass.setAuxNodeToBeAsked(nextQuestionFromIterateNode); //this is to treat the node as IterateLine node

		    						return nextQuestionFromIterateNode;
		    					}
	    					}	    					
	    				}
	    				else if(!hasChildren(nodeId) && ast.getInclusiveList().contains(node.getNodeName()) 
	  	            			&& !canEvaluate(node))
	  	            {
	  	            		ass.setNodeToBeAsked(node);
		  	            	int indexOfRuleToBeAsked = i;
		  	            	System.out.println("indexOfRuleToBeAsked : "+indexOfRuleToBeAsked);
		  	          	
		  	            	return ass.getNodeToBeAsked();
	  	            }
		            else if(hasChildren(nodeId) && !ast.getWorkingMemory().containsKey(node.getVariableName()) 
	            			&& !ast.getWorkingMemory().containsKey(node.getNodeName()) && ast.getInclusiveList().contains(node.getNodeName()))
		            {
		            		addChildRuleIntoInclusiveList(node);
		            }
	  	       }
	    	} 	
	    	
	    	Node nextQuestionNode = ass.getNodeToBeAsked();
	    	if(nextQuestionNode != null && nextQuestionNode.getLineType().equals(LineType.ITERATE))
		{
			ass.setAuxNodeToBeAsked(nextQuestionNode);
		}
	    	
	    	return nextQuestionNode;
    }
    
    
    public List<String> getQuestionsFromNodeToBeAsked(Node nodeToBeAsked)
    {
    		List<String> questionList = new ArrayList<>();
    		LineType lineTypeOfNodeToBeAsked = nodeToBeAsked.getLineType();
    		// the most child node line types are as follows
    		// ValueConclusionLine type
    		if(lineTypeOfNodeToBeAsked.equals(LineType.VALUE_CONCLUSION))
    		{
    			/*
    			 * if the line format is 'A -statement' then node's nodeName and variableName has same value so that either of them can be asked as a question
    			 * 
    			 * if the line format is 'A IS IN LIST B' then the value of node's variableName is 'A' and the value of node's value is 'B' so that only 'A' needs to be asked.
    			 * list 'B' has to be provided in 'FIXED' list
    			 * 
    			 * In conclusion, if the line type is 'ValueConclusionLine' then node's variableName should be asked regardless its format
    			 */

			questionList.add(nodeToBeAsked.getVariableName());
    		}
    		// ComparionLine type
    		else if(lineTypeOfNodeToBeAsked.equals(LineType.COMPARISON))
    		{
    			if(!this.ast.getWorkingMemory().containsKey(((ComparisonLine)nodeToBeAsked).getLHS()))
    			{
    				questionList.add(((ComparisonLine)nodeToBeAsked).getLHS());
    			}

			
    			if(!TypeAlreadySet(nodeToBeAsked.getFactValue()) && 
    					!this.ast.getWorkingMemory().containsKey(((ComparisonLine)nodeToBeAsked).getRHS().getValue().toString()))
    			{
    				questionList.add(nodeToBeAsked.getFactValue().getValue().toString());
    			}
    		}
    		
	    	questionList.stream().forEachOrdered(item->ast.getInclusiveList().add(item));
	    	return questionList;
    }
    
    
    public HashMap<String,FactValueType> findTypeOfElementToBeAsked(Node node)
    {
	    	/*
	    	 * FactValueType can be handled as of 16/06/2017 is as follows;
	    	 *  1. TEXT, STRING;
	    	 *  2. INTEGER, NUMBER;
	    	 *  3. DOUBLE, DECIMAL;
	    	 *  4. BOOLEAN;
	    	 *  5. DATE;
	    	 *  6. HASH;
	    	 *  7. UUID; and
	    	 *  8. URL.   
	    	 * rest of them (LIST, RULE, RULE_SET, OBJECT, UNKNOWN, NULL) can't be handled at this stage
	    	 */
	    	FactValueType fvt = null;
	    	HashMap<String,FactValueType> factValueTypeMap = new HashMap<>();
	    	/*
	    	 * In a case of that if type of toBeAsked node is ComparisonLine type with following conditions;
	    	 *    - the type of the node's variable to compare is already set as 
	    	 *      DefiString (eg. 'dean' or "dean"), Integer (eg. 1, or 2), Double (eg. 1.2 or 2.1), Date (eg. 21/3/1299), Hash, UUID, or URL 
	    	 *   then don't need to look into InputMap or FactMap to check the element's type of 'toBeAsked node' 
	    	 *   simply because we can check by looking at type of value variable because two different type CANNOT be compared
	    	 *   
	    	 * If neither type of variable is NOT defined in INPUT/FACT list nor the above case, and value of nodeVariable is same as value of nodeValueString  
	    	 * then the engine will recognise a nodeVariable or/and nodeVlaue as a boolean type 
	    	 * so that the question for a nodeVariable or/and a nodeValue seeks boolean type of answer
	    	 *   
	    	 */
	    	
	    	String nodeVariableName = node.getVariableName();
	    	String nodeValueString = node.getFactValue().getValue().toString();
	    	boolean TypeAlreadySet = TypeAlreadySet(node.getFactValue());
	    	HashMap<String, FactValue> tempFactMap = this.nodeSet.getFactMap();
	    	HashMap<String, FactValue> tempInputMap = this.nodeSet.getInputMap();
	    	LineType nodeLineType = node.getLineType();	   
	    	
	    	//ComparisonLine type node and type of the node's value is clearly defined 
	    	if(LineType.COMPARISON.equals(nodeLineType))
	    	{
	    		FactValueType nodeRHSType = ((ComparisonLine)node).getRHS().getType();
	    		if(!nodeRHSType.equals(FactValueType.STRING))
    			{
	    			if(nodeRHSType.equals(FactValueType.DEFI_STRING))
	    			{
		    			fvt = FactValueType.STRING;
		    		}
		    		else if(TypeAlreadySet)
		    		{
		    			fvt= nodeRHSType;
		    		}
		    		factValueTypeMap.put(((ComparisonLine)node).getLHS(), fvt);
	    				
    			}
	    		else if(nodeRHSType.equals(FactValueType.STRING))
	    		{
	    			if(tempInputMap.containsKey(((ComparisonLine)node).getLHS()))
	    			{
	    				fvt = tempInputMap.get(((ComparisonLine)node).getLHS()).getType();
	    			}
	    			else if(tempInputMap.containsKey(((ComparisonLine)node).getRHS().getValue().toString()))
	    			{
	    				fvt = tempInputMap.get(((ComparisonLine)node).getRHS().getValue().toString()).getType();
	    			}
	    			else if(tempFactMap.containsKey(((ComparisonLine)node).getLHS()))
	    			{
	    				fvt = tempFactMap.get(((ComparisonLine)node).getLHS()).getType();
	    			}
	    			else if(tempFactMap.containsKey(((ComparisonLine)node).getRHS().getValue().toString()))
	    			{
	    				fvt = tempFactMap.get(((ComparisonLine)node).getRHS().getValue().toString()).getType();
	    			}
	    			factValueTypeMap.put(((ComparisonLine)node).getLHS(), fvt);
    				factValueTypeMap.put(((ComparisonLine)node).getRHS().getValue().toString(), fvt);
	    		}
	    		
	    	}
	    	//ComparisonLine type node and type of the node's value is not clearly defined and not defined in INPUT nor FIXED list
	    	//ValueConclusionLine type node and it is 'A-statement' line, and variableName is not defined neither INPUT nor FIXED 
	    	else if(LineType.VALUE_CONCLUSION.equals(nodeLineType))
	    	{
	    		if(tempInputMap.containsKey(nodeVariableName))
	    		{
	    			fvt = tempInputMap.get(nodeVariableName).getType();
	    		}
	    		else if(ast.getWorkingMemory().containsKey(nodeValueString))
	    		{
	    			FactValue tempFv= ast.getWorkingMemory().get(nodeValueString);
	    			if(tempFv.getType().equals(FactValueType.LIST))
	    			{
	    				fvt = ((FactValue)((FactListValue<?>)tempFv).getValue().get(0)).getType();
	    			}
	    			else
	    			{
	    				fvt = tempFv.getType();
	    			}
	    		}
	    		else
	    		{
	    			fvt = FactValueType.BOOLEAN;
	    			
	    		}
	    		factValueTypeMap.put(nodeVariableName, fvt);
	    		
	    	}
	    	
	    	return factValueTypeMap;
    }
    
    public boolean TypeAlreadySet(FactValue value)
    {
    		boolean hasAlreadySetType = false;
    		
    		FactValueType factValueType = value.getType();
    		if(factValueType.equals(FactValueType.DEFI_STRING) || factValueType.equals(FactValueType.INTEGER) || factValueType.equals(FactValueType.DOUBLE) 
    				|| factValueType.equals(FactValueType.DATE) || factValueType.equals(FactValueType.BOOLEAN) || factValueType.equals(FactValueType.UUID) 
    				|| factValueType.equals(FactValueType.URL) || factValueType.equals(FactValueType.HASH))
		{
    			hasAlreadySetType = true;
		}
    		return hasAlreadySetType;
    }
    
    public boolean isIterateLineChild(int nodeId)
    {
    		boolean isIterateLineChild = false;
    		List<Integer> tempList = new ArrayList<>();
    		List<Node> iterateLineList = nodeSet.getNodeMap().values().stream().filter(node->node.getLineType().equals(LineType.ITERATE)).collect(Collectors.toList());
    		
    		iterateLineList.forEach(iNode -> {
    			List<Integer> iterateChildNodeList = this.nodeSet.getDependencyMatrix().getToChildDependencyList(iNode.getNodeId());
    			if(iterateChildNodeList.contains(nodeId))
    			{
    				tempList.add(1);
    			}
    			else
    			{
    				isIterateLineChildAux(tempList, iterateChildNodeList, nodeId);
    			}
    		});
    		
    		if(tempList.size() > 0 )
    		{
    			isIterateLineChild = true;
    		}
    		else
    		{
    			if(this.getAssessmentState().getMandatoryList().contains(this.nodeSet.getNodeIdMap().get(nodeId)))
    			{
    				this.getAssessmentState().getMandatoryList().remove(this.nodeSet.getNodeIdMap().get(nodeId));
    			}
    		}
    		
    		return isIterateLineChild;
    }
    
    public void isIterateLineChildAux(List<Integer> tempList, List<Integer> iterateChildNodeList, int nodeId)
    {
	    	iterateChildNodeList.forEach(id -> {
					List<Integer> iterateChildNodeListAux = this.nodeSet.getDependencyMatrix().getToChildDependencyList(id);
					if(iterateChildNodeList.contains(nodeId))
					{
						tempList.add(1);
					}
					else
					{
						isIterateLineChildAux(tempList, iterateChildNodeListAux, nodeId);
					}
		});
    }
    
    /*
     * this is to check whether or not a node can be evaluated with all information in the workingMemory. If there is information for a value of node's value(FactValue) or variableName, then the node can be evaluated otherwise not.
     * In order to do it, AssessmentState.workingMemory must contain a value for variable of the rule, 
     * and rule type must be either COMPARISON, ITERATE or VALUE_CONCLUSION because they are the ones only can be the most child nodes, 
     * and other type of node must be a parent of other types of node.	
     */
    public boolean canEvaluate(Node node)
    {
    	
	    	boolean canEvaluate = false;
	    	LineType lineType = node.getLineType();

	    	if(lineType.equals(LineType.VALUE_CONCLUSION))
	    	{
	    		if(((ValueConclusionLine)node).getIsPlainStatementFormat() && ast.getWorkingMemory().containsKey(node.getVariableName()))
	    		{
		    	    	/*
		    	    	 * If the node is in plain statement format then varibaleName has same value as nodeName,
		    	    	 * and if a value for either variableName or nodeName of the node is in workingMemory then it means the node has already been evaluated.
		    	    	 * Hence, 'canEvaluate' needs to be 'true' in this case.
		    	    	 */
	    			canEvaluate = true;
	    		}
	    		else if(node.getTokens().tokensList.stream().anyMatch((s) -> s.equals("IS IN LIST:")) 
	    				&& ast.getWorkingMemory().containsKey(node.getFactValue().getValue().toString())
	    				&& ast.getWorkingMemory().containsKey(node.getVariableName()))
			{
	    			canEvaluate = true;
	    			FactValue fv = node.selfEvaluate(ast.getWorkingMemory(), this.scriptEngine);

	    			/*
		    		 * the reason why ast.setFact() is used here rather than this.feedAndwerToNode() is that LineType is already known, and target node object is already found. 
		    		 * node.selfEvaluation() returns a value of the node's self-evaluation hence, node.getNodeName() is used to store a value for the node itself into a workingMemory
		    		 */
		    		ast.setFact(node.getNodeName(), fv);
			}
	    	}
	    	else if(lineType.equals(LineType.COMPARISON))
	    	{
	    		FactValue nodeRhsValue = ((ComparisonLine)node).getRHS();
	    		if(!nodeRhsValue.getType().equals(FactValueType.STRING) 
	    				&& ast.getWorkingMemory().containsKey(((ComparisonLine)node).getLHS()))
	    		{
	    			canEvaluate = true;
	    			if(!ast.getWorkingMemory().containsKey(node.getNodeName())) 
	    			{
	    				ast.setFact(node.getNodeName(), node.selfEvaluate(ast.getWorkingMemory(), this.scriptEngine));
	    			}
	    		}
	    		else if(nodeRhsValue.getType().equals(FactValueType.STRING) 
	    				&& ast.getWorkingMemory().containsKey(((ComparisonLine)node).getLHS()) 
	    				&& ast.getWorkingMemory().containsKey(((ComparisonLine)node).getRHS().getValue().toString()))
	    		{
	    			canEvaluate = true;
	    			if(!ast.getWorkingMemory().containsKey(node.getNodeName())) 
	    			{
	    				ast.setFact(node.getNodeName(), node.selfEvaluate(ast.getWorkingMemory(), this.scriptEngine));
	    			}
	    		}
	    	}

	    	return canEvaluate;
    }
    

    /* 
     * this method is to add fact or set a node as a fact by using AssessmentState.setFact() method. it also is used to feed an answer to a being asked node.
     * once a fact is added then forward-chain is used to update all effected nodes' state, and workingMemory in AssessmentState class will be updated accordingly
     * the reason for taking nodeName instead nodeVariableName is that it will be easier to find an exact node with nodeName
     * rather than nodeVariableName because a certain nodeVariableName could be found in several nodes
     */
    @SuppressWarnings({"unchecked" })
	public <T> void feedAnswerToNode(Node targetNode, String questionName, T nodeValue, FactValueType nodeValueType, Assessment ass)
    {
	    	FactValue fv = null;
	    	
	    	if(nodeValueType.equals(FactValueType.BOOLEAN))
	    	{
	    		fv = FactValue.parse(Boolean.parseBoolean((String)nodeValue));
	    	}
	    	else if(nodeValueType.equals(FactValueType.DATE))
	    	{
	    		/*
	    		 * the string of nodeValue date format is dd/MM/YYYY
	    		 */
	    		String[] dateArray = ((String)nodeValue).split("/");
	    		LocalDate factValueInDate = LocalDate.of(Integer.parseInt(dateArray[2]), Integer.parseInt(dateArray[1]), Integer.parseInt(dateArray[0]));
	    		
	    		fv = FactValue.parse(factValueInDate);
	    	}
	    	else if(nodeValueType.equals(FactValueType.DOUBLE))
	    	{
	    		fv = FactValue.parse(Double.parseDouble((String)nodeValue));
	    	}
	    	else if(nodeValueType.equals(FactValueType.INTEGER))
	    	{
	    		fv = FactValue.parse(Integer.parseInt((String)nodeValue));
	    	}
	    	else if(nodeValueType.equals(FactValueType.LIST))
	    	{
	    		fv = FactValue.parse((List<FactValue>)nodeValue);
	    	}
	    	else if(nodeValueType.equals(FactValueType.STRING))
	    {
	    		fv = FactValue.parse((String) nodeValue);
	    }
	    	else if(nodeValueType.equals(FactValueType.DEFI_STRING))
	    	{
	    		fv = FactValue.parseDefiString((String) nodeValue);
	    	}
	    	else if(nodeValueType.equals(FactValueType.HASH))
	    	{
	    		fv = FactValue.parseHash((String)nodeValue);
	    	}
	    	else if(nodeValueType.equals(FactValueType.URL))
	    	{
	    		fv = FactValue.parseURL((String)nodeValue);
	    	}
	    	else if(nodeValueType.equals(FactValueType.UUID))
	    	{
	    		fv = FactValue.parseUUID((String)nodeValue);
	    	}
	    	
	    	if(fv != null && !ass.getNodeToBeAsked().getLineType().equals(LineType.ITERATE))
	    	{
	    		ast.setFact(questionName, fv);
			ast.addItemToSummaryList(questionName);// add currentRule into SummeryList as the rule determined

	    		
	    		if(targetNode.getLineType().equals(LineType.VALUE_CONCLUSION) && !((ValueConclusionLine)targetNode).getIsPlainStatementFormat())
	    		{
	    			FactValue selfEvalFactValue = targetNode.selfEvaluate(ast.getWorkingMemory(), this.scriptEngine);
		    		ast.setFact(targetNode.getNodeName(), selfEvalFactValue); // add the value of targetNode itself into the workingMemory	
				ast.addItemToSummaryList(targetNode.getNodeName());// add currentRule into SummeryList as the rule determined
	    		}
	    		else if(targetNode.getLineType().equals(LineType.COMPARISON))
	    		{
	    			FactValue rhsValue = ((ComparisonLine)targetNode).getRHS();
	    			if((rhsValue.getType().equals(FactValueType.STRING) 
	    					&& ast.getWorkingMemory().containsKey(rhsValue.getValue().toString())) 
	    				|| !rhsValue.getType().equals(FactValueType.STRING)
    				  )
	    			{
	    				FactValue selfEvalFactValue = targetNode.selfEvaluate(ast.getWorkingMemory(), this.scriptEngine);
	    				ast.setFact(targetNode.getNodeName(), selfEvalFactValue); // add the value of targetNode itself into the workingMemory	
					ast.addItemToSummaryList(targetNode.getNodeName());// add currentRule into SummeryList as the rule determined
	    			}
	    			
	    		}
	    		
	    	 	
	    		/*
	        	 * once any rules are set as fact and stored into the workingMemory, back-propagation(forward-chaining) needs to be done
	        	 */
	    		backPropagating(nodeSet.findNodeIndex(targetNode.getNodeName()));
	        
	    	}
	    	else if(ass.getNodeToBeAsked().getLineType().equals(LineType.ITERATE))
	    	{
	    		targetNode = ass.getAuxNodeToBeAsked();
    			((IterateLine)ass.getNodeToBeAsked()).iterateFeedAnswers(targetNode, questionName, nodeValue, nodeValueType, this.nodeSet, ast, ass);
    			if(((IterateLine)ass.getNodeToBeAsked()).canBeSelfEvaluated(ast.getWorkingMemory()))
    			{
    	    			backPropagating(nodeSet.findNodeIndex(ass.getNodeToBeAsked().getNodeName()));
    			}
	    	}
    }
    
   
    public void backPropagating(int nodeIndex)
    {
    		List<Node> nodeSortedList = nodeSet.getNodeSortedList();
    		int sortedListSize = nodeSortedList.size();
    		IntStream.range(0, sortedListSize).forEachOrdered(i ->{
    			//current index = sortedListSize - (i+1)
    			Node tempNode = nodeSortedList.get(sortedListSize - (i+1));
			LineType lineType = tempNode.getLineType();
			
			int tempNodeId = tempNode.getNodeId();
			List<Integer> parentDependencyList = nodeSet.getDependencyMatrix().getFromParentDependencyList(tempNodeId);
			if(!parentDependencyList.isEmpty())
			{
				
				parentDependencyList.stream().forEachOrdered(parentId -> {
					if((nodeSet.getDependencyMatrix().getDependencyType(parentId, tempNodeId)&DependencyType.getMandatory()) == DependencyType.getMandatory()
							&& !ast.isInclusiveList(tempNode.getNodeName())
							&& !isIterateLineChild(tempNode.getNodeId()))
					{
						ast.addItemToMandatoryList(tempNode.getNodeName());
					}
				});
			}

    			if(nodeIndex < (sortedListSize - (i+1))) //case of all nodes located after the nodeIndex
    			{
    				if(hasChildren(tempNode.getNodeId()))
    				{
    					 if(!ast.getWorkingMemory().containsKey(tempNode.getNodeName())
	    		    			   	 && canDetermine(tempNode, lineType)
	    		    		   )
		    		    	 {
    						 if(!lineType.equals(LineType.EXPR_CONCLUSION))
    						 {
    	     						ast.addItemToSummaryList(tempNode.getNodeName());// add currentRule into SummeryList as the rule determined
    						 }
		    		    	 }
    				}
    				else
    				{
    					/*
    					 * ValueConclusionLine in 'A-statement' format does not need to be considered here due to the reason that
    					 * the case should be in the workingMemory if it is already asked.
    					 */
    					if(lineType.equals(LineType.VALUE_CONCLUSION) 
							&& !((ValueConclusionLine)tempNode).getIsPlainStatementFormat() 
							&& ast.getWorkingMemory().containsKey(((ValueConclusionLine)tempNode).getVariableName()))
    					{
						FactValue fv = tempNode.selfEvaluate(ast.getWorkingMemory(), scriptEngine);

    						ast.setFact(tempNode.getNodeName(), fv);
    						ast.addItemToSummaryList(tempNode.getNodeName());// add currentRule into SummeryList as the rule determined
    					}
    					else if(lineType.equals(LineType.COMPARISON) 
    							&& ast.getWorkingMemory().containsKey(((ComparisonLine)tempNode).getLHS())
    							&& (((ComparisonLine)tempNode).getRHS().getType().equals(FactValueType.STRING)? ast.getWorkingMemory().containsKey(((ComparisonLine)tempNode).getRHS().getValue().toString()):true))  
        				{
    						FactValue fv = tempNode.selfEvaluate(ast.getWorkingMemory(), scriptEngine);

    						ast.setFact(tempNode.getNodeName(), fv);
    						ast.addItemToSummaryList(tempNode.getNodeName());// add currentRule into SummeryList as the rule determined
        				}

    				}
    				
    			}
    			else // case of all nodes located before the nodeIndex
    			{
    				/*
    				 * it the tempNode is located before the nodeIndex then there is need to check whether or not the tempNode is in the inclusiveList due to the reason that
    				 * evaluating only relevant node could speed the propagation faster. In addition only relevant nodes can be traced by checking the inclusiveList.
    				 */
    				
    				if(ast.getInclusiveList().contains(tempNode.getNodeName()))
	    	    		{
	    	    			/*
	    	    			 * once a user feeds an answer to the engine, the engine will propagate the entire NodeSet or Assessment base on the answer
	    	    			 * during the back-propagation, the engine checks if current node;
	    	    			 * 1. has been determined;
	    	    			 * 2. has any child nodes;
	    	    			 * 3. can be determined with given facts in the workingMemory.
	    	    			 * 
    	    			     * once the current checking node meets the condition then add it to the summaryList for summary view.
	    		    	     */
	    		    	   if (!ast.getWorkingMemory().containsKey(tempNode.getNodeName()) 
	    		    			   && hasChildren(tempNode.getNodeId())
	    		    			   && canDetermine(tempNode, lineType)
	    		    		  )
	    		    	   {
	    		    		   if(!lineType.equals(LineType.EXPR_CONCLUSION))
	    		    		   {
	    		    			   ast.addItemToSummaryList(tempNode.getNodeName()); // add currentRule into SummeryList as the rule determined
	    		    		   }

	    		    	   }
	    	    		}
    			}
    		});
    		
    }

    
    /*
     *this method is to find all parent rules of a given rule, and add them into the ' inclusiveList' for future reference
     */
    public void addParentIntoInclusiveList(Node node)
    {
    		List<Integer> nodeInDependencyList = nodeSet.getDependencyMatrix().getFromParentDependencyList(node.getNodeId());
        if(!nodeInDependencyList.isEmpty()) // if rule has parents
        {
	        	nodeInDependencyList.stream().forEachOrdered(i -> {
	        		Node parentNode = nodeSet.getNodeMap().get(nodeSet.getNodeIdMap().get(i));
	        		if(!ast.getInclusiveList().contains(parentNode.getNodeName()))
	        		{
	        			ast.getInclusiveList().add(parentNode.getNodeName());
	        		}
	        	});
          
        }
    }
    
    public boolean hasAllMandatoryChildAnswered(int nodeId)
    {    		

    		List<Integer> mandatoryChildDependencyList = nodeSet.getDependencyMatrix().getMandatoryToChildDependencyList(nodeId);
    		boolean hasAllMandatoryChildAnswered = false;
    		if(!mandatoryChildDependencyList.isEmpty())
    		{
    			hasAllMandatoryChildAnswered = mandatoryChildDependencyList.parallelStream()
																	 .allMatch(childId->
																					    	(ast.getWorkingMemory().containsKey(nodeSet.getNodeIdMap().get(childId))
																					    	     && hasAllMandatoryChildAnswered(childId ))
																			  );
    		}
    		else if(mandatoryChildDependencyList.isEmpty())
    		{
    			hasAllMandatoryChildAnswered = true;
    		}
    		
    		return hasAllMandatoryChildAnswered;
    }
	public boolean canDetermine(Node node, LineType lineType)
	{
	    	boolean canDetermine = false;
	    	/*
	    	 * Any type of node/line can have either 'OR' or 'AND' type of child nodes
	    	 *  
	    	 * -----ValueConclusion Type
	    	 * there will be two cases for this type
	    	 *    V.1 the format of node is 'A -statement' so that 'TRUE' or "FALSE' value outcome case
	    	 *    	   V.1.1 if it has 'OR' child nodes
	    	 *    			 V.1.1.1 TRUE case
	    	 *    					 if there is any of child node is 'true'
	    	 *    					 then trim off 'UNDETERMINED' child nodes, which are not in 'workingMemory', other than 'MANDATORY' child nodes
	    	 *    			 V.1.1.2 FALSE case
	    	 *    					 if its all 'OR' child nodes are determined and all of them are 'false'
	    	 *    	   V.1.2 if it has 'AND' child nodes
	    	 *       		 V.1.2.1 TRUE case
	    	 *       				 if its all 'AND' child nodes are determined and all of them are 'true'
	    	 *       		 V.1.2.2 FALSE case
	    	 *       				 if its all 'AND' child nodes are determined and all of them are 'false'
	    	 *                     	 , and there is no need to trim off 'UNDETERMINED' child nodes other than 'MANDATORY' child nodes
	    	 *                       because since 'virtual node' is introduced, any parent nodes won't have 'OR' and 'AND' dependency at the same time
	    	 *              
	    	 *         V.1.3 other than above scenario it can't be determined in 'V.1' case
	    	 *    
	    	 *    V.2 a case of that the value in the node text can be used as a value of its node's variable (e.g. A IS B, B can be used as a value for variable, 'A' in this case if all its child nodes or one of its child node is true)
	    	 *    	   V.2.1 if it has 'OR' child nodes
	    	 *    			 V.2.1.1 the value CAN BE USED case
	    	 *    					 if its any of child node is 'true'
	    	 *    					 then trim off 'UNDETERMINED' child nodes, which are not in 'workingMemory', other than 'MANDATORY' child nodes
	    	 *    			 V.2.1.2 the value CANNOT BE USED case
	    	 *    					 if its all 'OR' child nodes are determined and all of them are 'false'
	    	 *    	   V.2.2 if it has 'AND' child nodes
	    	 *    			 V.2.2.1 the value CAN BE USED case
	    	 *    					 if its all 'AND' child nodes are determined and all of them are 'true'
	    	 *    			 V.2.2.2 the value CANNOT BE USED case
	    	 *    					 if its all 'AND' child nodes are determined and all of them are 'false'
	    	 *                     	 , and there is no need to trim off 'UNDETERMINED' child nodes other than 'MANDATORY' child nodes
	    	 *                       because since 'virtual node' is introduced, any parent nodes won't have 'OR' and 'AND' dependency at the same time
	    	 *         
	    	 *         V.2.3 other than above scenario it can't be determined in 'V.2' case
	    	 *              
	    	 *    
	    	 * Note: the reason why only ResultType and ExpressionType are evaluated with selfEvaluation() is as follows;
	    	 *       1. ComparisonType is only evaluated by comparing a value of rule's variable in workingMemory with the value in the node
	    	 *       2. ExpressionType is only evaluated by retrieving a value(s) of needed child node(s)   
	    	 *       3. ValueConclusionType is evaluated under same combination of various condition, and trimming dependency is involved.	    	 *        
	    	 *       
	    	 */
	    	List<Integer> orToChildDependencies = nodeSet.getDependencyMatrix().getOrToChildDependencyList(node.getNodeId());
	    	List<Integer> andToChildDependencies = nodeSet.getDependencyMatrix().getAndToChildDependencyList(node.getNodeId());
	   
	    	
	    
    		if(LineType.VALUE_CONCLUSION.equals(lineType))
	    	{
	    		if(node.getNodeName().contains("IS IN LIST") 
	    				&& ast.getWorkingMemory().containsKey(node.getVariableName()) 
	    				&& ast.getWorkingMemory().containsKey(node.getFactValue().getValue().toString()))
	    		{
	    			ast.setFact(node.getNodeName(), node.selfEvaluate(ast.getWorkingMemory(), scriptEngine));
	    			canDetermine = true; 
	    		}
	    		else
	    		{
	    			boolean isPlainStatementFormat = ((ValueConclusionLine)node).getIsPlainStatementFormat();
		    		String nodeFactValueInString = node.getFactValue().getValue().toString();
		    		
		    		/*
		    		 * isAnyOrDependencyTrue() method contains trimming off method to cut off any 'UNDETERMINED' state 'OR' child nodes. 
		    		 */
		    		if(andToChildDependencies.isEmpty() && !orToChildDependencies.isEmpty()) // rule has only 'OR' child rules 
		    		{
		    			
		    			if(isAnyOrDependencyTrue(node, orToChildDependencies)) //TRUE case
		    			{
		    				int nodeId = node.getNodeId();
	    				    if(nodeSet.getDependencyMatrix().hasMandatoryChildNode(nodeId) && !hasAllMandatoryChildAnswered(nodeId))
	    					{
							return canDetermine;
	    					}
		    				canDetermine = true;
		    				
		    				handleValuConclusionLineTrueCase(node, isPlainStatementFormat, nodeFactValueInString);
		    				
		    			}
		    			else if(isAllRelevantChildDependencyDetermined(node, orToChildDependencies) && !isAnyOrDependencyTrue(node, orToChildDependencies)) //FALSE case
		    			{
		    				canDetermine = true;
		    				
		    				handleValueConclusionLineFalseCase(node, isPlainStatementFormat, nodeFactValueInString);
		    				
		    			}
		    		}
		    		else if(!andToChildDependencies.isEmpty() && orToChildDependencies.isEmpty())// node has only 'AND' child nodes
		    		{
		    			if(isAllRelevantChildDependencyDetermined(node, andToChildDependencies) && isAllAndDependencyTrue(node, andToChildDependencies)) // TRUE case
					{
		    				canDetermine = true;
		    				
		    				handleValuConclusionLineTrueCase(node, isPlainStatementFormat, nodeFactValueInString);
	    					
					}
		    			/*
		    			 * 'isAnyAndDependencyFalse()' contains a trimming off dependency method 
		    			 * due to the fact that all undetermined 'AND' child nodes need to be trimmed off when any 'AND' node is evaluated as 'NO'
	               	 * , which does not influence on determining a parent rule's evaluation.
	               	 * 
		    			 */
		    			else if(isAnyAndDependencyFalse(node, andToChildDependencies)) //FALSE case
		    			{
		    				int nodeId = node.getNodeId();
	    				    if(nodeSet.getDependencyMatrix().hasMandatoryChildNode(nodeId))
	    					{
	    						if(!hasAllMandatoryChildAnswered(nodeId))
	    						{
	    							return canDetermine;
	    						}
	    					}
		    				canDetermine = true;
		    				
		    				handleValueConclusionLineFalseCase(node, isPlainStatementFormat, nodeFactValueInString);	    					
		    			}				
		    		}
	    		}	    		
	    	}
    		else if(LineType.COMPARISON.equals(lineType))
    		{
    			if(andToChildDependencies.isEmpty() && !orToChildDependencies.isEmpty()) // rule has only 'OR' child rules 
	    		{
	    			/*
	    			 * the node might have a 'MANDATORY OR' child nodes so that the mandatory child nodes need being handled
	    			 */
	    			if(hasAnyOrChildEvaluated(node.getNodeId(), orToChildDependencies))
	    			{
	    				if(!hasAllMandatoryChildAnswered(node.getNodeId()))
	    				{
	    					return canDetermine = false;
	    				}
	    				canDetermine = true;
	    				ast.setFact(node.getNodeName(), node.selfEvaluate(ast.getWorkingMemory(), scriptEngine));
	    				ast.addItemToSummaryList(node.getNodeName());
	    			}
	    		}
	    		else if(!andToChildDependencies.isEmpty() && orToChildDependencies.isEmpty())// node has only 'AND' child nodes
	    		{
	    			/*
	    			 * in this case they are all 'MANDATORY' child nodes
	    			 */
	    			if(hasAllAndChildEvaluated(andToChildDependencies))
	    			{
	    				if(!hasAllMandatoryChildAnswered(node.getNodeId()))
	    				{
	    					return canDetermine = false;
	    				}
	    				canDetermine = true;
	    				ast.setFact(node.getNodeName(), node.selfEvaluate(ast.getWorkingMemory(), scriptEngine));
	    				ast.addItemToSummaryList(node.getNodeName());
	    			}
	    		}
    			
    		}
	    	else if(LineType.EXPR_CONCLUSION.equals(lineType))
	    	{
	    		if(andToChildDependencies.isEmpty() && !orToChildDependencies.isEmpty()) // rule has only 'OR' child rules 
	    		{
	    			/*
	    			 * the node might have a 'MANDATORY OR' child nodes so that the mandatory child nodes need being handled
	    			 */
	    			if(hasAnyOrChildEvaluated(node.getNodeId(), orToChildDependencies))
	    			{
	    				if(!hasAllMandatoryChildAnswered(node.getNodeId()))
	    				{
	    					return canDetermine = false;
	    				}
	    				canDetermine = true;
	    				ast.setFact(node.getVariableName(), node.selfEvaluate(ast.getWorkingMemory(), scriptEngine));
	    				ast.setFact(node.getNodeName(), node.selfEvaluate(ast.getWorkingMemory(), scriptEngine)); //inserting same value for node's name is for the purpose of display equation

	    				ast.addItemToSummaryList(node.getVariableName());
	    				ast.addItemToSummaryList(node.getNodeName()); // inserting node's name is to find its evaluated value from the workingMemory with its name
	    			}
	    		}
	    		else if(!andToChildDependencies.isEmpty() && orToChildDependencies.isEmpty())// node has only 'AND' child nodes
	    		{
	    			/*
	    			 * in this case they are all 'MANDATORY' child nodes
	    			 */
	    			if(hasAllAndChildEvaluated(andToChildDependencies))
	    			{
	    				if(!hasAllMandatoryChildAnswered(node.getNodeId()))
	    				{
	    					return canDetermine = false;
	    				}
	    				canDetermine = true;
	    				ast.setFact(node.getVariableName(), node.selfEvaluate(ast.getWorkingMemory(), scriptEngine));
	    				ast.setFact(node.getNodeName(), node.selfEvaluate(ast.getWorkingMemory(), scriptEngine)); //inserting same value for node's name is for the purpose of display equation

	    				ast.addItemToSummaryList(node.getVariableName());
	    				ast.addItemToSummaryList(node.getNodeName()); // inserting node's name is to find its evaluated value from the workingMemory with its name
	    			}
	    		}
	    		else
	    		{
	    			if(hasAnyOrChildEvaluated(node.getNodeId(), orToChildDependencies) && hasAllAndChildEvaluated(andToChildDependencies))
	    			{
	    				if(!hasAllMandatoryChildAnswered(node.getNodeId()))
	    				{
	    					return canDetermine = false;
	    				}
	    				canDetermine = true;
	    				ast.setFact(node.getVariableName(), node.selfEvaluate(ast.getWorkingMemory(), scriptEngine));
	    				ast.setFact(node.getNodeName(), node.selfEvaluate(ast.getWorkingMemory(), scriptEngine)); //inserting same value for node's name is for the purpose of display equation

	    				ast.addItemToSummaryList(node.getVariableName());
	    				ast.addItemToSummaryList(node.getNodeName()); // inserting node's name is to find its evaluated value from the workingMemory with its name

	    			}
	    		}
	    	}
	    	else if(LineType.ITERATE.equals(lineType))
	    	{
	    		if(((IterateLine)node).canBeSelfEvaluated(ast.getWorkingMemory()))
	    		{
	    			ast.setFact(node.getNodeName(), node.selfEvaluate(ast.getWorkingMemory(), scriptEngine));
    				ast.addItemToSummaryList(node.getVariableName());
	    		}
	    	}
 	
	    	return canDetermine;
	}
    
	
	private boolean hasAnyOrChildEvaluated(int parentNodeId, List<Integer>orToChildDependencies)
	{
		
		boolean hasAnyOrChildEvaluated = orToChildDependencies.stream().anyMatch(i -> 
																					(ast.getWorkingMemory().containsKey(nodeSet.getNodeByNodeId(i).getVariableName())
																							&& (nodeSet.getDependencyMatrix().getDependencyType(parentNodeId, i) & DependencyType.getMandatory()) == DependencyType.getMandatory()
																					)
																					|| 
																					ast.getWorkingMemory().containsKey(nodeSet.getNodeByNodeId(i).getVariableName()) 
																			   );
		
		return hasAnyOrChildEvaluated;
	}
	
	private boolean hasAllAndChildEvaluated(List<Integer> andToChildDependencies)
	{
		boolean hasAllAndChildEvaluated = andToChildDependencies.stream().allMatch(i -> ast.getWorkingMemory().containsKey(nodeSet.getNodeByNodeId(i).getVariableName()));
				
		return hasAllAndChildEvaluated;
	}
	
	private void handleValuConclusionLineTrueCase(Node node, boolean isPlainStatementFormat, String nodeFactValueInString)
	{
		ast.setFact(node.getNodeName(), FactValue.parse(true));
		
		if(!isPlainStatementFormat)
		{
			if(ast.getWorkingMemory().containsKey(nodeFactValueInString))
			{
				ast.setFact(node.getVariableName(),  ast.getWorkingMemory().get(nodeFactValueInString));
			}
			else
			{
				ast.setFact(node.getVariableName(), node.getFactValue());
			}
			ast.addItemToSummaryList(node.getVariableName());
		}
	}
	private void handleValueConclusionLineFalseCase(Node node, boolean isPlainStatementFormat, String nodeFactValueInString)
	{
		ast.setFact(node.getNodeName(), FactValue.parse(false));
		
		if(!isPlainStatementFormat)
		{
			if(ast.getWorkingMemory().containsKey(nodeFactValueInString))
			{
				ast.setFact(node.getVariableName(), FactValue.parse("NOT "+ast.getWorkingMemory().get(nodeFactValueInString)));
			}
			else
			{
				ast.setFact(node.getVariableName(), FactValue.parse("NOT "+nodeFactValueInString));
			}
			ast.addItemToSummaryList(node.getVariableName());
		}
	}
    
    public String getDefaultGoalRuleQuestion() 
    {
    		return nodeSet.getDefaultGoalNode().getNodeName();
    }
    public String getAssessmentGoalRuleQuestion(Assessment ass)
    {
    		return ass.getGoalNode().getNodeName();
    }
    
    public FactValue getDefaultGoalRuleAnswer() 
    {
    		return ast.getWorkingMemory().get(nodeSet.getDefaultGoalNode().getVariableName());
    }
    
    public FactValue getAssessmentGoalRuleAnswer(Assessment ass)
    {
    		return ast.getWorkingMemory().get(ass.getGoalNode().getVariableName());
    }


    /*
     Returns boolean value that can determine whether or not the given rule has any children
     this method is used within the process of backward chaining.
     */
    public boolean hasChildren(int nodeId)
    {
        boolean hasChildren = false;
        if (!nodeSet.getDependencyMatrix().getToChildDependencyList(nodeId).isEmpty())
        {
            hasChildren = true;
            nodeSet.getDependencyMatrix().getToChildDependencyList(nodeId).stream().forEach(item->{
	            	Node nodeName = nodeSet.getNodeByNodeId(item);
	            	addChildRuleIntoInclusiveList(nodeName);
            });
        }
        return hasChildren;
    }

    /*
    the method adds all children rules of relevant parent rule into the 'inlcusiveList' if they are not in the list.
    */
    public void addChildRuleIntoInclusiveList(Node node)
    {

	 	List<Integer> childrenListOfNode = nodeSet.getDependencyMatrix().getToChildDependencyList(node.getNodeId());
    	    	childrenListOfNode.stream().forEachOrdered(item -> {
    	    		String childNodeName = nodeSet.getNodeMap().get(nodeSet.getNodeIdMap().get(item)).getNodeName();
    	    		if(!ast.getInclusiveList().contains(childNodeName) && !ast.getExclusiveList().contains(childNodeName))
    	    		{
    	    			ast.getInclusiveList().add(childNodeName);
    	    		}
    	    	});

    }


    public boolean isAnyOrDependencyTrue(Node node, List<Integer> orChildDependencies)
    {
        boolean isAnyOrDependencyTrue = false;
        targetNode = node;
        if (!orChildDependencies.isEmpty())
        {	        	
	        	
	        	List<Integer> trueOrChildList = new ArrayList<>();
	        	orChildDependencies.stream().forEachOrdered(item -> {
	        		if(ast.isInclusiveList(nodeSet.getNodeIdMap().get(item)) 
	        			&& ast.getWorkingMemory().containsKey(nodeSet.getNodeIdMap().get(item)))
	        		{
	        			if((nodeSet.getDependencyMatrix().getDependencyType(targetNode.getNodeId(), item)&DependencyType.getKnown()) == DependencyType.getKnown()
						  &&((nodeSet.getDependencyMatrix().getDependencyType(targetNode.getNodeId(), item)&DependencyType.getNot()) != DependencyType.getNot())
					  )
	        			{
	        				trueOrChildList.add(item);
	        				if(!ast.getWorkingMemory().containsKey("KNOWN "+nodeSet.getNodeIdMap().get(item)))
	        				{
	        					ast.setFact("KNOWN "+nodeSet.getNodeIdMap().get(item), FactBooleanValue.parse(true));
	        					ast.addItemToSummaryList("KNOWN "+nodeSet.getNodeIdMap().get(item));
	        				}
	        				
	        			}
	        			else if(ast.getWorkingMemory().get(nodeSet.getNodeIdMap().get(item)).getValue().equals(true)
							  &&((nodeSet.getDependencyMatrix().getDependencyType(targetNode.getNodeId(), item)&DependencyType.getNot()) != DependencyType.getNot()))
	        			{
	        				
	        				trueOrChildList.add(item);
	        			}
	        			else if(ast.getWorkingMemory().get(nodeSet.getNodeIdMap().get(item)).getValue().equals(false)
							  &&((nodeSet.getDependencyMatrix().getDependencyType(targetNode.getNodeId(), item)&DependencyType.getNot()) == DependencyType.getNot()))
	        			{
	        				trueOrChildList.add(item);
	        				
	        				if(!ast.getWorkingMemory().containsKey("NOT "+nodeSet.getNodeIdMap().get(item)))
	        				{
	        					ast.setFact("NOT "+nodeSet.getNodeIdMap().get(item), FactBooleanValue.parse(true));
	        					ast.addItemToSummaryList("NOT "+nodeSet.getNodeIdMap().get(item));
	        				}

	        			}
	        		}
	        	});
	        	
	        	
	        	if(!trueOrChildList.isEmpty())
	        	{
	        		isAnyOrDependencyTrue = true;
	        		orChildDependencies.stream().forEachOrdered(i -> {
	        			trueOrChildList.stream().forEachOrdered(n -> {
	        				if(i != n)
	        				{
	        					trimDependency(node, i);
	        				}
	        			});
	        		});
	        	}
        }
        return isAnyOrDependencyTrue;
    }

	public void trimDependency(Node parentNode, int childNodeId)
    {
		int parentNodeId = parentNode.getNodeId();
		int dpType = nodeSet.getDependencyMatrix().getDependencyMatrixArray()[parentNodeId][childNodeId];
		int mandatoryDependencyType = DependencyType.getMandatory();
		List<Integer> parentDependencyList = nodeSet.getDependencyMatrix().getFromParentDependencyList(childNodeId);
		
		if((parentDependencyList.size()>1 																		      				// the child has more than one parent,
				&& parentDependencyList.parallelStream()																				// all parents have been determined
								       .allMatch(parent -> ast.getWorkingMemory().containsKey(nodeSet.getNodeIdMap().get(parent)))
			    && parentDependencyList.parallelStream()																				//the child has no Mandatory dependency parents
								       .noneMatch(parent -> (nodeSet.getDependencyMatrix().getDependencyMatrixArray()[parent][childNodeId]&mandatoryDependencyType)==mandatoryDependencyType))
			||
			(parentDependencyList.size() == 1                                    													    // the child has only one parent
			&& ((dpType & mandatoryDependencyType) != mandatoryDependencyType) ))   													// the dependency is not 'MANDATORY'
	    	{
	    		ast.getInclusiveList().remove(nodeSet.getNodeIdMap().get(childNodeId));
	    		if(!ast.getExclusiveList().contains(nodeSet.getNodeIdMap().get(childNodeId)))
    			{
	    			ast.getExclusiveList().add(nodeSet.getNodeIdMap().get(childNodeId));
    			}
	    		List<Integer> childDependencyListOfChildNode = nodeSet.getDependencyMatrix().getToChildDependencyList(childNodeId);
	    		if(!childDependencyListOfChildNode.isEmpty())
	    		{
	    			childDependencyListOfChildNode.stream().forEach(item->{
	    				trimDependency(nodeSet.getNodeByNodeId(childNodeId),item);
	    			});
	    		}
	    	}
		
    }
    
    public boolean isAnyAndDependencyFalse(Node node, List<Integer> andChildDependencies)
    {
        boolean isAnyAndDependencyFalse = false;
        targetNode = node;

        if (!andChildDependencies.isEmpty())
        {	
	        	List<Integer> falseAndList = new ArrayList<>();
	            
            andChildDependencies.stream().forEachOrdered(item -> {
            		if(ast.getWorkingMemory().containsKey(nodeSet.getNodeIdMap().get(item)))
            		{
            			if(ast.getWorkingMemory().get(nodeSet.getNodeIdMap().get(item)).getValue().equals(false)
    							&&((nodeSet.getDependencyMatrix().getDependencyType(targetNode.getNodeId(), item)&DependencyType.getNot()) != DependencyType.getNot())
    							&&((nodeSet.getDependencyMatrix().getDependencyType(targetNode.getNodeId(), item) & DependencyType.getKnown()) != DependencyType.getKnown()))
            			{
               	 		falseAndList.add(item);
            			}
            			else if(ast.getWorkingMemory().get(nodeSet.getNodeIdMap().get(item)).getValue().equals(true)
								&&((nodeSet.getDependencyMatrix().getDependencyType(targetNode.getNodeId(), item)&DependencyType.getNot()) == DependencyType.getNot())
								&&((nodeSet.getDependencyMatrix().getDependencyType(targetNode.getNodeId(), item) & DependencyType.getKnown()) != DependencyType.getKnown())
						)
            			{
            				if(!ast.getWorkingMemory().containsKey("NOT "+nodeSet.getNodeIdMap().get(item)))
            				{
            					ast.setFact("NOT "+nodeSet.getNodeIdMap().get(item), FactBooleanValue.parse(false));
            					ast.addItemToSummaryList("NOT "+nodeSet.getNodeIdMap().get(item));
            				}
            				
               	 		falseAndList.add(item);
            			}
            			else if((nodeSet.getDependencyMatrix().getDependencyType(targetNode.getNodeId(), item) & (DependencyType.getNot()|DependencyType.getKnown())) == (DependencyType.getNot()|DependencyType.getKnown()))
            			{
            				if(!ast.getWorkingMemory().containsKey("NOT KNOWN "+nodeSet.getNodeIdMap().get(item)))
            				{
            					ast.setFact("NOT KNOWN "+nodeSet.getNodeIdMap().get(item), FactBooleanValue.parse(false));
            					ast.addItemToSummaryList("NOT KNOWN "+nodeSet.getNodeIdMap().get(item));
            				}
            				
               	 		falseAndList.add(item);
            			}
            		}
            });
            
            if(falseAndList.size() > 0)
            {
	    	        	isAnyAndDependencyFalse = true;
	    	        	andChildDependencies.stream().forEachOrdered(i -> {
	    	        		falseAndList.stream().forEachOrdered(f -> {
	    	        			if(i != f)
	    	        			{
	    	        				trimDependency(node, i);
	    	        			}
	    	        		});
	    	        	});
            }
            else if(andChildDependencies.size() == 0)
            {
            		isAnyAndDependencyFalse = true;
            }
        }
         
        		 
       
        return isAnyAndDependencyFalse;
    }
    
    
    public boolean isAllAndDependencyTrue(Node node, List<Integer> andChildDependencies)
    {
        boolean isAllAndTrue = false;
        targetNode = node;

        	List<Integer> determinedTrueAndChildDependencies = new ArrayList<>();
        andChildDependencies.stream().forEachOrdered(item -> {
        	if(ast.isInclusiveList(nodeSet.getNodeIdMap().get(item))
	        && ast.getWorkingMemory().containsKey(nodeSet.getNodeIdMap().get(item)) )
    		{
    			if(ast.getWorkingMemory().get(nodeSet.getNodeIdMap().get(item)).getValue().equals(true)
				&&((nodeSet.getDependencyMatrix().getDependencyType(targetNode.getNodeId(), item)&DependencyType.getNot()) != DependencyType.getNot()))
    			{
    				determinedTrueAndChildDependencies.add(item);
    			}
    			else if((nodeSet.getDependencyMatrix().getDependencyType(targetNode.getNodeId(), item)&DependencyType.getKnown()) == DependencyType.getKnown()
					  &&((nodeSet.getDependencyMatrix().getDependencyType(targetNode.getNodeId(), item)&DependencyType.getNot()) != DependencyType.getNot()))
    			{
    				if(!ast.getWorkingMemory().containsKey("KNOWN "+nodeSet.getNodeIdMap().get(item)))
    				{
    					ast.setFact("KNOWN "+nodeSet.getNodeIdMap().get(item), FactBooleanValue.parse(false));
    					ast.addItemToSummaryList("KNOWN "+nodeSet.getNodeIdMap().get(item));
    				}
    				
    				determinedTrueAndChildDependencies.add(item);
    			}
    			else if(ast.getWorkingMemory().get(nodeSet.getNodeIdMap().get(item)).getValue().equals(false)
					  &&((nodeSet.getDependencyMatrix().getDependencyType(targetNode.getNodeId(), item)&DependencyType.getNot()) == DependencyType.getNot())
					  && ((nodeSet.getDependencyMatrix().getDependencyType(targetNode.getNodeId(), item)&DependencyType.getKnown()) != DependencyType.getKnown()))
    			{
    				if(!ast.getWorkingMemory().containsKey("NOT "+nodeSet.getNodeIdMap().get(item)))
    				{
    					ast.setFact("NOT "+nodeSet.getNodeIdMap().get(item), FactBooleanValue.parse(false));
    					ast.addItemToSummaryList("NOT "+nodeSet.getNodeIdMap().get(item));
    				}
    				
    				determinedTrueAndChildDependencies.add(item);
    			}
    		}
        	
        });
        
        if(andChildDependencies != null && determinedTrueAndChildDependencies.size() == andChildDependencies.size())
        {
        		isAllAndTrue = true;
        } 

       return isAllAndTrue;
    }

    public boolean isAllRelevantChildDependencyDetermined(Node node, List<Integer> allChildDependencies)
    {
        boolean isAllRelevantChildDependencyDetermined = false;
        targetNode = node;
        
        List<Integer> determinedAndOutDependencies = new ArrayList<>();
        allChildDependencies.stream().forEachOrdered(item ->{
	        														if(ast.getWorkingMemory().get(nodeSet.getNodeIdMap().get(item)) != null
	    																&&(nodeSet.getDependencyMatrix().getDependencyType(targetNode.getNodeId(), item) & (DependencyType.getNot()|DependencyType.getKnown())) == (DependencyType.getNot()|DependencyType.getKnown())
	    																&& !ast.getWorkingMemory().containsKey("NOT KNOWN "+nodeSet.getNodeIdMap().get(item)))
	        														{
	        															ast.setFact("NOT KNOWN "+nodeSet.getNodeIdMap().get(item), FactBooleanValue.parse(false));
	        															ast.addItemToSummaryList("NOT KNOWN "+nodeSet.getNodeIdMap().get(item));
	        														}
	        														else if(ast.getWorkingMemory().get(nodeSet.getNodeIdMap().get(item)) != null
	        																 &&ast.getWorkingMemory().get(nodeSet.getNodeIdMap().get(item)).getValue().equals(false)
	        																 &&(nodeSet.getDependencyMatrix().getDependencyType(targetNode.getNodeId(), item)&DependencyType.getNot()) == DependencyType.getNot()
	        																 && !ast.getWorkingMemory().containsKey("NOT "+nodeSet.getNodeIdMap().get(item)))
														        {
	        															ast.setFact("NOT "+nodeSet.getNodeIdMap().get(item), FactBooleanValue.parse(true));
	        															ast.addItemToSummaryList("NOT "+nodeSet.getNodeIdMap().get(item));
														        }
	        														else if(ast.getWorkingMemory().get(nodeSet.getNodeIdMap().get(item))  != null
	        																 &&(nodeSet.getDependencyMatrix().getDependencyType(targetNode.getNodeId(), item)&DependencyType.getKnown()) == DependencyType.getKnown()
	        																 && !ast.getWorkingMemory().containsKey("KNOWN "+nodeSet.getNodeIdMap().get(item)))
	        														{
	        															ast.setFact("KNOWN "+nodeSet.getNodeIdMap().get(item), FactBooleanValue.parse(true));
	        															ast.addItemToSummaryList("KNOWN "+nodeSet.getNodeIdMap().get(item));
	        														}
	        														else if(ast.getWorkingMemory().get(nodeSet.getNodeIdMap().get(item)) != null
	        																 &&ast.getWorkingMemory().get(nodeSet.getNodeIdMap().get(item)).getValue().equals(true)
	        																 &&(nodeSet.getDependencyMatrix().getDependencyType(targetNode.getNodeId(), item)&DependencyType.getNot()) == DependencyType.getNot()
	        																 && !ast.getWorkingMemory().containsKey("NOT "+nodeSet.getNodeIdMap().get(item)))
														        {
	        															ast.setFact("NOT "+nodeSet.getNodeIdMap().get(item), FactBooleanValue.parse(false));
	        															ast.addItemToSummaryList("NOT "+nodeSet.getNodeIdMap().get(item));
														        }
	        														
	        														
	        														if(ast.isInclusiveList(nodeSet.getNodeIdMap().get(item)) 
																	&& ast.getWorkingMemory().containsKey(nodeSet.getNodeIdMap().get(item)))
																{
	        															determinedAndOutDependencies.add(item);
																}
												        }
												       );
        
        
        if(allChildDependencies != null && determinedAndOutDependencies.size() == allChildDependencies.size())
        {
        		isAllRelevantChildDependencyDetermined = true;
        }


        return isAllRelevantChildDependencyDetermined;
    }


    public List<String> generateSortedSummaryList()
    {
	    	List<String> sortedSummaryList = new ArrayList<>();
		nodeSet.getNodeSortedList().stream().forEachOrdered(node ->{
			if(ast.getSummaryList().contains(node.getNodeName()))
			{
				sortedSummaryList.add(node.getNodeName());
			}
			if(ast.getSummaryList().contains("NOT "+node.getNodeName()))
			{
				sortedSummaryList.add("NOT "+node.getNodeName());
			}
			if(ast.getSummaryList().contains("KNOWN "+node.getNodeName()))
			{
				sortedSummaryList.add("KNOWN "+node.getNodeName());
			}
			if(ast.getSummaryList().contains("NOT KNOWN "+node.getNodeName()))
			{
				sortedSummaryList.add("NOT KNOWN "+node.getNodeName());
			}
		});
		
		ast.getSummaryList().stream().forEach(nodeName->{
			if(!sortedSummaryList.contains(nodeName))
			{
				sortedSummaryList.add(1, nodeName);
			}
		});
			
		return sortedSummaryList;
    }

//    public boolean allNeedsChildDetermined(Node parentNode, List<Integer> outDependency)
//    {
//	    	boolean allNeedsChildDetermined = false;
//	    	
//	    	int mandatoryAndDependencyType = DependencyType.getMandatory() | DependencyType.getAnd();
//	    	List<Integer> determinedList = outDependency.stream().filter(i -> (nodeSet.getDependencyMatrix().getDependencyMatrixArray()[parentNode.getNodeId()][i] & mandatoryAndDependencyType) == mandatoryAndDependencyType
//	    										&& ast.getWorkingMemory().containsKey(nodeSet.getNodeMap().get(nodeSet.getNodeIdMap().get(i)).getVariableName())).collect(Collectors.toList());
//	    	
//	    	if(outDependency.size() == determinedList.size())
//	    	{
//	    		allNeedsChildDetermined = true;
//	    	}    	
//	    	
//	    	return allNeedsChildDetermined;
//    }
    

    
 
   
    

    /**
     * make a summary of the assessment rules and answers as a html document
     * using a template for the structure and replacing markers the values
     * @return html
     */
//    public String generateAssessmentSummaryFromTemplate()
//    {    		    	
//     	
//	    Map<String, String> map = new HashMap<>();
//	    
//	    
//	    int i = 0 ; 
//		for (String ruleName : ast.getInclusiveList())
//		{
//			String ruleState = ast.getWorkingMemory().get(ruleName);
//			if (ruleState != null) {
//				map.put("rules["+i+"].number", Integer.toString(i+1));//add question to array
//				map.put("rules["+i+"].question", ruleName);//add question to array
//				map.put("rules["+i+"].answer", ruleState);// add answer to array
//				i++;
//			}
//		}
//		map.put("rules[]", Integer.toString(i)); //specify size of array
//		String html = Document.getTemplate("assessment_summary.xml");
//		html = Document.replaceValuesInHtml(html, map);
//		
//	    return html;
//	}
//
//    
    
    /*
    this method is to reset 'workingMemory' list and 'inclusiveList'
    usage of this method will depend on a user. if a user wants to continue to assessment on a same veteran with same conditions
    then don't need to reset 'workingMemory' and 'inclusiveList' otherwise reset them.
    */
    public void resetWorkingMemoryAndInclusiveList()
    {
        if(!ast.getInclusiveList().isEmpty())
        {
        		ast.getInclusiveList().removeAll(ast.getInclusiveList());
        }
        if(!ast.getWorkingMemory().isEmpty())
        {
        		ast.getWorkingMemory().clear();
        }
    }
    
    
    /*
     * this is to generate Assessment Summary
     * need to modify this method to have correct summary
     */
	public ObjectNode[] generateAssessmentSummary()
	{ 
		List<ObjectNode> tempSummaryList = new ArrayList<>();
		this.getAssessmentState().getSummaryList().stream().forEachOrdered((item)->{
			ObjectNode objectNode = new ObjectMapper().createObjectNode();
			objectNode.put("nodeText", item);
			objectNode.put("nodeValue", this.getAssessmentState().getWorkingMemory().get(item).getValue().toString());
			tempSummaryList.add(objectNode);
		});
		
		return tempSummaryList.stream().toArray(ObjectNode[]::new);
    }
	
//    public String generateAssessmentSummary()
//    {    		    	
//    	StringBuilder htmlText = new StringBuilder();
//    	htmlText.append("<!DOCTYPE html>"+"\n"+
//    	                "<html>"+"\n"+
//    	                "<head><title></title></head>"+"\n"+
//    	                "<body><h3> Assessment Summary</h3>"+"\n");
//    	int summaryListSize = ast.getSummaryList().size();
//    	if(summaryListSize != 0 )
//    	{
//    		htmlText.append("<ol type =\"1\">"+"\n");
//    		
//    		for(int i = summaryListSize; i > 0; i--)
//        	{
//        		Rule rule = ast.getSummaryList().get(i);
//        		FactValue factValue = ast.getWorkingMemory().get(rule.getVariableName());
//        		FactValueType factValueType = factValue.getType();
//        		String printingValue = null;
//        		if(factValue != null)
//        		{
//        			if(factValueType.equals(FactValueType.BOOLEAN))
//            		{
//            			printingValue = Boolean.toString(((FactBooleanValue)factValue).getValue());
//            			printingValue = printingValue.toUpperCase();
//            		}
//            		else if(factValueType.equals(FactValueType.DATE))
//            		{
//            			printingValue =((FactDateValue)factValue).getValue().toString();
//            		}
//            		else if(factValueType.equals(FactValueType.DOUBLE))
//            		{
//            			printingValue = Double.toString(((FactDoubleValue)factValue).getValue());
//            		}
//            		else if(factValueType.equals(FactValueType.INTEGER))
//            		{
//            			printingValue = Integer.toString(((FactIntegerValue)factValue).getValue());
//            		}
//            		else if(factValueType.equals(FactValueType.LIST))
//            		{
//            			
//            		}
//        		}
//        		
//        		
//        		
//        		
//        		if(ruleState != null)
//        		{
//        			
//        			htmlText.append("<li>"+ruleName+" : "+ruleState+"</li>"+"\n");
//        		}
//        		
//        	}    
//        	htmlText.append("</ol>"+"\n");
//    	}
//    	htmlText.append("</body>"+"\n"+
//						"</html>"+"\n");
//    	
//     	
//    	return htmlText.toString();
//    }
    
    /*
     * this is to find a condition in a rule set with a given keyword string, that may contains multiple keywords
     */
    public List<String> findCondition(String keyword)
    {
    	int initialSize = nodeSet.getNodeSortedList().size();
    	List<String> conditionList = new ArrayList<>(initialSize);
    	List<String> questionList = new ArrayList<>(initialSize);
    	for(Node node: nodeSet.getNodeSortedList())
    	{
    		if(nodeSet.getDependencyMatrix().getToChildDependencyList(node.getNodeId()).isEmpty())
    		{
    			questionList.add(node.getNodeName());
    		}
    	}
    	
    	String[] keywordArray = keyword.split("\\W+"); // split the keyword by none word character including whitespace.
    	int keywordArrayLength = keywordArray.length;
    	int numberOfMatched = 0;
    	for(String ruleName: questionList)
    	{
    		numberOfMatched = 0;
    		for(int i = 0; i < keywordArrayLength; i++)
    		{
    			if(ruleName.contains(keywordArray[i]))
    			{
    				numberOfMatched++;
    			}
    		}
    		if(numberOfMatched == keywordArrayLength)
    		{
    			conditionList.add(ruleName);
    		}
    	}
    	
    	return conditionList;
    }
} public class ComparisonLine extends Node{
	
	private String operator;
	private String lhs;
	private FactValue rhs;
	
	public ComparisonLine(String childText, Tokens tokens)
	{
		super(childText, tokens);
		
	}
	
	@Override
	public void initialisation(String childText, Tokens tokens) {

		/*
		 * this line pattern is as (^[ML]+)(O)([MLNoDaDeHaUrlId]*$)
		 */
		
		this.nodeName = childText;		
		/*
		 * In javascript engine '=' operator means assigning a value, hence if the operator is '=' then it needs to be replaced with '=='. 
		 */
		int operatorIndex = tokens.tokensStringList.indexOf("O");
		this.operator = tokens.tokensList.get(operatorIndex).matches("=")?"==":tokens.tokensList.get(operatorIndex);
		
		if(operator.equals("=="))
		{
			this.variableName = childText.split("=")[0].trim();
		}
		else
		{
			this.variableName = childText.split(this.operator)[0].trim();
		}
		this.lhs = variableName;
		
		int tokensStringListSize = tokens.tokensStringList.size();
		String lastToken = tokens.tokensList.get(tokensStringListSize-1);
		String lastTokenString = tokens.tokensStringList.get(tokensStringListSize-1);
		this.setValue(lastTokenString, lastToken);
		this.rhs = this.value;
		
		
	}

	
	
	public String getRuleName()
	{
		return this.nodeName;
	}
	
	public String getLHS()
	{
		return this.lhs;
	}
	
	public FactValue getRHS()
	{
		return this.rhs;
	}
	
	@Override
	public LineType getLineType()
	{
		return LineType.COMPARISON;
	}
	
	@Override
	public FactValue selfEvaluate(HashMap<String, FactValue> workingMemory, ScriptEngine nashorn)
	{
		
		/*
		 * Negation type can only be used for this line type
		 * 
		 */		
		
		FactValue workingMemoryLhsValue = workingMemory.containsKey(this.variableName)?workingMemory.get(this.variableName):null;
		FactValue workingMemoryRhsValue = this.getRHS().getType().equals(FactValueType.STRING)?
											workingMemory.get(this.getRHS().getValue().toString())
											:
											this.getRHS();
		
		String script = "";
		
		/*
		 * There will NOT be the case of that workingMemoryRhsValue is null because the node must be in following format;
		 * - A = 12231 (int or double)
		 * - A = Adam sandler (String)
		 * - A = 11/11/1977 (Date)
		 * - A = 123123dfae1421412aer(Hash)
		 * - A = 1241414-12421312-142421312(UUID)
		 * - A = true(Boolean)
		 * - A = www.aiBrain.com(URL)
		 * - A = B(another variable) 
		 */
		
		/*
		 * if it is about date comparison then string of 'script' needs rewriting
		 */
		if((workingMemoryLhsValue!= null && workingMemoryLhsValue.getType().equals(FactValueType.DATE)) || (workingMemoryRhsValue!= null && workingMemoryRhsValue.getType().equals(FactValueType.DATE)))
		{
			switch(this.operator)
			{
				case ">":
					return FactValue.parse(((LocalDate)workingMemoryLhsValue.getValue()).isAfter(((LocalDate)workingMemoryRhsValue.getValue())));
				case ">=":
					return FactValue.parse(((LocalDate)workingMemoryLhsValue.getValue()).isAfter(((LocalDate)workingMemoryRhsValue.getValue())) || ((LocalDate)workingMemoryLhsValue.getValue()).isEqual(((LocalDate)workingMemoryRhsValue.getValue())));
				case "<":
					return FactValue.parse(((LocalDate)workingMemoryLhsValue.getValue()).isBefore(((LocalDate)workingMemoryRhsValue.getValue())));
				case "<=":
					return FactValue.parse(((LocalDate)workingMemoryLhsValue.getValue()).isBefore(((LocalDate)workingMemoryRhsValue.getValue())) || ((LocalDate)workingMemoryLhsValue.getValue()).isEqual(((LocalDate)workingMemoryRhsValue.getValue())));
			}
//			script = "new Date("+((FactDateValue)workingMemoryLhsValue).getValue().getYear()+"/"+((FactDateValue)workingMemoryLhsValue).getValue().getMonthValue()+"/"+((FactDateValue)workingMemoryLhsValue).getValue().getDayOfMonth()+")"+operator+"new Date("+((FactDateValue)workingMemoryRhsValue).getValue().getYear()+"/"+((FactDateValue)workingMemoryRhsValue).getValue().getMonthValue()+"/"+((FactDateValue)workingMemoryRhsValue).getValue().getDayOfMonth()+");" ;
		}
		else if(workingMemoryLhsValue != null && (workingMemoryLhsValue.getType().equals(FactValueType.DECIMAL) || workingMemoryLhsValue.getType().equals(FactValueType.DOUBLE) 
				|| workingMemoryLhsValue.getType().equals(FactValueType.INTEGER) || workingMemoryLhsValue.getType().equals(FactValueType.NUMBER)))
		{
			script = workingMemoryLhsValue.getValue().toString()+operator+workingMemoryRhsValue.getValue().toString() ;
		}
		else
		{
			if(workingMemoryRhsValue != null && workingMemoryLhsValue != null)
			{
				script = "'"+workingMemoryLhsValue.getValue().toString()+"' "+operator+" '"+workingMemoryRhsValue.getValue().toString()+"'" ;
			}
			
		}
		boolean result;
		FactValue fv = null;
		if(workingMemoryRhsValue != null && workingMemoryLhsValue != null)
		{
			try {
				result = (boolean) nashorn.eval(script);
				fv = FactValue.parse(result);
			} catch (ScriptException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
				
		return fv;
	}
} public class IterateLine extends Node {

	private String numberOfTarget;
	private NodeSet iterateNodeSet;
	private String givenListName;
	private int givenListSize;
	private InferenceEngine iterateIE;

	

	public IterateLine(String childText, Tokens tokens)
	{
		super(childText, tokens);
		givenListSize = 0;
		
	}

	public String getGivenListName() {
		return this.givenListName;
	}
	
	public String getNumberOfTarget() {
		return this.numberOfTarget;
	}
	public NodeSet createIterateNodeSet(NodeSet parentNodeSet)
	{

		DependencyMatrix parentDM = parentNodeSet.getDependencyMatrix();
		HashMap<String, Node> parentNodeMap = parentNodeSet.getNodeMap();
		HashMap<Integer, String> parentNodeIdMap = parentNodeSet.getNodeIdMap();
		
		
		HashMap<String, Node> thisNodeMap = new HashMap<>();
		HashMap<Integer, String> thisNodeIdMap = new HashMap<>();
		List<Dependency> tempDependencyList = new ArrayList<>();
		NodeSet newNodeSet = new NodeSet();

		thisNodeMap.put(this.nodeName, this);
		thisNodeIdMap.put(this.nodeId, this.nodeName);
		IntStream.range(1, this.givenListSize+1).forEachOrdered(nTh -> {
			parentDM.getToChildDependencyList(this.nodeId).stream().forEach((item)->{
				if(this.getNodeId()+1 != item) // not first question id
				{
					Node tempChildNode = parentNodeMap.get(parentNodeIdMap.get(item));
					LineType lt = tempChildNode.getLineType();
					
					Node tempNode = null;
					String nextNThInString = ordinal(nTh);

					if(lt.equals(LineType.VALUE_CONCLUSION))
					{
	    					tempNode = new ValueConclusionLine(nextNThInString+" "+this.getVariableName()+" "+tempChildNode.getNodeName(), tempChildNode.getTokens());
					}
    				else if(lt.equals(LineType.COMPARISON))
    				{
    					tempNode = new ComparisonLine(nextNThInString+" "+this.getVariableName()+" "+tempChildNode.getNodeName(), tempChildNode.getTokens());
    					FactValue tempNodeFv = ((ComparisonLine)tempNode).getRHS(); 
    					if(tempNodeFv.getType().equals(FactValueType.STRING))
    					{
    						FactValue tempFv = FactValue.parse(nextNThInString+" "+this.getVariableName()+" "+tempNodeFv.getValue());
    						tempNode.setValue(tempFv);
    					}
    				}
    				else if(lt.equals(LineType.EXPR_CONCLUSION))
					{
						tempNode = new ExprConclusionLine(nextNThInString+" "+this.getVariableName()+" "+tempChildNode.getNodeName(), tempChildNode.getTokens());
					}

    				
					thisNodeMap.put(tempNode.getNodeName(), tempNode);
					thisNodeIdMap.put(tempNode.getNodeId(), tempNode.getNodeName());
					tempDependencyList.add(new Dependency(this, tempNode, parentDM.getDependencyType(this.nodeId, item)));
					
					createIterateNodeSetAux(parentDM, parentNodeMap, parentNodeIdMap, thisNodeMap, thisNodeIdMap, tempDependencyList, item, tempNode.getNodeId(), nextNThInString);

				}
				else // first question id
				{
					Node firstIterateQuestionNode = parentNodeSet.getNodeByNodeId(parentNodeSet.getDependencyMatrix().getToChildDependencyList(this.getNodeId()).stream().min((id1, id2) -> Integer.compare(id1, id2)).get());
					thisNodeMap.put(firstIterateQuestionNode.getNodeName(), firstIterateQuestionNode);
					thisNodeIdMap.put(item, firstIterateQuestionNode.getNodeName());
					tempDependencyList.add(new Dependency(this, firstIterateQuestionNode, parentDM.getDependencyType(this.nodeId, item)));
				}
			});			
		});
		
		int numberOfRules = Node.getStaticNodeId();
		int[][] dependencyMatrix = new int[numberOfRules][numberOfRules];
	
		
		tempDependencyList.forEach(dp -> {
			int parentId = dp.getParentNode().getNodeId();
			int childId = dp.getChildNode().getNodeId();
			int dpType = dp.getDependencyType();
			dependencyMatrix[parentId][childId] = dpType;
		});
		
		
		
		newNodeSet.setNodeIdMap(thisNodeIdMap);
		newNodeSet.setNodeMap(thisNodeMap);
		newNodeSet.setDependencyMatrix(new DependencyMatrix(dependencyMatrix));
		newNodeSet.setFactMap(parentNodeSet.getFactMap());
		newNodeSet.setNodeSortedList(TopoSort.dfsTopoSort(thisNodeMap, thisNodeIdMap, dependencyMatrix));
//		newNodeSet.getNodeSortedList().stream().forEachOrdered(item->System.out.println(item.getNodeId()+"="+item.getNodeName()));
		return newNodeSet;
	
	}
	
	public void createIterateNodeSetAux(DependencyMatrix parentDM, HashMap<String, Node> parentNodeMap, HashMap<Integer, String> parentNodeIdMap, HashMap<String, Node> thisNodeMap, HashMap<Integer, String> thisNodeIdMap, List<Dependency> tempDependencyList, int originalParentId, int modifiedParentId, String nextNThInString)
	{
		List<Integer> childDependencyList = parentDM.getToChildDependencyList(originalParentId);

		if(childDependencyList.size() > 0)
		{
			childDependencyList.stream().forEach((item)->{
				Node tempChildNode = parentNodeMap.get(parentNodeIdMap.get(item));
				LineType lt = tempChildNode.getLineType();
				
				Node tempNode = thisNodeMap.get(nextNThInString+" "+this.getVariableName()+" "+tempChildNode.getNodeName());
				if(tempNode == null)
				{
					if(lt.equals(LineType.VALUE_CONCLUSION))
					{
							tempNode = new ValueConclusionLine(nextNThInString+" "+this.getVariableName()+" "+tempChildNode.getNodeName(), tempChildNode.getTokens());
							
							if(parentNodeMap.get(parentNodeIdMap.get(originalParentId)).getLineType().equals(LineType.EXPR_CONCLUSION))
							{
								ExprConclusionLine exprTempNode = ((ExprConclusionLine)thisNodeMap.get(thisNodeIdMap.get(modifiedParentId)));
								String replacedString = exprTempNode.getEquation().getValue().toString().replaceAll(tempChildNode.getNodeName(), nextNThInString+" "+this.getVariableName()+" "+tempChildNode.getNodeName());
								exprTempNode.setValue(FactValue.parse(replacedString));
								exprTempNode.setEquation(FactValue.parse(replacedString));
							}
					}
					else if(lt.equals(LineType.COMPARISON))
					{
						tempNode = new ComparisonLine(nextNThInString+" "+this.getVariableName()+" "+tempChildNode.getNodeName(), tempChildNode.getTokens());
						FactValue tempNodeFv = ((ComparisonLine)tempNode).getRHS(); 
						if(tempNodeFv.getType().equals(FactValueType.STRING))
						{
							FactValue tempFv = FactValue.parse(nextNThInString+" "+this.getVariableName()+" "+tempNodeFv);
							tempNode.setValue(tempFv);
						}
					}
					else if(lt.equals(LineType.EXPR_CONCLUSION))
					{
						tempNode = new ExprConclusionLine(nextNThInString+" "+this.getVariableName()+" "+tempChildNode.getNodeName(), tempChildNode.getTokens());
					}
				}
				else
				{
					if(lt.equals(LineType.VALUE_CONCLUSION) && parentNodeMap.get(parentNodeIdMap.get(originalParentId)).getLineType().equals(LineType.EXPR_CONCLUSION))
					{
							
						ExprConclusionLine exprTempNode = ((ExprConclusionLine)thisNodeMap.get(thisNodeIdMap.get(modifiedParentId)));
						String replacedString = exprTempNode.getEquation().getValue().toString().replaceAll(tempChildNode.getNodeName(), nextNThInString+" "+this.getVariableName()+" "+tempChildNode.getNodeName());
						exprTempNode.setValue(FactValue.parse(replacedString));
						exprTempNode.setEquation(FactValue.parse(replacedString));
					}
				}
				
						
				thisNodeMap.put(tempNode.getNodeName(), tempNode);
				thisNodeIdMap.put(tempNode.getNodeId(), tempNode.getNodeName());
				tempDependencyList.add(new Dependency(thisNodeMap.get(thisNodeIdMap.get(modifiedParentId)), tempNode,parentDM.getDependencyType(originalParentId, item)));
				
				createIterateNodeSetAux(parentDM, parentNodeMap, parentNodeIdMap, thisNodeMap, thisNodeIdMap, tempDependencyList, item, tempNode.getNodeId(), nextNThInString);
			});
		}		
	}	
		
	public NodeSet getIterateNodeSet()
	{
		return this.iterateNodeSet;
	}
	
	/*
	 * this method is used when a givenList exists as a string
	 */
	public void iterateFeedAnswers(String givenJsonString, NodeSet parentNodeSet, AssessmentState parentAst, Assessment ass) // this method uses JSON object via jackson library
	{
/*
 * 		givenJsonString has to be in same format as Example otherwise the engine would NOT be able to enable 'IterateLine' node
 * 		--------------------------- "givenJsonString" Format ----------------------------
 *      
 *		String givenJsonString = "{
 *									\"iterateLineVariableName\":
 *									    [
 *										  {
 *											\"1st iterateLineVariableName\":
 *												{
 *												  \"1st iterateLineVariableName ruleNme1\":\"..value..\", 
 *												  \"1st iterateLineVariableName ruleNme2\":\"..value..\"
 *												}
 *										  },
 *										  {
 *											\"2nd iterateLineVariableName\":
 *												{
 *												  \"2nd iterateLineVariableName ruleName1\":\"..value..\",
 *												  \"2nd iterateLineVariableName ruleName2\":\"..value..\"}
 *										  }
 *										]
 *								 }";
 *
 *     -----------------------------  "givenJsonString" Example ----------------------------
 *     String givenJsonString = "{
 *									\"service\":
 *									    [
 *										  {
 *											\"1st service\":
 *												{
 *												  \"1st service period\":\"..value..\", 
 *												  \"1st service type\":\"..value..\"
 *												}
 *										  },
 *										  {
 *											\"2nd service\":
 *												{
 *												  \"2nd service period\":\"..value..\",
 *												  \"2nd service type\":\"..value..\"}
 *										  }
 *										]
 *								 }";
*/
		ObjectMapper mapper = new ObjectMapper();
	    try {
				JsonNode jsonObj = mapper.readTree(givenJsonString);
				Iterator<JsonNode> serviceListIterator = jsonObj.path(this.variableName).elements();
				List<JsonNode> serviceList = new ArrayList<>();
				serviceListIterator.forEachRemaining(serviceList::add);
				this.givenListSize = serviceList.size();
				
				if(this.iterateNodeSet == null)
				{
					this.iterateNodeSet = createIterateNodeSet(parentNodeSet);
					this.iterateIE = new InferenceEngine(this.iterateNodeSet);
					if(this.iterateIE.getAssessment() == null)
					{
						this.iterateIE.setAssessment(new Assessment(this.iterateNodeSet, this.getNodeName()));
					}
				} 
				
				while(!this.iterateIE.getAssessmentState().getWorkingMemory().containsKey(this.nodeName))
				{
					Node nextQuestionNode = getIterateNextQuestion(parentNodeSet, parentAst);
					String answer ="";
					HashMap<String,FactValueType> questionFvtMap = this.iterateIE.findTypeOfElementToBeAsked(nextQuestionNode);
					for(String question: this.iterateIE.getQuestionsFromNodeToBeAsked(nextQuestionNode))
					{
						answer = jsonObj.get(this.variableName)
								        .get(nextQuestionNode.getVariableName().substring(0, nextQuestionNode.getVariableName().lastIndexOf(this.variableName)+this.variableName.length()))
								        .get(nextQuestionNode.getVariableName())
								        .asText().trim();
						
						this.iterateIE.feedAnswerToNode(nextQuestionNode, question, answer, questionFvtMap.get(question), this.iterateIE.getAssessment());
					}
					
					HashMap<String,FactValue> iterateWorkingMemory = this.iterateIE.getAssessmentState().getWorkingMemory();
					HashMap<String,FactValue> parentWorkingMemory = parentAst.getWorkingMemory();
					
					transeferFactValue(iterateWorkingMemory, parentWorkingMemory);
					
				}
				
			
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	/*
	 * this method is used when a givenList does NOT exist
	 */
	public <T> void iterateFeedAnswers(Node targetNode, String questionName, T nodeValue, FactValueType nodeValueType, NodeSet parentNodeSet, AssessmentState parentAst, Assessment ass)
	{
		
		if(this.iterateNodeSet == null)
		{
			Node firstIterateQuestionNode = parentNodeSet.getNodeByNodeId(parentNodeSet.getDependencyMatrix().getToChildDependencyList(this.getNodeId()).stream().min((id1, id2) -> Integer.compare(id1, id2)).get());
			if(questionName.equals(firstIterateQuestionNode.getNodeName()))
			{
				this.givenListSize = Integer.parseInt(nodeValue.toString());
			}
			this.iterateNodeSet = createIterateNodeSet(parentNodeSet);
			this.iterateIE = new InferenceEngine(this.iterateNodeSet);
			if(this.iterateIE.getAssessment() == null)
			{
				this.iterateIE.setAssessment(new Assessment(this.iterateNodeSet, this.getNodeName()));
			}
		} 
		this.iterateIE.getAssessment().setNodeToBeAsked(targetNode);
		this.iterateIE.feedAnswerToNode(targetNode, questionName, nodeValue, nodeValueType, this.iterateIE.getAssessment());
		
		HashMap<String,FactValue> iterateWorkingMemory = this.iterateIE.getAssessmentState().getWorkingMemory();
		HashMap<String,FactValue> parentWorkingMemory = parentAst.getWorkingMemory();

		transeferFactValue(iterateWorkingMemory, parentWorkingMemory);
		
	}
	
	public void transeferFactValue(HashMap<String, FactValue> workingMemory_one, HashMap<String, FactValue> workingMemory_two)
	{
		workingMemory_one.keySet().stream().forEach(key->{
			FactValue tempFv = workingMemory_one.get(key);
			if(!workingMemory_two.containsKey(key))
			{
				workingMemory_two.put(key, tempFv);
			}
		});
	}
	
	public Node getIterateNextQuestion(NodeSet parentNodeSet, AssessmentState parentAst)
	{
		if(this.iterateNodeSet == null && this.givenListSize != 0)
		{
			this.iterateNodeSet = createIterateNodeSet(parentNodeSet);
			this.iterateIE = new InferenceEngine(this.iterateNodeSet);
			if(this.iterateIE.getAssessment() == null)
			{
				this.iterateIE.setAssessment(new Assessment(this.iterateNodeSet, this.getNodeName()));
			}
		}
		
    		Node firstIterateQuestionNode = parentNodeSet.getNodeByNodeId(parentNodeSet.getDependencyMatrix().getToChildDependencyList(this.getNodeId()).stream().min((id1, id2) -> Integer.compare(id1, id2)).get());
    		Node questionNode = null;
    		
		if(!parentAst.getWorkingMemory().containsKey(this.value.getValue().toString())) // a list is not given yet so that the engine needs to find out more info.
		{

	    		if(!parentAst.getWorkingMemory().containsKey(firstIterateQuestionNode.getNodeName()))
	    		{
	    			questionNode = firstIterateQuestionNode;
	    		}
	    		else 
	    		{
	    			if(!canBeSelfEvaluated(parentAst.getWorkingMemory()))
	    			{
	    				questionNode = this.iterateIE.getNextQuestion(this.iterateIE.getAssessment());
	    			}
	    		}
		}
		
    		
    		return questionNode;
	}
	

	public int findNTh(HashMap<String, FactValue> workingMemory)
	{
		return IntStream.range(1, this.givenListSize+1)
						.filter(item-> workingMemory.get(ordinal(item)+" "+this.variableName) != null)
						.boxed().collect(Collectors.toList()).size();
	}
	
	public String ordinal(int i) 
	{
	    String[] sufixes = new String[] { "th", "st", "nd", "rd", "th", "th", "th", "th", "th", "th" };
	    switch (i % 100) {
		    case 11:
		    case 12:
		    case 13:
		        return i + "th";
		    default:
		        return i + sufixes[i % 10];
	    }
	}
	
	@Override
	public void initialisation(String parentText, Tokens tokens) {
		this.nodeName = parentText;
		this.numberOfTarget = tokens.tokensList.get(0);
		this.variableName = tokens.tokensList.get(1);
		int tokensStringListSize = tokens.tokensStringList.size();
		String lastToken = tokens.tokensList.get(tokensStringListSize-1); //this is a givenListName.
		String lastTokenString = tokens.tokensStringList.get(tokensStringListSize-1);
		this.setValue(lastTokenString, lastToken);
		this.givenListName = lastToken;
		
	}



	@Override
	public LineType getLineType() {
		return LineType.ITERATE;
	}

	public boolean canBeSelfEvaluated(HashMap<String, FactValue> workingMemory) {
		boolean canBeSelfEvaluated = false;
		if(this.iterateIE != null)
		{
			List<Integer> numberOfDeterminedSecondLevelNode = this.iterateIE.getNodeSet().getDependencyMatrix().getToChildDependencyList(this.nodeId)
																						        .stream()
																							    .filter(i -> i != this.nodeId+1)
																							    .filter(id -> workingMemory.get(this.iterateIE.getNodeSet().getNodeIdMap().get(id)) !=null 
																			    								  && workingMemory.get(this.iterateIE.getNodeSet().getNodeIdMap().get(id)).getValue() != null)
																							    .collect(Collectors.toList());


			if(this.givenListSize == numberOfDeterminedSecondLevelNode.size() && this.iterateIE.hasAllMandatoryChildAnswered(this.nodeId))
			{
				canBeSelfEvaluated = true;
			}
		}
		
		
		
		return canBeSelfEvaluated;
	}

	@Override
	public FactValue selfEvaluate(HashMap<String, FactValue> workingMemory, ScriptEngine nashorn) {
		
		int numberOfTrueChildren = numberOfTrueChildren(workingMemory);
		int sizeOfGivenList = this.givenListSize;
		FactBooleanValue<?> fbv = null;
		switch(this.numberOfTarget)
		{
			case "ALL":
				if(numberOfTrueChildren == sizeOfGivenList)
				{
					fbv = FactBooleanValue.parse(true);
				}
				else
				{
					fbv = FactBooleanValue.parse(false);
				}
				break;
			case "NONE":
				if(numberOfTrueChildren == 0)
				{
					fbv = FactBooleanValue.parse(true);
				}
				else
				{
					fbv = FactBooleanValue.parse(false);
				}
				break;
			case "SOME":
				if(numberOfTrueChildren > 0)
				{
					fbv = FactBooleanValue.parse(true);
				}
				else
				{
					fbv = FactBooleanValue.parse(false);
				}
				break;
			default:
				if(numberOfTrueChildren == Integer.parseInt(this.numberOfTarget))
				{
					fbv = FactBooleanValue.parse(true);
				}
				else
				{
					fbv = FactBooleanValue.parse(false);
				}
				break;
		}
		return fbv;
	}
	
	public int numberOfTrueChildren(HashMap<String, FactValue> workingMemory)
	{		
		return this.iterateIE.getNodeSet().getDependencyMatrix().getToChildDependencyList(this.nodeId).stream()
																							 .filter(i -> i != this.nodeId+1)
																							 .filter(id -> workingMemory.get(this.iterateIE.getNodeSet().getNodeIdMap().get(id)).getValue().toString().toLowerCase().equals("true"))
																							 .collect(Collectors.toList()).size();

	}
	
	
} public enum LineType {
	META, VALUE_CONCLUSION, EXPR_CONCLUSION, COMPARISON, ITERATE, WARNING
} public abstract class Node {
	
	
	protected static int staticNodeId = 0;
	protected int nodeId;
	protected String nodeName;
	protected int nodeLine;
	protected String variableName;
	protected FactValue value;
    protected Tokens tokens;
    
	public Node(String parentText, Tokens tokens)
	{
		nodeId = staticNodeId;
		staticNodeId++;
		this.tokens = tokens;

		initialisation(parentText, tokens);	
	}
	
	public abstract void initialisation(String parentText, Tokens tokens);
	public abstract LineType getLineType();
	public abstract FactValue selfEvaluate(HashMap<String, FactValue> workingMemory, ScriptEngine nashorn);
	
	public void setNodeLine(int nodeLine)
	{
		this.nodeLine = nodeLine;
	}
	public int getNodeLine()
	{
		return this.nodeLine;
	}
	
	public static int getStaticNodeId()
	{
		return staticNodeId;
	}
	public int getNodeId()
	{
		return this.nodeId;
	}
	public String getNodeName()
	{
		return this.nodeName;
	}
	
	public Tokens getTokens()
	{
		return this.tokens;
	}

	public String getVariableName()
	{
		return variableName;
	}
	public void setNodeVariable(String newVariableName)
	{
		this.variableName = newVariableName;
	}
	
	public FactValue getFactValue()
	{
		return this.value;
	}
	protected void setValue(String lastTokenString, String lastToken)
	{
		switch(lastTokenString)
		{
			case "No":
				this.value = FactValue.parse(Integer.parseInt(lastToken));
				break;
			case "Do":
				this.value = FactValue.parse(Double.parseDouble(lastToken));
				break;
			case "Da":
				this.value = FactValue.parse(LocalDate.parse(lastToken, DateTimeFormatter.ofPattern("d/M/yyyy")));
				break;
			case "Url":
				this.value = FactValue.parseURL(lastToken);
				break;
			case "Id":
				this.value = FactValue.parseUUID(lastToken);
				break;
			case "Ha":
				this.value = FactValue.parseHash(lastToken);
				break;
			case "Q":
				this.value = FactValue.parseDefiString(lastToken);
				break;
			case "L":
			case "M":
			case "U":
			case "C":
				if(this.isBoolean(lastToken))
				{
					this.value = lastToken.equalsIgnoreCase("false")? FactValue.parse(false): FactValue.parse(true);
				}
				else
				{
					Pattern defiStringPattern = Pattern.compile("(^[\'\"])(.*)([\'\"]$)");
					Matcher matcher = defiStringPattern.matcher(lastToken);
					if(matcher.find())
					{
						String newS = matcher.group(2);
						this.value = FactValue.parseDefiString(newS);
					}
					else
					{
						this.value = FactValue.parse(lastToken);
					}
				}
				break;
		}
	}
	public void setValue(FactValue fv)
	{
		this.value = fv;
	}
	protected boolean isBoolean(String str)
	{
		return str.matches("[FfAaLlSsEe]+")||str.matches("[TtRrUuEe]+")? true: false;
	}
	
	protected boolean isInteger(String str)
	{
		return str.equals("No")? true: false;
	}
	
	protected boolean isDouble(String str)
	{
		return str.equals("De")?true: false;
	}
	
	protected boolean isDate(String str)
	{
		 return str.equals("Da")? true: false;
	}
	
	protected boolean isURL(String str)
	{
		return str.equals("Url")? true: false;
	}
	
	protected boolean isHash(String str)
	{
		return str.equals("Ha")? true: false;
	}
	
	protected boolean isUUID(String str)
	{
		return str.equals("Id")? true: false;
	}
} public class RuleSetParser implements IScanFeeder {

//	enum LineType {META, VALUE_CONCLUSION, EXPR_CONCLUSION, COMPARISON, WARNING}
	/*
	 * patterns are as follows;
	 * U : upper case
	 * L : lower case
	 * M : mixed case
	 * No: number
	 * Da: date
	 * Ha: hash
	 * Url: url
	 * Id: UUID
	 * C : CALC (this is a keyword)
	 * De: decimal
	 * Q : quotation mark
	 * 
	 */
	
	final Pattern META_PATTERN_MATCHER = Pattern.compile("(^U)([MLU]*)([(No)(Da)ML(De)(Ha)(U(rl)?)(Id)]*$)");
	final Pattern VALUE_MATCHER = Pattern.compile("(^[LM]+)(U)?([MLQ(No)(Da)(De)(Ha)(Url)(Id)]*$)(?!C)");
	final Pattern EXPRESSION_CONCLUSION_MATCHER = Pattern.compile("(^[LM(Da)]+)(U)(C)");
	final Pattern COMPARISON_MATCHER = Pattern.compile("(^[MLU(Da)]+)(O)([MLUQ(No)(Da)(De)(Ha)(Url)(Id)]*$)");
	final Pattern ITERATE_MATCHER = Pattern.compile("(^[MLU(No)(Da)]+)(I)([MLU]+$)");
	final Pattern WARNING_MATCHER = Pattern.compile("WARNING");
	LineType matchTypes[] = LineType.values();
	NodeSet nodeSet = new NodeSet();
	List<Dependency> dependencyList = new ArrayList<>();
	
	@Override
	public void handleParent(String parentText, int lineNumber) {
		
		Node data = nodeSet.getNodeMap().get(parentText);
		
		if(data == null)
		{
			
//			valueConclusionMatcher = Pattern.compile("(^[LM]+)(U)?([MLQ(No)(Da)(De)(Ha)(Url)(Id)]*$)(?!C)"); // parent statement must not have operators in the middle of the statement, hence there is no 'O' of Token.tokenString in the regex.
			 
			 
			Tokens tokens = Tokenizer.getTokens(parentText);
			
			Pattern matchPatterns[] = {META_PATTERN_MATCHER, VALUE_MATCHER, EXPRESSION_CONCLUSION_MATCHER, WARNING_MATCHER};
			Pattern p;
			Matcher matcher;
			for(int i = 0; i < matchPatterns.length; i++) {
				
				p =  matchPatterns[i];
				matcher = p.matcher(tokens.tokensString);
				if(matcher.find() == true) {
					switch(i) {
						case 3:  //warningMatcher case
							handleWarning(parentText);
							break;
						case 0:  //metaMatcher case
							data = new MetadataLine(parentText, tokens);
							
							if(data.getFactValue().getValue().equals("WARNING"))
							{
								handleWarning(parentText);
							}
							break;
						case 1:  //valueConclusionMatcher case
							data = new ValueConclusionLine(parentText, tokens);
							
							if(matcher.group(2) != null 
								|| (tokens.tokensString.equals("L") || tokens.tokensString.equals("LM") || tokens.tokensString.equals("ML") || tokens.tokensString.equals("M")))
							{
								String variableName = data.getVariableName();
								Node tempNode = data;
								/*
								 * following lines are to look for any nodes having a its nodeName with any operators due to the reason that
								 * the node could be used to define a node previously used as a child node for other nodes
								 */
								List<String> possibleParentNodeKeyList = nodeSet.getNodeMap().keySet().stream().filter(key -> key.matches("(.+)?(\\s[<>=]+\\s?)?("+variableName+")(\\s[<>=]+)*(.(?!(IS)))*(.*(IS IN LIST).*)*")).collect(Collectors.toList());
								if(!possibleParentNodeKeyList.isEmpty())
								{
									possibleParentNodeKeyList.stream().forEachOrdered(item -> {
										this.dependencyList.add(new Dependency(nodeSet.getNodeMap().get(item), tempNode, DependencyType.getOr())); //Dependency Type :OR
									});
								}
							}	
							if(data.getFactValue().getValue().equals("WARNING"))
							{
								handleWarning(parentText);
							}
							break;
						case 2: //exprConclusionMatcher case 
							data = new ExprConclusionLine(parentText, tokens);
							
							String variableName = data.getVariableName();
							Node tempNode = data;
							/*
							 * following lines are to look for any nodes having a its nodeName with any operators due to the reason that
							 * the exprConclusion node could be used to define another node as a child node for other nodes if the variableName of exprConclusion node is mentioned somewhere else.
							 * However, it is excluding nodes having 'IS' keyword because if it has the keyword then it should have child nodes to define the node otherwise the entire rule set has NOT been written in correct way
							 */
							List<String> possibleParentNodeKeyList = nodeSet.getNodeMap().keySet().stream().filter(key -> key.matches("(.+)?(\\s[<>=]+\\s?)?("+variableName+")(\\s[<>=]+)*(.(?!(IS)))*(.*(IS IN LIST).*)*")).collect(Collectors.toList());
							if(!possibleParentNodeKeyList.isEmpty())
							{
								possibleParentNodeKeyList.stream().forEachOrdered(item -> {
									this.dependencyList.add(new Dependency(nodeSet.getNodeMap().get(item), tempNode, DependencyType.getOr())); //Dependency Type :OR
								});
							}
							if(data.getFactValue().getValue().equals("WARNING"))
							{
								handleWarning(parentText);
							}
							break;
						default:
							handleWarning(parentText);
							break;				
					
					}
					data.setNodeLine(lineNumber);
					if(data.getLineType().equals(LineType.META))
					{
						if(((MetadataLine)data).getMetaType().equals(MetaType.INPUT))
						{
							this.nodeSet.getInputMap().put(data.getVariableName(), data.getFactValue());
						}
						else if(((MetadataLine)data).getMetaType().equals(MetaType.FIXED))
						{
							this.nodeSet.getFactMap().put(data.getVariableName(), data.getFactValue());
						}
					}
					else
					{
						this.nodeSet.getNodeMap().put(data.getNodeName(), data);
						this.nodeSet.getNodeIdMap().put(data.getNodeId(), data.getNodeName());
					}
					break;
				}
			}	 
		}			
	}

	@SuppressWarnings("unlikely-arg-type")
	@Override
	public void handleChild(String parentText, String childText, String firstKeywordsGroup, int lineNumber) {
		/*
		 * the reason for using '*' at the last group of pattern within comparison is that 
		 * the last group contains No, Da, De, Ha, Url, Id. 
		 * In order to track more than one character within the square bracket of last group '*'(Matches 0 or more occurrences of the preceding expression) needs to be used.
		 * 
		 */
		int dependencyType = 0; 
		
		// is 'ITEM' child line
		if(childText.matches("(ITEM)(.*)"))
		{
			if(!parentText.matches("(.*)(AS LIST)"))
			{
				handleWarning(childText);
				return;
			}
			
			// is an indented item child
			childText = childText.replaceFirst("ITEM", "").trim();
			MetaType metaType = null;
			if(parentText.matches("^(INPUT)(.*)"))
			{
				metaType = MetaType.INPUT;
			}
			else if(parentText.matches("^(FIXED)(.*)"))
			{
				metaType = MetaType.FIXED;
			}
			handleListItem(parentText, childText, metaType);
		}
		else  // is 'A-statement', 'A IS B', 'A <= B', or 'A IS CALC (B * C)' child line
		{
			if(firstKeywordsGroup.matches("^(AND\\s?)(.*)")) 
			{
				dependencyType = handleNotKnownManOptPos(firstKeywordsGroup, DependencyType.getAnd()); // 8-AND | 1-KNOWN? 2-NOT? 64-MANDATORY? 32-OPTIONALLY? 16-POSSIBLY? 
			}
			else if(firstKeywordsGroup.matches("^(OR\\s?)(.*)"))
			{
				dependencyType = handleNotKnownManOptPos(firstKeywordsGroup, DependencyType.getOr()); // 4-OR | 1-KNOWN? 2-NOT? 64-MANDATORY? 32-OPTIONALLY? 16-POSSIBLY? 
			}
			else if(firstKeywordsGroup.matches("^(WANTS)"))
			{
				dependencyType = DependencyType.getOr(); // 4-OR
			}
			else if(firstKeywordsGroup.matches("^(NEEDS)"))
			{
				dependencyType = DependencyType.getMandatory() | DependencyType.getAnd();  //  8-AND | 64-MANDATORY
			}
			
			
			/*
			 * the keyword of 'AND' or 'OR' should be removed individually. 
			 * it should NOT be removed by using firstToken string in Tokens.tokensList.get(0)
			 * because firstToken string may have something else. 
			 * (e.g. string: 'AND NOT ALL Males' name should sound Male', then Token string will be 'UMLM', and 'U' contains 'AND NOT ALL'.
			 * so if we used 'firstToken string' to remove 'AND' in this case as 'string.replace(firstTokenString)' 
			 * then it will remove 'AND NOT ALL' even we only need to remove 'AND' 
			 * 
			 */
			
			
			Node data = nodeSet.getNodeMap().get(childText);  
			Tokens tokens = Tokenizer.getTokens(childText);
			if(data == null)
			{
//				valueConclusionMatcher =Pattern.compile("(^U)([LMU(Da)(No)(De)(Ha)(Url)(Id)]+$)"); // child statement for ValueConclusionLine starts with AND(OR), AND MANDATORY(OPTIONALLY, POSSIBLY) or AND (MANDATORY) (NOT) KNOWN
							
				Pattern matchPatterns[] = { VALUE_MATCHER, COMPARISON_MATCHER, ITERATE_MATCHER, EXPRESSION_CONCLUSION_MATCHER, WARNING_MATCHER};
				
				
				Pattern p;
				Matcher matcher;
				Node tempNode;
				List<String> possibleChildNodeKeyList;
				
				for(int i = 0; i < matchPatterns.length; i++) {
					p = matchPatterns[i];
					matcher = p.matcher(tokens.tokensString);
					
					if(matcher.find() == true)
					{
						switch(i)
						{
							case 4:  // warningMatcher case
								handleWarning(childText);
								break;
							case 0:  // valueConclusionMatcher case
								data = new ValueConclusionLine(childText, tokens);
								
								tempNode = data;
								possibleChildNodeKeyList = nodeSet.getNodeMap().keySet().stream().filter(key -> key.matches("(^"+tempNode.getVariableName()+")(.(IS(?!(\\sIN LIST))).*)*")).collect(Collectors.toList());
														
								if(!possibleChildNodeKeyList.isEmpty())
								{
									possibleChildNodeKeyList.stream().forEachOrdered(item -> {
										this.dependencyList.add(new Dependency(tempNode, nodeSet.getNodeMap().get(item), DependencyType.getOr())); //Dependency Type :OR
									});
								}
								
								if(data.getFactValue().getValue().equals("WARNING"))
								{
									handleWarning(parentText);
								}
								break;
							case 1:  // comparisonMatcher case
								data = new ComparisonLine(childText, tokens);
								
								FactValueType rhsType = ((ComparisonLine)data).getRHS().getType();
								String rhsString = ((ComparisonLine)data).getRHS().getValue().toString();
								String lhsString = ((ComparisonLine)data).getLHS();
								tempNode = data;
								possibleChildNodeKeyList = rhsType.equals(FactValueType.STRING)? 
														nodeSet.getNodeMap().keySet().stream().filter(key -> key.matches("(^"+lhsString+")(.(IS(?!(\\sIN LIST))).*)*")|| key.matches("(^"+rhsString+")(.(IS(?!(\\sIN LIST))).*)*")).collect(Collectors.toList())
														:
														nodeSet.getNodeMap().keySet().stream().filter(key -> key.matches("(^"+lhsString+")(.(IS(?!(\\sIN LIST))).*)*")).collect(Collectors.toList());

								if(!possibleChildNodeKeyList.isEmpty())
								{
									possibleChildNodeKeyList.stream().forEachOrdered(item -> {
										this.dependencyList.add(new Dependency(tempNode, nodeSet.getNodeMap().get(item), DependencyType.getOr())); //Dependency Type :OR
									});
								}
								
								if(data.getFactValue().getValue().equals("WARNING"))
								{
									handleWarning(parentText);
								}
								break;
							case 2:  // comparisonMatcher case
								data = new IterateLine(childText, tokens);
								if(data.getFactValue().getValue().equals("WARNING"))
								{
									handleWarning(parentText);
								}
								break;
							case 3: //exprConclusionMatcher case
								data = new ExprConclusionLine(childText, tokens);
								
								/*
								 * In this case, there is no mechanism to find possible parent nodes.
								 * I have brought 'local variable' concept for this case due to it may massed up with structuring node dependency tree with topological sort
								 * If ExprConclusion node is used as a child, then it means that this node is a local node which has to be strictly bound to its parent node only.  
								 */
								
								if(data.getFactValue().getValue().equals("WARNING"))
								{
									handleWarning(parentText);
								}
								break;
								
						}
						data.setNodeLine(lineNumber);
						this.nodeSet.getNodeMap().put(data.getNodeName(), data);
						this.nodeSet.getNodeIdMap().put(data.getNodeId(), data.getNodeName());
						break;
					}
				}
			}
			
			this.dependencyList.add(new Dependency(this.nodeSet.getNode(parentText),data,dependencyType));
		}
	}
	
	
	@Override
	public void handleListItem(String parentText, String itemText, MetaType metaType) {
		Tokens tokens = Tokenizer.getTokens(itemText);
		FactValue fv;
		if(tokens.tokensString.equals("Da"))
		{
			DateTimeFormatter formatter = DateTimeFormatter.ofPattern("d/M/yyyy");
     		LocalDate factValueInDate = LocalDate.parse(itemText, formatter);
     		fv = FactValue.parse(factValueInDate);
		}
		else if(tokens.tokensString.equals("De"))
		{
			fv = FactValue.parse(Double.parseDouble(itemText));
		}
		else if(tokens.tokensString.equals("No"))
		{
			fv = FactValue.parse(Integer.parseInt(itemText));
		}
		else if(tokens.tokensString.equals("Ha"))
		{
			fv = FactValue.parseHash(itemText);
		}
		else if(tokens.tokensString.equals("Url"))
		{
			fv = FactValue.parseURL(itemText);
		}
		else if(tokens.tokensString.equals("Id"))
		{
			fv = FactValue.parseUUID(itemText);
		}
		else if(itemText.matches("FfAaLlSsEe")||itemText.matches("TtRrUuEe"))
		{
			fv =FactValue.parse(Boolean.parseBoolean(itemText));
		}
		else
		{
			fv = FactValue.parse(itemText);
		}
		String stringToGetFactValue = (parentText.substring(5, parentText.indexOf("AS"))).trim();
		if(metaType.equals(MetaType.INPUT))
		{
			((FactListValue<?>)this.nodeSet.getInputMap().get(stringToGetFactValue)).getValue().add(fv);
		}
		else if(metaType.equals(MetaType.FIXED))
		{
			((FactListValue<?>)this.nodeSet.getFactMap().get(stringToGetFactValue)).getValue().add(fv);
		}
	}
	
	
	@Override
	public NodeSet getNodeSet()
	{
		return this.nodeSet;
	}
	
	@Override
	public String handleWarning(String parentText)
	{
		return parentText+": rule format is not matched. Please check the format again";
	}
	
	/*
	 * this method is to create virtual nodes where a certain node has 'AND' or 'MANDATORY_AND', and 'OR' children at the same time.
	 * when a virtual node is created, all 'AND' children should be connected to the virtual node as 'AND' children
	 * and the virtual node should be a 'OR' child of the original parent node 
	 */
	public HashMap<String, Node> handlingVirtualNode(List<Dependency> dependencyList)
	{
		
		HashMap<String, Node> virtualNodeMap = new HashMap<>();
		

		nodeSet.getNodeMap().values().stream().forEachOrdered((node) ->{
			virtualNodeMap.put(node.getNodeName(), node);
			List<Dependency> dpList= dependencyList.stream()
							   .filter(dp -> node.getNodeName().equals(dp.getParentNode().getNodeName()))
							   .collect(Collectors.toList());
			
			/*
			 * need to handle Mandatory, optionally, possibly NodeOptions
			 */
			int and = 0;
			int mandatoryAnd = 0;
			int or = 0;
			if(!dpList.isEmpty())
			{
				for(Dependency dp: dpList) //can this for each loop be converted to dpList.stream().forEachOrdered() ?
				{
					if((dp.getDependencyType() & DependencyType.getAnd()) == DependencyType.getAnd())
					{
						and++;
						if(dp.getDependencyType() == (DependencyType.getMandatory() | DependencyType.getAnd()))
						{
							mandatoryAnd++;
						}
					}
					else if((dp.getDependencyType() & DependencyType.getOr()) == DependencyType.getOr())
					{
						or++;
					}
				}
				boolean hasAndOr = (and>0 && or>0)? true:false;  
				if(hasAndOr)
				{
					
					String parentNodeOfVirtualNodeName = node.getNodeName();
					Node virtualNode = new ValueConclusionLine("VirtualNode-"+parentNodeOfVirtualNodeName, Tokenizer.getTokens("VirtualNode-"+parentNodeOfVirtualNodeName));
					this.nodeSet.getNodeIdMap().put(virtualNode.getNodeId(), "VirtualNode-"+parentNodeOfVirtualNodeName);
					virtualNodeMap.put("VirtualNode-"+parentNodeOfVirtualNodeName, virtualNode);
					if(mandatoryAnd > 0)
					{
						dependencyList.add(new Dependency(node, virtualNode, (DependencyType.getMandatory() | DependencyType.getOr())));
					}
					else
					{
						dependencyList.add(new Dependency(node, virtualNode, DependencyType.getOr()));
					}
					dpList.stream()
						  .filter(dp -> dp.getDependencyType() == DependencyType.getAnd() || dp.getDependencyType() == (DependencyType.getMandatory() | DependencyType.getAnd()))
						  .forEachOrdered(dp -> dp.setParentNode(virtualNode));
				}
			}
			
		});
		return virtualNodeMap;
	}
	
	@Override
	public int[][] createDependencyMatrix()
	{
		this.nodeSet.setNodeMap(handlingVirtualNode(this.dependencyList));
		/*
		 * number of rule is not always matched with the last ruleId in Node 
		 */
		int numberOfRules = Node.getStaticNodeId();
		
		int[][] dependencyMatrix = new int[numberOfRules][numberOfRules];
	
		
		this.dependencyList.forEach(dp -> {
			int parentId = dp.getParentNode().getNodeId();
			int childId = dp.getChildNode().getNodeId();
			int dpType = dp.getDependencyType();
			dependencyMatrix[parentId][childId] = dpType;
		});
		
		return dependencyMatrix;
	}

	@Override
	public void setNodeSet(NodeSet ns)
	{
		this.nodeSet = ns;
	}
	
	private int handleNotKnownManOptPos(String firstTokenString, int dependencyType)
	{
		if(dependencyType != 0)
		{
			if(firstTokenString.contains("NOT"))
			{
				dependencyType |= DependencyType.getNot();
			}
			if(firstTokenString.contains("KNOWN"))
			{
				dependencyType |= DependencyType.getKnown();
			}
			if(firstTokenString.contains("MANDATORY"))
			{
				dependencyType |= DependencyType.getMandatory();
			}
			if(firstTokenString.contains("OPTIONALLY"))
			{
				dependencyType |= DependencyType.getOptional();
			}
			if(firstTokenString.contains("POSSIBLY"))
			{
				dependencyType |= DependencyType.getPossible();
			}
		}
		
		return dependencyType;
	}

} public class RuleSetReader implements ILineReader{
	
	private BufferedReader br;
	
	public void setFileSource(String filePath)
	{
		try 
		{
			br = new BufferedReader(new FileReader(filePath));
		} 
		catch (FileNotFoundException e) 
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}
	
	public void setStreamSource(InputStream inputStream)
	{
		br = new BufferedReader(new InputStreamReader(inputStream));
	}
	
	public void setStringSource(String text)
	{
		//convert a given text into InputStream
		InputStream inputStream = new ByteArrayInputStream(text.getBytes());
		
		//read the inputStream with BufferedReader
		br = new BufferedReader(new InputStreamReader(inputStream));
	}
	
	public String getNextLine()
	{
		String line = null;
		try 
		{
			line = br.readLine();
		} 
		catch (IOException e) 
		{
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		if(line == null)
		{
			try 
			{
				br.close();
			} 
			catch (IOException e) 
			{
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		
		return line;
	}
	

} public class RuleSetScanner {
	private IScanFeeder scanFeeder = null;
    private ILineReader lineReader = null;
    private boolean useHistoricalData = false;
    private HashMap<String, Record> recordMapOfNodes; 

    public RuleSetScanner(ILineReader reader, IScanFeeder feeder) {
          scanFeeder = feeder;
          lineReader = reader;
    }

    public void setScanFeeder(IScanFeeder scanFeeder, ILineReader lineReader)
    {
    		this.scanFeeder = scanFeeder;
    		this.lineReader = lineReader;
    }
    
    public void scanRuleSet() {

          String parent = null; 
          String line = null;
          String lineTrimed;
          Stack<String> parentStack = new Stack<>();
          int previousWhitespace = 0;
          int lineNumber = 0;


          while ((line = lineReader.getNextLine()) != null) {

       	   line = line.replaceAll("\\s*(if)*\\s*$", ""); // it trims off whitespace including 'if' at the end of each line due to it could effect on calculating indentation
       	   lineTrimed = line.trim();
       	   int currentWhitespace = 0;
       	   lineNumber++;

               // check the line

               // is it empty?
               if(line.isEmpty()) 
               {
            	   		parentStack.clear();
               }
               else if(line.trim().substring(0, 2).equals("//"))
               {
        	   			//this els if statement is to handle commenting in new line only
            	   		// handling commenting in rule text file needs enhancement later
               }
               
               // does it begin with a white space?
               else if(Character.isWhitespace(line.charAt(0))) 
           	   {
               		currentWhitespace = line.length()-lineTrimed.length(); // calculating indentation level

	                	if (lineTrimed.isEmpty()) // is it a blank line? 
	                	{
	                      // blank line - no parent
	                      parent = null;
	                	} 
	                	else 
	                	{
	                		int indentationDifference = previousWhitespace - currentWhitespace;
	                		
	                		if(indentationDifference == -4) // this condition is for handling inputs from ACE text editor
	                		{
	                			indentationDifference = -1;
	                		}
	                		
	                   	if(indentationDifference == 0 || indentationDifference > 0) //current line is at same level as previous line || current line is in upper level than previous line
	                   	{
	                   		parentStack = handlingStackPop(parentStack, indentationDifference);
	                   		
	                   	}
	                   	else if(indentationDifference < -1) // current line is not a direct child of previous line hence the format is invalid
	                   	{	                   		
	               			//need to handle error
	               			scanFeeder.handleWarning(lineTrimed);
	               			break;	                   		
	                   	}
	                   	
	                   	parent = parentStack.peek();
	           			
	           			String tempLineTrimed = lineTrimed.trim().replaceAll("^(OR\\s?|AND\\s?)?(MANDATORY|OPTIONALLY|POSSIBLY)?(\\s?NOT|\\s?KNOWN)*(NEEDS|WANTS)?", "").trim();
	           			String tempFirstKeywordsGroup = lineTrimed.replace(tempLineTrimed, "").trim();													
	           			parentStack.push(tempLineTrimed.trim()); // due to lineTrimed string contains keywords such as "AND", "OR", "AND KNOWN" or "OR KNOWN" so that it needs removing those keywords for the 'parentStack'
		
		           		// is an indented child	           			
	           			scanFeeder.handleChild(parent, tempLineTrimed, tempFirstKeywordsGroup,lineNumber);	
                    }

               } 
               // does not begin with a white space
               else 
               {
                    // is a parent
            	   		parentStack.clear();
	               parent = lineTrimed;
	               scanFeeder.handleParent(parent, lineNumber);
	               parentStack.push(parent);
               }
               previousWhitespace = currentWhitespace;
          }
    }
    
    public void establishNodeSet()
    {
	   	 NodeSet ns = scanFeeder.getNodeSet();
	   	 ns.setDependencyMatrix(scanFeeder.createDependencyMatrix());
	   	 List<Node> sortedList = TopoSort.bfsTopoSort(ns.getNodeMap(), ns.getNodeIdMap(), ns.getDependencyMatrix().getDependencyMatrixArray()); 
	   	 if(sortedList.size() != 0)
	   	 {
	   	   	 ns.setNodeSortedList(sortedList);
	   	 }
	   	 else
	   	 {
	   		scanFeeder.handleWarning("RuleSet needs rewriting due to it is cyclic.");
	   	 }
    }
    
    public void establishNodeSet(HashMap<String, Record> recordMapOfNodes)
    {
	   	 NodeSet ns = scanFeeder.getNodeSet();
	   	 ns.setDependencyMatrix(scanFeeder.createDependencyMatrix());
	   	 List<Node> sortedList = TopoSort.dfsTopoSort(ns.getNodeMap(), ns.getNodeIdMap(), ns.getDependencyMatrix().getDependencyMatrixArray(), recordMapOfNodes);
	   	 if(sortedList.size() != 0)
	   	 {
	   	   	 ns.setNodeSortedList(sortedList);
	   	 }
	   	 else
	   	 {
	   		scanFeeder.handleWarning("RuleSet needs rewriting due to it is cyclic.");
	   	 }
    }
    
    public Stack<String> handlingStackPop(Stack<String>parentStack, int indentationDifference)
    {
   	 for(int i = 0; i < indentationDifference+1; i++)
   	 {
   		 parentStack.pop();
   	 }
   	 return parentStack;
    }
    
    public void setHistoricalData()
    {
    		this.useHistoricalData = !this.useHistoricalData;
    }
    
    public void setRecordMapOfNodes(HashMap<String, Record> recordMapOfNodes)
    {
    		this.recordMapOfNodes = recordMapOfNodes;
    }
    public HashMap<String, Record> getRecordMapOfNodes()
    {
    		return recordMapOfNodes;
    }


} public class NodeObject_For_Inference_Test {
	private String name;
	private String[] valueArray;
	private String value;
	
	public NodeObject_For_Inference_Test(String name, String[] valueArray)
	{
		this.name = name;
		this.valueArray = valueArray;
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public String[] getValueArray() {
		return valueArray;
	}

	public void setValueArray(String[] valueArray) {
		this.valueArray = valueArray;
	}
	
	public String getValue() {
		Random rand = new Random(); 
		this.value = this.valueArray[rand.nextInt(2)]; 
		return value;
	}
}
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.stream.IntStream;
import factValuePackage.FactListValue;
import factValuePackage.FactValue;
import factValuePackage.FactValueType;
import inferencePackage.Assessment;
import inferencePackage.InferenceEngine;
import nodePackage.ComparisonLine;
import nodePackage.IterateLine;
import nodePackage.LineType;
import nodePackage.Node;
import ruleParser.RuleSetParser;
import ruleParser.RuleSetReader;
import ruleParser.RuleSetScanner;
import testingUtilPackage.NodeObject_For_Inference_Test;
package testingPackage.testing10;



public class Testing_for_ALL_Node_Lines_and_features_10 {
	public static void main(String[]args) throws IOException {
		RuleSetReader ilr = new RuleSetReader();
		ilr.setStreamSource(Testing_for_ALL_Node_Lines_and_features_10.class.getResourceAsStream("Testing for ALL Node Lines and features.txt"));
		RuleSetParser isf = new RuleSetParser();		
		RuleSetScanner rsc = new RuleSetScanner(ilr,isf);