package com.hammurapi.common.concurrent;

import java.util.ArrayList;
import java.util.Collection;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

/**
 * Future task which cannot block in get() indefinitely. If it is not yet started,
 * the task gets executed in the caller thread. 
 * @author Pavel Vlasov
 *
 * @param <V>
 */
public class NonBlockingNotifyingFutureTask<V> extends FutureTask<V> implements NotifyingFuture<V> {
	
	private static class NotifyingCallable<T> implements Callable<T> {
		
		private Callable<T> master;
		
		Collection<CallableListener<T>> listeners = new ArrayList<CallableListener<T>>();

		public NotifyingCallable(Callable<T> master) {
			this.master = master;
		}

		public T call() throws Exception {
			try {
				T ret = master.call();
				for (CallableListener<T> listener: copy()) {
					listener.onCall(ret);
				}
				return ret;
			} catch (Exception e) {
				for (CallableListener<T> listener: copy()) {
					listener.onException(e);
				}
				throw e;
			}
		}
		
		private Collection<CallableListener<T>> copy() {
			synchronized (listeners) {
				return new ArrayList<CallableListener<T>>(listeners);
			}			
		}
	}

	private Collection<CallableListener<V>> listeners;
	private TaskCounter taskCounter;

	public NonBlockingNotifyingFutureTask(Callable<V> callable, TaskCounter taskCounter) {
		super(callable = new NotifyingCallable<V>(callable));	
		this.listeners = ((NotifyingCallable<V>) callable).listeners;
		if (taskCounter!=null) {
			taskCounter.onTaskCreated(this);
		}
		this.taskCounter = taskCounter;
	}
	
	@Override
	public void run() {
		try {
			super.run();
		} finally {
			if (taskCounter!=null) {
				taskCounter.onTaskFinished(this);
			}
		}
	}
	
	@Override
	public V get() throws InterruptedException, ExecutionException {
		if (!isDone()) {
			run();
		}
		return super.get();
	}

	public void addListener(CallableListener<V> listener) {
		synchronized (listeners) {
			listeners.add(listener);
		}		
	}

	public void removeListener(CallableListener<V> listener) {
		synchronized (listeners) {
			listeners.remove(listener);
		}
	}
	
}
