package com.googlecode.objectify.cache;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;


/**
 * <p>
 * A Future<?> wrapper that executes an abstract method with the result at some point after
 * the data becomes available.  A "best effort" is made to ensure execution, but it may be
 * left untriggered until the end of a request.
 * </p>
 * 
 * <p>
 * Notification will happen ONCE:
 * </p>
 * 
 * <ul>
 * <li>After get() is called</li>
 * <li>When the future is done and isDone() is called</li>
 * <li>At the end of a request that has the AsyncCacheFilter enabled.</li>
 * </ul>
 * 
 * <p>Use the AsyncCacheFilter for normal requests. For situations where a filter is not appropriate
 * (ie, the remote api) be sure to call PendingFutures.completeAllPendingFutures() manually.</p>
 *
 * <p>Note that if you are using this with Objectify, you probably want to use ObjectifyFilter.complete()
 * rather than PendingFutures or AsyncCacheFilter static methods.</p>
 * 
 * @author Jeff Schnitzer <jeff@infohazard.org>
 */
abstract public class TriggerFuture<T> implements Future<T>
{
	/** Wrap the raw Future<?> */
	protected Future<T> raw;
	
	/** If we have run the trigger() method already */
	boolean triggered = false;
	
	/** Wrap a normal Future<?> */
	public TriggerFuture(Future<T> raw) {
		this.raw = raw;
		
		// We now need to register ourself so that we'll get checked at future API calls
		PendingFutures.addPending(this);
	}
	
	/**
	 * This method will be called ONCE upon completion of the future, successful or not.
	 * Beware that this.get() may throw an exception.
	 */
	abstract protected void trigger();

	/* (non-Javadoc)
	 * @see java.util.concurrent.Future#cancel(boolean)
	 */
	@Override
	public boolean cancel(boolean mayInterruptIfRunning) {
		throw new UnsupportedOperationException("This makes my head spin. Don't do it.");
	}

	/* (non-Javadoc)
	 * @see java.util.concurrent.Future#isCancelled()
	 */
	@Override
	public boolean isCancelled()
	{
		return this.raw.isCancelled();
	}

	/**
	 * This version also checks to see if we are done and we still need to call the trigger.
	 * If so, it calls it.
	 * 
	 * @see java.util.concurrent.Future#isDone()
	 */
	@Override
	public boolean isDone() {
		boolean done = this.raw.isDone();
		
		if (!triggered && done) {
			this.triggered = true;
			PendingFutures.removePending(this);
			
			this.trigger();
		}

		return done;
	}
	
	/* (non-Javadoc)
	 * @see java.util.concurrent.Future#get()
	 */
	@Override
	public T get() throws InterruptedException, ExecutionException
	{
		try {
			return this.raw.get();
		} finally {
			this.isDone();
		}
	}

	/* (non-Javadoc)
	 * @see java.util.concurrent.Future#get(long, java.util.concurrent.TimeUnit)
	 */
	@Override
	public T get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException
	{
		try {
			return this.raw.get(timeout, unit);
		} finally {
			this.isDone();
		}
	}
}