package com.hammurapi.reasoning.impl;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.commons.pool.KeyedObjectPool;
import org.apache.commons.pool.KeyedPoolableObjectFactory;

import com.hammurapi.config.bootstrap.ConfigurationException;
import com.hammurapi.config.bootstrap.Destroyable;
import com.hammurapi.reasoning.Derivation;
import com.hammurapi.reasoning.ExceptionHandler;
import com.hammurapi.reasoning.ForwardReasoningSession;
import com.hammurapi.reasoning.ForwardReasoningSessionFactory;
import com.hammurapi.reasoning.Handle;
import com.hammurapi.reasoning.InferenceListener;
import com.hammurapi.reasoning.ReasoningException;

/**
 * Implements session pooling, uses properties as the key.
 * @author Pavel Vlasov
 *
 * @param <F>
 */
public class PoolingForwardReasoningSessionFactory<F> implements ForwardReasoningSessionFactory<F>, Destroyable {
	
	private ForwardReasoningSessionFactory<F> master;
		
	protected KeyedObjectPool pool;
	
	private class PoolableForwardReasoningSession implements ForwardReasoningSession<F> {
		
		private ForwardReasoningSession<F> master;
		private Object key;

		PoolableForwardReasoningSession(ForwardReasoningSession<F> master, Object key) {
			this.master = master;
			this.key = key;
		}
		
		public void close() throws ReasoningException {
			master.reset();
			master.setInferenceListener(null);
			master.setExceptionHandler(null);
			try {
				pool.returnObject(key, this);
			} catch (Exception e) {
				throw new ReasoningException("Failed to return session to pool: "+e, e);
			}
		}

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

		public boolean contains(Object fact) throws ReasoningException {
			return master.contains(fact);
		}

		public void executeRules() throws ReasoningException {
			master.executeRules();
		}

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

		public Map<Class<F>, Set<Class<F>>> getConclusionMap() {
			return master.getConclusionMap();
		}

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

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

		public String getDescription() {
			return master.getDescription();
		}

		public ExceptionHandler getExceptionHandler() throws ReasoningException {
			return master.getExceptionHandler();
		}

		public Handle<F> getHandle(Object fact) {
			return master.getHandle(fact);
		}

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

		public InferenceListener<F> getInferenceListener() throws ReasoningException {
			return master.getInferenceListener();
		}

		public String getName() {
			return master.getName();
		}

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

		public Handle<F> put(F fact) throws ReasoningException {
			return master.put(fact);
		}

		public Handle<F>[] putAll(F... facts) throws ReasoningException {
			return master.putAll(facts);
		}

		public List<Handle<F>> putAll(List<? extends F> facts)
				throws ReasoningException {
			return master.putAll(facts);
		}

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

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

		public void reset() throws ReasoningException {
			master.reset();
		}

		public void setExceptionHandler(ExceptionHandler handler) throws ReasoningException {
			master.setExceptionHandler(handler);
		}

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

		public void updateObject(Handle<F> handle, F obj) throws ReasoningException {
			master.updateObject(handle, obj);
		}
		
	}

	private KeyedPoolableObjectFactory poolFactory = new KeyedPoolableObjectFactory() {
		
		@Override
		public boolean validateObject(Object key, Object obj) {
			return true; // No way to validate, assume always valid.
		}
		
		@Override
		public void passivateObject(Object key, Object obj) throws Exception {
			// Nothing to do here, passivation handled by the session wrapper.
			
		}
		
		@Override
		public Object makeObject(Object key) throws Exception {			
			return new PoolableForwardReasoningSession(master.createSession((Map<?, ?>) (key==NULL_KEY ? null : key)), key);
		}
		
		@Override
		public void destroyObject(Object key, Object obj) throws Exception {
			((PoolableForwardReasoningSession) obj).master.close();			
		}
		
		@Override
		public void activateObject(Object key, Object obj) throws Exception {
			// Nothing to do here.			
		}
	};

	public PoolingForwardReasoningSessionFactory(ForwardReasoningSessionFactory<F> master, KeyedObjectPool pool) {
		this.master = master;
		this.pool = pool;
		pool.setFactory(poolFactory);
	}
	
	private static final Object NULL_KEY = new Object();
	
	private static Object toKey(Map<?, ?> keyCandidate) {
		return keyCandidate==null ? NULL_KEY : keyCandidate;
	}

	@SuppressWarnings("unchecked")
	@Override
	public ForwardReasoningSession<F> createSession(Map<?, ?> properties) throws ReasoningException {		
		try {
			return (ForwardReasoningSession<F>) pool.borrowObject(toKey(properties));
		} catch (Exception e) {
			throw new ReasoningException(e);
		}
	}

	/**
	 * Closes the pool.
	 */
	@Override
	public void destroy() throws ConfigurationException {
		if (pool!=null) {
			try {
				pool.close();
			} catch (ConfigurationException e) {
				throw e;
			} catch (Exception e) {
				throw new ConfigurationException(e);
			}
		}		
	}

}
