package com.hammurapi.reasoning.impl;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.ScheduledExecutorService;

import com.hammurapi.config.bootstrap.ConfigurationException;
import com.hammurapi.config.runtime.ConfigurationContext;
import com.hammurapi.flow.Flow;
import com.hammurapi.flow.runtime.Invocable;
import com.hammurapi.flow.runtime.Invoker;
import com.hammurapi.flow.runtime.ProcessingPathElement;
import com.hammurapi.flow.runtime.PropertySet;
import com.hammurapi.flow.runtime.PropertySetFactory;
import com.hammurapi.flow.runtime.SynapseFactory;
import com.hammurapi.flow.runtime.Transition;
import com.hammurapi.flow.runtime.impl.ExecutorSynapseFactory;
import com.hammurapi.flow.runtime.impl.MapPropertySetFactory;
import com.hammurapi.flow.runtime.impl.TaskCountingFlowBase;
import com.hammurapi.reasoning.ExceptionHandler;
import com.hammurapi.reasoning.ReasoningException;
import com.hammurapi.util.Context;

/**
 * Flow which implements rule set.
 * @author Pavel Vlasov
 *
 */
public class RuleSetFlow<F> extends TaskCountingFlowBase<ReasoningNodeBase<F>, Transition<KnowledgeBase<F>, InferenceToken<F>>, List<?>, Integer, KnowledgeBase<F>, InferenceToken<F>> {
	
	private List<?> rules;
	private KnowledgeBase<F> knowledgeBase;
	private Executor executor;
	private ScheduledExecutorService scheduler;
	private PropertySetFactory propertySetFactory;
	
	private List<Exception> collectedExceptions = new ArrayList<Exception>();
	
	private ExceptionHandler exceptionHandler = new ExceptionHandler() {
		
		@Override
		public void handleException(Exception e) throws Exception {
			synchronized (collectedExceptions) {
				collectedExceptions.add(e);
			}
		}
	};
	
	public void checkCollectedExceptions() throws RuleExecutionException {
		synchronized (collectedExceptions) {
			if (!collectedExceptions.isEmpty()) {
				try {
					throw new RuleExecutionException(collectedExceptions);
				} finally {
					collectedExceptions.clear();
				}
			}
		}
	}
	
	public KnowledgeBase<F> getKnowledgeBase() {
		return knowledgeBase;
	}
	
	public void setKnowledgeBase(KnowledgeBase<F> knowledgeBase) {
		this.knowledgeBase = knowledgeBase;
	}
	
	public void setExecutor(Executor executor) {
		this.executor = executor;
	}
	
	public Executor getExecutor() {
		return executor;
	}
	
	public void setPropertySetFactory(PropertySetFactory propertySetFactory) {
		this.propertySetFactory = propertySetFactory;
	}
	
	public PropertySetFactory getPropertySetFactory() {
		return propertySetFactory;
	}
	
	public void setScheduler(ScheduledExecutorService scheduler) {
		this.scheduler = scheduler;
	}
	
	public ScheduledExecutorService getScheduler() {
		return scheduler;
	}
		
	@Override
	public void init(ConfigurationContext<Flow> context) throws ConfigurationException {
		if (executor==null) {
			executor = context.lookup(Executor.class);
		}
		
		if (scheduler==null) {
			scheduler = context.lookup(ScheduledExecutorService.class);
		}
		
		if (knowledgeBase==null) {
			Flow def = context.getDefinition();			
			knowledgeBase = new InMemoryKnowledgeBase<F>(def==null ? "Knowledge base" : def.getName(), def==null ? null : def.getDescription(), false);
			((InMemoryKnowledgeBase<F>) knowledgeBase).setExecutor(executor);
		}	
		
		if (propertySetFactory==null) {
			propertySetFactory = context.lookup(PropertySetFactory.class);
			if (propertySetFactory==null) {
				propertySetFactory = new MapPropertySetFactory(executor, scheduler);
			}
		}
		
		if (getSynapseFactory()==null) {
			setSynapseFactory(context.lookup(SynapseFactory.class));
		}
		
		if (getSynapseFactory()==null && executor!=null) {
			PriorityExecutorSynapseFactory<KnowledgeBase<F>, InferenceToken<F>> pesf = new PriorityExecutorSynapseFactory<KnowledgeBase<F>, InferenceToken<F>>();
			setSynapseFactory(pesf);
			pesf.setExecutor(executor);
			pesf.setScheduler(scheduler);
			pesf.setPropertySetFactory(propertySetFactory);
		} else if (getSynapseFactory() instanceof ExecutorSynapseFactory) {
			ExecutorSynapseFactory<Integer, KnowledgeBase<F>, InferenceToken<F>> esf = (ExecutorSynapseFactory<Integer, KnowledgeBase<F>, InferenceToken<F>>) getSynapseFactory();
			esf.setExecutor(executor);
			esf.setScheduler(scheduler);
			esf.setPropertySetFactory(propertySetFactory);			
		}
		
		if (getSynapseFactory() instanceof PriorityExecutorSynapseFactory) {
			((PriorityExecutorSynapseFactory<KnowledgeBase<F>, InferenceToken<F>>) getSynapseFactory()).setExceptionHandler(exceptionHandler);
		}
		
		
		super.init(context);
		
		if (transitions!=null) {
			for (Transition<KnowledgeBase<F>, InferenceToken<F>> t:transitions) {
				if (t instanceof ReasoningTransitionBase) {
					((ReasoningTransitionBase) t).setKnowledgeBase(knowledgeBase);
				}
			}
		}
		if (nodes!=null) {
			for (ReasoningNodeBase<F> n:nodes) {
				n.setKnowledgeBase(knowledgeBase);
			}
		}		
	}

	public void setRules(List<?> rules) throws ConfigurationException {
		this.rules = rules;
		if (transitions!=null) {
			for (Transition<KnowledgeBase<F>,InferenceToken<F>> t:transitions) {
				if (t instanceof ReasoningTransitionBase) {
					((ReasoningTransitionBase) t).setRules(rules);
				}
			}
		}
		if (nodes!=null) {
			for (ReasoningNodeBase<F> n:nodes) {
				n.setRules(rules);
			}
		}
	}
	
	@Override
	public void setTransitions(List<Transition<KnowledgeBase<F>,InferenceToken<F>>> transitions) throws ConfigurationException {
		super.setTransitions(transitions);
		for (Transition<KnowledgeBase<F>,InferenceToken<F>> t:transitions) {
			if (t instanceof ReasoningTransitionBase) {
				if (rules!=null) {
					((ReasoningTransitionBase) t).setRules(rules);					
				}
				if (knowledgeBase!=null) {
					((ReasoningTransitionBase) t).setKnowledgeBase(knowledgeBase);
				}
			}
		}
	}
	
	@Override
	public void setNodes(List<ReasoningNodeBase<F>> nodes) throws ConfigurationException {
		super.setNodes(nodes);
		for (ReasoningNodeBase<F> n:nodes) {
			if (rules!=null) {
				n.setRules(rules);
			}
			if (knowledgeBase!=null) {
				n.setKnowledgeBase(knowledgeBase);
			}
		}
	}

	@Override
	protected Invocable<KnowledgeBase<F>,InferenceToken<F>> getInternalInvocable(String pinName, Integer connectKey) {
		throw new UnsupportedOperationException("RuleSet flow doesn't have outputs");
	}

	@Override
	protected Invoker<KnowledgeBase<F>,InferenceToken<F>> getInternalInvoker(String pinName, Integer connectKey) {
		if (pinName.equals(Constants.PUT)) {
			return new Invoker<KnowledgeBase<F>,InferenceToken<F>>() {
	
				@Override
				public void setInvocable(Invocable<KnowledgeBase<F>,InferenceToken<F>> invocable) throws IllegalStateException {
					synchronized (addObjectInvocables) {
						addObjectInvocables.add(invocable);
					}
				}
				
			};
		}
		throw new IllegalArgumentException("Invalid pin name: "+pinName);
	}

	@Override
	public void setBackEnd(Object object) {
		throw new UnsupportedOperationException("RuleSet flow doesn't support back-end object.");
	}
	
	private Collection<Invocable<KnowledgeBase<F>,InferenceToken<F>>> addObjectInvocables = new ArrayList<Invocable<KnowledgeBase<F>,InferenceToken<F>>>();
	
	@Override
	public Invocable<KnowledgeBase<F>,InferenceToken<F>> getInvocable(String pinName, Integer connectKey) throws ConfigurationException {
		if (pinName.equals(Constants.PUT)) {
			return new Invocable<KnowledgeBase<F>,InferenceToken<F>>() {

				@Override
				public Collection<Future<?>> invoke(
						KnowledgeBase<F> flowState,
						InferenceToken<F>[] args,
						PropertySet properties,
						Context context,
						List<ProcessingPathElement> processingPath) throws Exception {					
					Collection<Future<?>> ret = new ArrayList<Future<?>>();
					
					for (Invocable<KnowledgeBase<F>,InferenceToken<F>> i: addObjectInvocables) {
						ret.addAll(i.invoke(flowState, args, properties, context, processingPath));
					}
					return ret;
				}
				
			};
		} 
		throw new IllegalArgumentException("Invalid pin name: "+pinName);
	}

	@Override
	public Invoker<KnowledgeBase<F>,InferenceToken<F>> getInvoker(String pinName, Integer connectKey) throws ConfigurationException {
		throw new UnsupportedOperationException("RuleSet flow doesn't have outputs.");
	}
	
	@Override
	protected Object convertResult(Object proxy, Method method, Object[] args, Collection<Future<?>> invocationResult) throws Exception {
		return invocationResult.iterator().next().get();
	}
	
	private List<PinEntry<F>> pins = new ArrayList<PinEntry<F>>();

	@Override
	public void addPin(String pinName, List<?> pinConfig) {
		pins.add(new PinEntry<F>(pinName, pinConfig));		
	}
		
	private String name;
	private String description;

	public String getDescription() {
		return description;
	}

	public String getName() {
		return name;
	}
	
	public void setName(String name) {
		this.name = name;
	}
	
	public void setDescription(String description) {
		this.description = description;
	}
	
	void reset() throws ReasoningException {
		for (Object rule: rules) {
			if (rule instanceof com.hammurapi.reasoning.spi.Rule) {
				((com.hammurapi.reasoning.spi.Rule) rule).reset();
			}
		}
	}
	
	void release() throws ReasoningException {
		for (Object rule: rules) {
			if (rule instanceof com.hammurapi.reasoning.spi.Rule) {
				((com.hammurapi.reasoning.spi.Rule) rule).release();
			}
		}
	}
	
	void initRules() throws ReasoningException {
		for (Object rule: rules) {
			if (rule instanceof com.hammurapi.reasoning.spi.Rule) {
				((com.hammurapi.reasoning.spi.Rule) rule).init();
			}
		}
	}
	
}
