package com.hammurapi.reasoning.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;

import com.hammurapi.config.bootstrap.ConfigurationException;
import com.hammurapi.config.bootstrap.Destroyable;
import com.hammurapi.config.bootstrap.FactoryClosure;
import com.hammurapi.flow.runtime.Invocable;
import com.hammurapi.flow.runtime.PropertySet;
import com.hammurapi.flow.runtime.impl.MapPropertySet;
import com.hammurapi.reasoning.Derivation;
import com.hammurapi.reasoning.ExceptionHandler;
import com.hammurapi.reasoning.ForwardReasoningSession;
import com.hammurapi.reasoning.Handle;
import com.hammurapi.reasoning.InferenceListener;
import com.hammurapi.reasoning.ReasoningException;

class ForwardReasoningSessionImpl<F> implements ForwardReasoningSession<F> {
	private static final Logger logger = Logger.getLogger(ForwardReasoningSessionImpl.class.getName());
	
	private RuleSetFlow<F> flow;
	private KnowledgeBase<F> knowledgeBase;
	private Invocable<KnowledgeBase<F>,InferenceToken<F>> flowInput;
	private PropertySet properties;
	private Destroyable flowDestroyable;
	
	@SuppressWarnings("unchecked")
	ForwardReasoningSessionImpl(FactoryClosure<?> factoryClosure, Map<?, ?> properties) throws ReasoningException {
		try {
			this.flow = (RuleSetFlow<F>) factoryClosure.create();
			this.flowDestroyable = factoryClosure.getDestroyable();
		} catch (ConfigurationException e) {
			throw new ReasoningException(e);
		}
		
		try {
			flowInput = flow.getInvocable(Constants.PUT, null);
		} catch (ConfigurationException e) {
			throw new ReasoningException(e);
		}
		knowledgeBase = flow.getKnowledgeBase();
		// Use only string keys
		Map<String, Object> props = new HashMap<String, Object>();
		if (properties!=null) {
			for (Map.Entry<?, ?> entry: properties.entrySet()) {
				if (entry.getKey() instanceof String || entry.getKey()==null) {
					props.put((String) entry.getKey(), entry.getValue()); 
				} else {
					throw new ReasoningException("This session implementation supports only String property keys");
				}
			}
		}
		this.properties = new MapPropertySet(props);
			
		flow.initRules();
	}

	@Override
	public boolean contains(Object fact) throws ReasoningException {
		return knowledgeBase.contains(fact);
	}

	@Override
	public boolean contains(Handle<F> handle) throws ReasoningException {
		return knowledgeBase.contains(handle);
	}

	@Override
	public void executeRules() throws ReasoningException {
		try {
			flow.join();
		} catch (InterruptedException e) {
			throw new ReasoningException(e);
		}
		flow.checkCollectedExceptions();
	}

	@Override
	public F get(Handle<F> handle) throws ReasoningException {
		return knowledgeBase.get(handle);
	}

	@Override
	public Collection<Derivation<F>> getDerivations(F obj) throws ReasoningException {
		return knowledgeBase.getDerivations(obj);
	}

	@Override
	public Collection<Derivation<F>> getDerivations(Handle<F> handle) throws ReasoningException {
		return knowledgeBase.getDerivations(handle);
	}

	@Override
	public Handle<F> getHandle(Object fact) {
		return knowledgeBase.getHandle(fact);
	}

	@Override
	public Collection<Handle<F>> getHandles() throws ReasoningException {
		return knowledgeBase.getHandles();
	}

	@Override
	public InferenceListener<F> getInferenceListener() throws ReasoningException {
		return knowledgeBase.getInferenceListener();
	}

	@Override
	public Collection<? extends F> getObjects() throws ReasoningException {
		return knowledgeBase.getObjects();
	}

	@SuppressWarnings("unchecked")
	@Override
	public Handle<F> put(F fact) throws ReasoningException {
		logger.fine("New input: "+fact);
		Handle<F> ret = knowledgeBase.getHandle(fact);
		if (ret==null) {
			// Wrap object into handles
			ret = knowledgeBase.put(fact);
			try {
				flowInput.invoke(
						knowledgeBase, 
						(InferenceToken<F>[]) new InferenceToken[] {new InferenceToken<F>(ret)}, 
						properties, 
						null, 
						null);
			} catch (ReasoningException e) {
				throw e;
			} catch (Exception e) {
				throw new ReasoningException(e);
			}
		} 
		return ret;
	}

	@Override
	public List<Handle<F>> putAll(List<? extends F> facts) throws ReasoningException {
		List<Handle<F>> ret = new ArrayList<Handle<F>>();
		for (F fact: facts) {
			ret.add(put(fact));
		}
		return ret;
	}

	@SuppressWarnings("unchecked")
	@Override
	public Handle<F>[] putAll(F... facts) throws ReasoningException {
		Handle<F>[] ret = new Handle[facts.length];
		for (int i=0; i<ret.length; ++i) {
			ret[i]=put(facts[i]);
		}
		return ret;
	}

	@Override
	public void remove(F fact, boolean strict) throws ReasoningException {
		knowledgeBase.remove(fact, strict);				
	}

	@Override
	public void remove(Handle<F> handle, boolean strict) throws ReasoningException {
		knowledgeBase.remove(handle, strict);		
	}

	@Override
	public void reset() throws ReasoningException {
		knowledgeBase.reset();		
		flow.reset();
	}

	@Override
	public void setInferenceListener(InferenceListener<F> listener) throws ReasoningException {
		knowledgeBase.setInferenceListener(listener);		
	}

	@SuppressWarnings("unchecked")
	@Override
	public void updateObject(Handle<F> handle, F obj) throws ReasoningException {
		knowledgeBase.updateObject(handle, obj);
		try {
			flowInput.invoke(
					knowledgeBase, 
					(InferenceToken<F>[]) new InferenceToken[] {new InferenceToken<F>(handle)}, 
					properties, 
					null, 
					null);
		} catch (ReasoningException e) {
			throw e;
		} catch (Exception e) {
			throw new ReasoningException(e);
		}		
	}

	@Override
	public String getDescription() {
		return flow.getDescription();
	}

	@Override
	public ExceptionHandler getExceptionHandler() throws ReasoningException {
		return knowledgeBase.getExceptionHandler();
	}

	@Override
	public String getName() {
		return flow.getName();
	}

	@Override
	public void close() throws ReasoningException {
		knowledgeBase.close();
		flow.release();
		if (flowDestroyable!=null) {
			try {
				flowDestroyable.destroy();
			} catch (ConfigurationException e) {
				throw new ReasoningException(e);
			}
		}
	}

	@Override
	public void setExceptionHandler(ExceptionHandler handler) throws ReasoningException {
		knowledgeBase.setExceptionHandler(handler);		
	}

	@Override
	public Map<Class<F>, Set<Class<F>>> getConclusionMap() {
		throw new UnsupportedOperationException("Not implemented yet");
	}

}
