package cz.cuni.amis.utils.future;

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

import cz.cuni.amis.utils.TimeUnitToMillis;

/**
 * Used to combine multiple Future&lt;Boolean> together into one Future&lt;Boolean>.
 * <p><p>
 * During construction of the instance of this class you have to specify an array of
 * booleans you want to combine - then all methods will wait for all futures to end
 * (e.g. get(), etc.).
 * <p><p>
 * Note that you will probably want to use getAll() method to get respective Future results.
 * 
 * @author Jimmy
 *
 */
public class CombinedBooleanFuture implements Future<Boolean> {
	
	private Future<Boolean>[] futures;
	
	public CombinedBooleanFuture(Future<Boolean>[] futures) {
		this.futures = futures;
		if (this.futures == null) throw new IllegalArgumentException("'futures' can't be null");
	}

	@Override
	public boolean cancel(boolean mayInterruptIfRunning) {
		boolean canceled = true;
		for (Future<Boolean> future : futures) {
			canceled = canceled && future.cancel(mayInterruptIfRunning);
		}
		return canceled;
	}

	@Override
	public Boolean get() throws InterruptedException, ExecutionException {
		boolean result = true;
		for (Future<Boolean> future : futures) {
			Boolean futureResult = future.get(); 
			result = result && (futureResult != null ? futureResult : false);
		}
		return result;
	}

	@Override
	public Boolean get(long timeout, TimeUnit unit) throws InterruptedException,
			ExecutionException, TimeoutException {
		long timeoutMillis = TimeUnitToMillis.toMillis(timeout, unit);
		long start = System.currentTimeMillis();
		boolean result = true;
		for (Future<Boolean> future : futures) {
			long futureStart = System.currentTimeMillis();
			if (futureStart - start > timeoutMillis) throw new TimeoutException("timeouted after " + timeout + " " + unit);
			Boolean futureResult = future.get(timeoutMillis - (futureStart - start), TimeUnit.MILLISECONDS);
			result = result && (futureResult != null ? futureResult : false);
		}
		return result;
	}
	
	public Boolean[] getAll(long timeout, TimeUnit unit) throws InterruptedException,
			ExecutionException, TimeoutException {
		long timeoutMillis = TimeUnitToMillis.toMillis(timeout, unit);
		long start = System.currentTimeMillis();
		Boolean[] results = new Boolean[futures.length];
		int i = 0;
		for (Future<Boolean> future : futures) {
			long futureStart = System.currentTimeMillis();
			if (futureStart - start > timeoutMillis) throw new TimeoutException("timeouted after " + timeout + " " + unit);
			results[i++] = future.get(timeoutMillis - (futureStart - start), TimeUnit.MILLISECONDS);
		}
		return results;
	} 

	@Override
	public boolean isCancelled() {
		boolean result = false;
		for (Future<Boolean> future : futures) {
			Boolean futureResult = future.isCancelled(); 
			result = result || (futureResult != null ? futureResult : false);			
		}
		return result;
	}

	@Override
	public boolean isDone() {
		boolean result = true;
		for (Future<Boolean> future : futures) {			
			result = result && future.isDone();
		}
		return result;
	}
	
	public Future<Boolean>[] getFutures() {
		return futures;
	}

}
