package com.hammurapi.reasoning.impl;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import org.codehaus.janino.CompileException;
import org.codehaus.janino.ExpressionEvaluator;
import org.codehaus.janino.Parser.ParseException;
import org.codehaus.janino.Scanner.ScanException;

import com.hammurapi.config.bootstrap.ConfigurationException;
import com.hammurapi.reasoning.ExceptionHandler;


public class ExpressionConditionNode<F> extends ConditionNode<F> {
	private static final Logger logger = Logger.getLogger(ExpressionConditionNode.class.getName());
	
	private boolean isFine = logger.isLoggable(Level.FINE);
	
	private String condition;
	
	public void setCondition(String condition) {
		this.condition = condition;
	}
	
	private List<ConditionArgument> conditionArguments = new ArrayList<ConditionArgument>();
	
	public void addConditionArgument(ConditionArgument conditionArgument) {
		conditionArguments.add(conditionArgument);
	}
	
	@Override
	public String toString() {
		return super.toString()+" condition='"+condition+"'";
	}
	
	/**
	 * Evaluate condition as soon as all condition arguments are available.
	 */
	@SuppressWarnings("unchecked")
	@Override
	protected boolean partialJoin(List<com.hammurapi.reasoning.impl.InferenceToken<F>>[] inputs, int index) throws Exception {
		if (index==lastConditionPin) {
			Object[] cArgs = new Object[conditionArguments.size()+1];
			Iterator<ConditionArgument> cit = conditionArguments.iterator();
			Z: for (int i=0; cit.hasNext(); ++i) {
				ConditionArgument ca = cit.next();
				for (int j=0; j<=index; ++j) {
					List<TokenInfo> pinConfig = (List<TokenInfo>) getInputPins().get(j).config;
					if (pinConfig.size()!=inputs[j].size()) {
						throw new IllegalArgumentException("Number of input tokens is different from number of token infos");
					}
					for (int k=0, l=pinConfig.size(); k<l; ++k) {
						TokenInfo ti = pinConfig.get(k);
						if (ti.getParameterIndex() == ca.getParameterIndex()) {
							InferenceToken<F> it = inputs[j].get(k);
							if (it.isConsumed()) {
								return false;
							}
							
							cArgs[i] = knowledgeBase.get(it.getHandle());
							if (cArgs[i]==null) {
								return false;
							}
							continue Z;
						}
					}
				}				
			}
			
			cArgs[cArgs.length-1] = rule;
			try {
				boolean result = Boolean.TRUE.equals(evaluator.evaluate(cArgs));
				if (isFine) {
					StringBuilder sb = new StringBuilder("Condition '"+condition+"' was evaluated to "+result+" for arguments ");
					for (Object o: cArgs) {
						sb.append(Constants.LINE_SEPARATOR+"\t"+o);
					}
					logger.fine(sb.toString());
				}
				
				return result;
			} catch (Exception e) {
				ExceptionHandler exceptionHandler = knowledgeBase.getExceptionHandler();
				if (exceptionHandler==null) {
					throw e;
				}
				exceptionHandler.handleException(e);
				return false;
			}
		}
		
		return true;
	}
	
	private boolean hasConditionArgument(PinEntry<F> pin) {
		for (Object o: pin.config) {
			TokenInfo ti = (TokenInfo) o;
			for (ConditionArgument ca: conditionArguments) {
				if (ca.getParameterIndex()==ti.getParameterIndex()) {
					return true;
				}
			}
		}
		
		return false;
	}
	
	private int lastConditionPin=-1;

	/**
	 * Sort input pins to move pins with conditon arguments to first positions.
	 */
	@Override
	protected void sortInputs() {
		Collections.sort(getInputPins(), new Comparator<PinEntry<F>>() {
			
			@Override
			public int compare(PinEntry<F> pe1, PinEntry<F> pe2) {
				if (hasConditionArgument(pe1)) {
					if (hasConditionArgument(pe2)) {
						return pe1.index-pe2.index;
					}
					return -1; // pe1 shall be before pe2
				}
				
				if (hasConditionArgument(pe2)) {
					return 1; // pe1 shall be after pe2.
				}
				
				return pe1.index - pe2.index;
			}
		});
		
		for (int i=0; i<getInputPins().size(); ++i) {
			if (hasConditionArgument(getInputPins().get(i))) {
				lastConditionPin = i;
			}
		}
	}
	
	@Override
	public void afterConnect() throws ConfigurationException {
		super.afterConnect();
		try {
			String[] pNames = new String[conditionArguments.size()+1];
			Class<?>[] pTypes = new Class[pNames.length];
			for (int i=0; i<conditionArguments.size(); ++i) {
				ConditionArgument ca = conditionArguments.get(i);
				pNames[i] = ca.getName();
				pTypes[i] = ca.getType();
			}
			
			pNames[conditionArguments.size()] = "rule";
			pTypes[conditionArguments.size()] = rule.getClass();
			
			evaluator = new ExpressionEvaluator(condition, boolean.class, pNames, pTypes);
		} catch (CompileException e) {
			throw new ConfigurationException("Problem with condition '"+condition+"': "+e, e);
		} catch (ParseException e) {
			throw new ConfigurationException("Problem with condition '"+condition+"': "+e, e);
		} catch (ScanException e) {
			throw new ConfigurationException("Problem with condition '"+condition+"': "+e, e);
		}
	}
	
	private ExpressionEvaluator evaluator;

}
