package com.hammurapi.reasoning.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.Future;

import com.hammurapi.flow.runtime.ProcessingPathElement;
import com.hammurapi.flow.runtime.PropertySet;
import com.hammurapi.flow.runtime.impl.Joiner.InputConsumer;
import com.hammurapi.reasoning.ReasoningException;
import com.hammurapi.util.Context;


/**
 * Base class for condition nodes.
 * 
 * @author Pavel Vlasov
 * 
 */
public abstract class ConditionNode<F> extends ReasoningNodeBase<F> {

	/**
	 * Dispatch inputs to outputs.
	 */
	@SuppressWarnings("unchecked")
	@Override
	protected Collection<Future<?>> process(
			KnowledgeBase<F> flowState,
			List<InferenceToken<F>>[] inputs, 
			PropertySet properties,
			Context context, 
			List<ProcessingPathElement> processingPath,
			InputConsumer consumer,
			int activator) throws Exception {
		
		Collection<Future<?>> ret = new ArrayList<Future<?>>();
		
		boolean dispatched = false; 
		Z: for (PinEntry<F> pin: pins) {
			if (pin.getName().startsWith(Constants.OUTPUT)) {
				for (Object alt: pin.config) {
					List<TokenInfo> lti=(List<TokenInfo>) alt;
					InferenceToken<F>[] out = match(inputs, lti);
					if (out!=null) {
						dispatched = true;
						for (Output<F> output: pin.outputs) {
							ret.addAll(output.invocable.invoke(flowState, out, properties, context, processingPath));
						}
						
						continue Z;
					}
				}
			}
		}
		if (!dispatched) {
			throw new IllegalArgumentException("Condition output was not dispatched to any output pins");
		}
		return ret;
	}
	
	/**
	 * Matches input tokens with output config.
	 * @param inputs
	 * @param outputConfig
	 * @return Filled out output array or null if there is no match.
	 * @throws ReasoningException 
	 */
	@SuppressWarnings("unchecked")
	private InferenceToken<F>[] match(List<InferenceToken<F>>[] inputs, List<TokenInfo> outputConfig) throws ReasoningException {
		InferenceToken<F>[] out = new InferenceToken[outputConfig.size()];
		Iterator<TokenInfo> outputConfigIterator = outputConfig.iterator();
		Z: for (int outputIdx=0; outputConfigIterator.hasNext(); ++outputIdx) {
			TokenInfo outputTokenInfo = outputConfigIterator.next();
			Iterator<PinEntry<F>> inputPinIterator = getInputPins().iterator();
			for (int inputPinIndex=0; inputPinIterator.hasNext(); ++inputPinIndex) {
				PinEntry<F> inputPin = inputPinIterator.next();
				List<TokenInfo> pinConfig = (List<TokenInfo>) inputPin.config;
				Iterator<TokenInfo> inputTokenInfoIterator = pinConfig.iterator();
				for (int inputTokenIndex=0; inputTokenInfoIterator.hasNext(); ++inputTokenIndex) {
					TokenInfo inputTokenInfo = inputTokenInfoIterator.next();
					if (outputTokenInfo.getParameterIndex() == inputTokenInfo.getParameterIndex()) {
						InferenceToken<F> it = inputs[inputPinIndex].get(inputTokenIndex);
						if (it.isConsumed()) {
							return null;
						}
						
						if (!outputTokenInfo.getType().isInstance(knowledgeBase.get(it.getHandle()))) {
							return null;
						}
						
						out[outputIdx] = it;
						continue Z;
					}								
				}
			}
			throw new IllegalArgumentException("Could not find input for output index "+outputIdx);			
		}		
		return out;
	}
	
}
