/**
 * 
 */
package com.hammurapi.reasoning.impl;

import java.lang.ref.Reference;
import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import java.util.concurrent.Executor;
import java.util.concurrent.FutureTask;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

import com.hammurapi.reasoning.Derivation;
import com.hammurapi.reasoning.Handle;

class HandleImpl<T> implements Handle<T> {
	
	private Reference<T> ref;
	
	HandleImpl(T obj) {
		ref = new WeakReference<T>(obj);
	}
	
	void init(T obj) {
		ref = new WeakReference<T>(obj);
		derivationsLock.writeLock().lock();
		try {
			derivations.clear();
		} finally {
			derivationsLock.writeLock().unlock();
		}
		
		derived.set(false);
		removed.set(false);
		
		synchronized (undoTasks) {
			undoTasks.clear();
		}
	}
	
	// True if this fact was not added through addObject method.
	// Not derived fact can still have derivations.
	AtomicBoolean derived = new AtomicBoolean(false);
	
	AtomicBoolean removed = new AtomicBoolean(false);
	
	ReadWriteLock derivationsLock = new ReentrantReadWriteLock();
	Set<Derivation<T>> derivations = new HashSet<Derivation<T>>();
	
	private Collection<FutureTask<?>> undoTasks = new ArrayList<FutureTask<?>>();

	void addUndo(Runnable undoTask) {
		synchronized (undoTasks) {
			undoTasks.add(new FutureTask<Object>(undoTask, null));
		}
	}
	
	void onRemove(Executor executor) {
		synchronized (undoTasks) {
			for (FutureTask<?> undoTask: undoTasks) {
				if (!undoTask.isCancelled() && !undoTask.isDone()) {
					if (executor==null) {
						undoTask.run();
					} else {
						executor.execute(undoTask);
					}
				}
			}
			undoTasks.clear();
		}
	}
	
	/**
	 * @return Handle's object or null if handle is invalid.
	 */
	T get() {
		if (removed.get()) {
			return null;
		}
				
		if (derived.get()) {
			boolean hasValidDerivation = false;
			Lock lock = derivationsLock.readLock();
			lock.lock();
			try {
				Iterator<Derivation<T>> dit = derivations.iterator();
				while (dit.hasNext()) {
					Derivation<T> 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) {
				return null;
			}
		}
		
		return ref==null ? null : ref.get();
	}
	
	void clear() {
		ref = null;
	}
	
	@Override
	public String toString() {
		return getClass().getName()+"("+get()+")";
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		T obj = get();
		result = prime * result + ((obj == null) ? 0 : obj.hashCode());
		return result;
	}

	@SuppressWarnings("unchecked")
	@Override
	public boolean equals(Object obj) {
		if (this == obj) {
			return true;
		}
		if (obj == null) {
			return false;
		}
		if (getClass() != obj.getClass()) {
			return false;
		}
		HandleImpl<T> other = (HandleImpl<T>) obj;
		T myFact = get();
		T otherFact = other.get();
		if (myFact == null) {
			return otherFact == null;
		} 
		
		return myFact.equals(otherFact);
	}
	
	
}