package com.hammurapi.reasoning.impl;

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.WeakHashMap;
import java.util.Map.Entry;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Level;
import java.util.logging.Logger;

import com.hammurapi.reasoning.Derivation;
import com.hammurapi.reasoning.ExceptionHandler;
import com.hammurapi.reasoning.Handle;
import com.hammurapi.reasoning.InferenceListener;
import com.hammurapi.reasoning.ReasoningException;

/**
 * Knowledge base which uses in-memory collecitons.
 * @author Pavel Vlasov
 */
public class InMemoryKnowledgeBase<F> implements KnowledgeBase<F> {

	private static final Logger logger = Logger.getLogger(InMemoryKnowledgeBase.class.getName());
	
	private String name;
	private String description;
	private Executor executor;
	
	/**
	 * @param executor Executor for delegation of execution of undo tasks.
	 */
	public void setExecutor(Executor executor) {
		this.executor = executor;
	}
	
	public Executor getExecutor() {
		return executor;
	}
	
	private boolean isFine;

	/**
	 * Creates a new simple knowledge base.
	 * @param weak If true, a weak hash map is used for fact storage. 
	 * It can be useful for long running sessions with inference listener.
	 * Use of weak hash map ensures that the session will not run out of memory.
	 * When a weak hash map is used, some derivations may become invalid when
	 * source facts of conclusions get garbage collected.
	 */
	public InMemoryKnowledgeBase(String name, String description, boolean weak) {
		this.name = name;
		this.description = description;
		storage = weak ? new WeakHashMap<F, HandleImpl<F>>() : new HashMap<F, HandleImpl<F>>();
		isFine = logger.isLoggable(Level.FINE);
	}
	
	private Map<String, Reference<Collection<?>>> collections = new HashMap<String, Reference<Collection<?>>>();

	// Handle collections shall also support array types, or there should be handlearray collections.
	/**
	 * Lists of handles are array lists, for other element types lists are facades for handle lists.
	 */
	@SuppressWarnings("unchecked")
	@Override
	public <T> List<T> getList(String id, Class<T> elementType) {
		synchronized (collections) {			
			Reference<Collection<?>> ref = collections.get(id);
			List<T> ret = (List<T>) (ref==null ? null : ref.get());
			if (ret==null) {
				if (doNotWrap(elementType)) {
					ret = new ArrayList<T>();
				} else if (elementType.isArray()) {
					throw new UnsupportedOperationException("Collections of arrays are not supported yet");
				} else {
					 ret = new HandleList<T>((KnowledgeBase<T>) this, new ArrayList<Handle<T>>()); // Cast is a hack around generics.
				}
				collections.put(id, new WeakReference<Collection<?>>(ret));
			}
			return ret;
		}
	}

	private <T> boolean doNotWrap(Class<T> elementType) {
		return Handle.class.isAssignableFrom(elementType) 
			|| InferenceToken.class.isAssignableFrom(elementType)
			|| (elementType.isArray() && doNotWrap(elementType.getComponentType()))
			|| Collection.class.isAssignableFrom(elementType);
	}		

	/**
	 * Sets of handles are hash sets, for other element types sets are facades for handle sets.
	 */
	@SuppressWarnings("unchecked")
	@Override
	public <T> Set<? extends T> getSet(String id, Class<T> elementType) {
		synchronized (collections) {			
			Reference<Collection<?>> ref = collections.get(id);
			Set<T> ret = (Set<T>) (ref==null ? null : ref.get());
			if (ret==null) {
				if (doNotWrap(elementType)) {
					ret = new HashSet<T>();
				} else if (elementType.isArray()) {
					throw new UnsupportedOperationException("Collections of arrays are not supported yet");
				} else {
					 ret = new HandleSet<T>((KnowledgeBase<T>) this, new HashSet<Handle<T>>()); // Cast is a hack around generics.
				}
				collections.put(id, new WeakReference<Collection<?>>(ret));
			}
			return ret;
		}
	}
	
	private ReadWriteLock storageLock = new ReentrantReadWriteLock();
	private Map<F, HandleImpl<F>> storage;

	@Override
	public Handle<F> put(F fact) throws ReasoningException {
		Lock lock = storageLock.writeLock();
		lock.lock();
		try {
			HandleImpl<F> ret = storage.get(fact); 
			if (ret==null) {
				ret = new HandleImpl<F>(fact);
				storage.put(fact, ret);
			}
			ret.derived.set(false);
			ret.removed.set(false);
			if (listener!=null) {
				listener.onPut(fact, ret);
			}
			return ret;
		} finally {
			lock.unlock();
		}
	}

	@Override
	public Collection<Derivation<F>> getDerivations(F obj) throws ReasoningException {
		Lock lock = storageLock.readLock();
		lock.lock();
		try {
			HandleImpl<F> handle = storage.get(obj);
			if (handle==null) {
				return Collections.emptyList();
			}
			return getDerivations(handle);
		} finally {
			lock.unlock();
		}
	}

	@Override
	public Collection<Derivation<F>> getDerivations(Handle<F> handle)	throws ReasoningException {
		if (handle==null) {
			return Collections.emptyList();
		}
		Collection<Derivation<F>> ret = new ArrayList<Derivation<F>>();
		Set<Derivation<F>> derivations = ((HandleImpl<F>) handle).derivations;
		ReadWriteLock dLock = ((HandleImpl<F>) handle).derivationsLock;
		Lock lock = dLock.readLock();
		lock.lock();
		try {
			Iterator<Derivation<F>> dit = derivations.iterator();
			while (dit.hasNext()) {
				Derivation<F> next = dit.next();
				if (next.isValid()) {
					ret.add(next);
// We don't remove invalid derivations to avoid having to use write lock.				
//				} else {
//					dit.remove();
				}
			}
		} finally {
			lock.unlock();
		}
		return Collections.unmodifiableCollection(ret);
	}

	@Override
	public F get(Handle<F> handle) throws ReasoningException {
		if (handle==null) {
			return null;
		}
		return ((HandleImpl<F>) handle).get();
	}

	@Override
	public Collection<F> getObjects() throws ReasoningException {
		List<F> ret = new ArrayList<F>();
		Lock lock = storageLock.readLock();
		lock.lock();
		try {
			for (HandleImpl<F> hImpl: storage.values()) {
				F o = hImpl.get();
				if (o!=null) {
					ret.add(o);
				}
			}
		} finally {
			lock.unlock();
		}
		return Collections.unmodifiableCollection(ret);
	}

	@Override
	public void remove(F obj, boolean strict) throws ReasoningException {
		Lock lock = storageLock.writeLock();
		lock.lock();
		try {
			HandleImpl<F> handle = strict ? storage.remove(obj) : storage.get(obj);
			if (strict) {
				handle.clear();
			}
			if (handle!=null) {
				remove(handle, strict);
			}
		} finally {
			lock.unlock();
		}
	}

	@Override
	public void remove(Handle<F> handle, boolean strict) throws ReasoningException {
		if (handle!=null) {
			HandleImpl<F> hImpl = (HandleImpl<F>) handle;
			if (strict) {
				hImpl.removed.set(true);
				F obj = hImpl.get();
				if (obj!=null) {
					Lock lock = storageLock.writeLock();
					lock.lock();
					try {
						storage.remove(obj);
					} finally {
						lock.unlock();
					}
					if (listener!=null) {
						listener.onRemove(obj, hImpl);
					}
					hImpl.onRemove(executor);
				}
			} else {
				hImpl.removed.set(true); // Temporarily for derivation validation.
				boolean hasValidDerivation = false;
				Set<Derivation<F>> derivations = hImpl.derivations;
				Lock lock = hImpl.derivationsLock.readLock();
				lock.lock();
				try {
					Iterator<Derivation<F>> dit = derivations.iterator();
					while (dit.hasNext()) {
						Derivation<F> next = dit.next();
						if (next.isValid()) {
							hasValidDerivation = true;
							break;
// We don't remove invalid derivations to avoid having to use write lock.							
//						} else {
//							dit.remove();
						}
					}
				} finally {
					lock.unlock();
				}
				if (hasValidDerivation) {
					hImpl.removed.set(false);
					// This fact is not known to be true by the external world. So its validity is based
					// on validity of derivations.
					hImpl.derived.set(true);					
				} else {
					remove(handle, true);
				}
			}
		} 
	}

	@Override
	public String getDescription() {
		return description;
	}

	@Override
	public String getName() {
		return name;
	}

	@Override
	public void close() throws ReasoningException {
		// NOP
	}

	@Override
	public void reset() throws ReasoningException {
		Lock lock = storageLock.writeLock();
		lock.lock();
		try {
			storage.clear();
		} finally {
			lock.unlock();
		}
		
		synchronized (collections) {
			Iterator<Reference<Collection<?>>> cit = collections.values().iterator();
			while (cit.hasNext()) {
				Reference<Collection<?>> next = cit.next();
				Collection<?> c = next.get();
				if (c==null) {
					cit.remove();
				} else {
					if (c instanceof ReadWriteLock) {
						Lock lck = ((ReadWriteLock) c).writeLock();
						lck.lock();
						try {
							c.clear();
						} finally {
							lck.unlock();
						}						
					} else {
						synchronized (c) {
							c.clear();
						}
					}
				}
			}
		}

	}

	@Override
	public void executeRules() throws ReasoningException {
		throw new UnsupportedOperationException("This method shall not be invoked on knowledge base.");
	}
	
	private InferenceListener<F> listener;
	private ExceptionHandler exceptionHandler;

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

	@Override
	public void setInferenceListener(InferenceListener<F> listener) throws ReasoningException {
		this.listener = listener;
	}
	
	private AtomicInteger duplicateDerivationCounter = new AtomicInteger();
	
	private class PutDerivedResultImpl implements PutDerivedResult<F> {

		PutDerivedResultImpl(Handle<F> handle, boolean isNew) {
			super();
			this.handle = handle;
			this.isNew = isNew;
		}

		private boolean isNew;
		private Handle<F> handle;

		@Override
		public Handle<F> getHandle() {
			return handle;
		}

		@Override
		public boolean isNew() {
			return isNew;
		}
		
	}

	@Override
	public PutDerivedResult<F> putConclusion(F conclusion, Object rule, String ruleName, String ruleDescription, Method method, List<Handle<F>> handleList) {
		Lock lock = storageLock.writeLock();
		lock.lock();
		
		try {			
			HandleImpl<F> hImpl = storage.get(conclusion);
			boolean isNew = false;
						
			if (hImpl==null) {
				isNew = true;
				hImpl = new HandleImpl<F>(conclusion);
				hImpl.derived.set(true);
				storage.put(conclusion, hImpl);
				if (listener!=null) {
					listener.onPut(conclusion, hImpl);
				}
			} else {
				// Check circular inference
				for (Handle<F> h: handleList) {
					if (h.equals(hImpl)) {
						logger.fine(conclusion + " - discarded, self-inference");
						return new PutDerivedResultImpl(hImpl, false);					
					}
					
					try {
						for (Derivation<F> d: getDerivations(h)) {
							if (d.isDerivedFrom(conclusion)) {
								logger.fine(conclusion + " - discarded, inference loop");
								return new PutDerivedResultImpl(hImpl, false);
							}					
						}
					} catch (ReasoningException e) {
						throw new RuntimeReasoningException(e);
					}					
				}				
			}
			
			Derivation<F> derivation = new DerivationImpl<F>(rule, ruleName, ruleDescription, method, handleList, this);
			
			Lock lck = hImpl.derivationsLock.writeLock();
			lck.lock();
			try {			
				if (!hImpl.derivations.add(derivation)) {
					// Duplicate derivation
					if (isFine) {
						Derivation<F> existingDerivation = null;
						for (Derivation<F> d: hImpl.derivations) {
							if (d.equals(derivation)) {
								existingDerivation = d;
							}
						}
						logger.info("Duplicate derivation #"+duplicateDerivationCounter.incrementAndGet()+" of "+conclusion
								+Constants.LINE_SEPARATOR+"  --- New derivation ---"+Constants.LINE_SEPARATOR
								+derivation
								+Constants.LINE_SEPARATOR+"  --- Existing derivation ---"+Constants.LINE_SEPARATOR
								+existingDerivation);
					}
				}
			} finally {
				lck.unlock();
			}
			hImpl.removed.set(false);
			return new PutDerivedResultImpl(hImpl, isNew);
		} finally {
			lock.unlock();
		}
	}

	@Override
	public Handle<F> getHandle(Object fact) {
		Lock lock = storageLock.readLock();
		lock.lock();
		try {
			HandleImpl<F> ret = storage.get(fact);
			return ret==null || ret.get()==null ? null : ret; 
		} finally {
			lock.unlock();
		}
	}

	/**
	 * Removes invalid entries. Similar to garbage collection. 
	 * Can be used with long running sessions with significant 
	 * rate of removals.
	 * @throws ReasoningException 
	 */
	@SuppressWarnings("unchecked")
	public void cleanup() throws ReasoningException {
		Lock lock = storageLock.writeLock();
		lock.lock();
		try {
			Iterator<Entry<F, HandleImpl<F>>> eit = storage.entrySet().iterator();
			while (eit.hasNext()) {
				Entry<F, HandleImpl<F>> entry = eit.next();
				if (entry.getValue().get()==null) {
					eit.remove();
				}
			}
		} finally {
			lock.unlock();
		}
		
		synchronized (collections) {
			Iterator<Reference<Collection<?>>> cit = collections.values().iterator();
			while (cit.hasNext()) {
				Reference<Collection<?>> next = cit.next();
				Collection<?> c = next.get();
				if (c==null) {
					cit.remove();
				} else {
					if (c instanceof ReadWriteLock) {
						Lock lck = ((ReadWriteLock) c).writeLock();
						lck.lock();
						try {
							Iterator<?> it = c.iterator();
							while (it.hasNext()) {
								Object nxt = it.next();
								if (nxt==null || (nxt instanceof Handle<?> && get((Handle<F>) nxt)==null)) {
									it.remove();
								}
							}
						} finally {
							lck.unlock();
						}						
					} else {
						synchronized (c) {
							Iterator<?> it = c.iterator();
							while (it.hasNext()) {
								Object nxt = it.next();
								if (nxt==null || (nxt instanceof Handle<?> && get((Handle<F>) nxt)==null)) {
									it.remove();
								}
							}
						}
					}
				}
			}
		}
	}

	@Override
	public boolean contains(Object fact) throws ReasoningException {
		Lock lock = storageLock.readLock();
		lock.lock();
		try {
			HandleImpl<?> h = storage.get(fact);
			return h!=null && h.get()!=null;
		} finally {
			lock.unlock();
		}
	}

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

	@Override
	public void setExceptionHandler(ExceptionHandler handler) throws ReasoningException {
		this.exceptionHandler = handler;		
	}

	@Override
	public boolean contains(Handle<F> handle) throws ReasoningException {
		F obj = ((HandleImpl<F>) handle).get();		
		return obj==null ? false : contains(obj);
	}

	@Override
	public Collection<Handle<F>> getHandles() throws ReasoningException {
		List<Handle<F>> ret = new ArrayList<Handle<F>>();
		Lock lock = storageLock.readLock();
		lock.lock();
		try {
			for (HandleImpl<F> hImpl: storage.values()) {
				F o = hImpl.get();
				if (o!=null) {
					ret.add(hImpl);
				}
			}
		} finally {
			lock.unlock();
		}
		return Collections.unmodifiableCollection(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 updateObject(Handle<F> handle, F fact) throws ReasoningException {
		Lock lock = storageLock.writeLock();
		lock.lock();
		try {
			remove(handle, true);
			((HandleImpl<F>) handle).init(fact);; 
			storage.put(fact, (HandleImpl<F>) handle);
			if (listener!=null) {
				listener.onPut(fact, handle);
			}
		} finally {
			lock.unlock();
		}
	}

	@Override
	public Map<Class<F>, Set<Class<F>>> getConclusionMap() {
		throw new UnsupportedOperationException("Knowledge base doesn't implement this operation");
	}
}
