package com.farata.lang.async.api;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.CountDownLatch;

import org.apache.commons.javaflow.api.continuable;

import com.farata.lang.async.core.AsyncExecutor;
import com.farata.lang.async.core.NoActiveAsyncCallException;


/**
 * @author Valery Silaev
 * 
 */
public class AsyncCall {

	@SafeVarargs
	public @continuable static <E extends Throwable> List<Object> await(CompletionStage<?>... conditions) 
			throws NoActiveAsyncCallException, E {
		
		return await(atLeast(conditions.length, conditions));
	}
	
	@SafeVarargs
	public @continuable static <E extends Throwable> List<Object> await(final int minCount, final CompletionStage<?>... conditions) 
			throws NoActiveAsyncCallException, E {
		
		return await(atLeast(minCount, conditions));
	}
	
	/**
	 * Wait for the {@link CompletionStage} within {@link async} method.
	 * 
	 * The {@link async} method will be suspended until {@link CompletionStage} returns or throws the result. 
	 */
	public @continuable static <T, E extends Throwable> T await(final CompletionStage<T> condition) 
			throws NoActiveAsyncCallException, E {
		
		return AsyncExecutor.await(condition);
	}
	
	public @continuable static <T, E extends Throwable> T await(final CompletionStage<T> condition, final Class<E> expectedException) 
			throws NoActiveAsyncCallException, E {
		
		return await(condition);
	}

	final public static <T> CompletableFuture<T> asyncResult(final T value) {
		throw new IllegalStateException("Method call must be replaced by bytecode enhancer");
	}
	
	@SafeVarargs
	private static CompletionStage<List<Object>> atLeast(final int n, final CompletionStage<?>... conditions) {
		if (n > conditions.length) {
			throw new IllegalArgumentException("The number of futures supplied is less than a number of futures to await");
		} else if (n == 0) {
			return CompletableFuture.completedFuture(Collections.emptyList());
		} else if (conditions.length == 1) {
			final CompletableFuture<List<Object>> result = new CompletableFuture<>();
			if (1 == 1)
			throw new RuntimeException("UNIMPLEMENTED");
//			conditions[0].whenComplete((r, e) -> {
//				if (null == e) {
//					result.complete(Collections.singletonList(r));
//				} else {
//					result.completeExceptionally(e);
//				}
//			});
			return result;
		} else {
			final List<Object> values = new ArrayList<>(Collections.nCopies(conditions.length, null));
			final List<Throwable> errors = new ArrayList<>(Collections.nCopies(conditions.length, null));
			
			int[] valuesCount = {0}, errorsCount = {0};
			
			final CountDownLatch latch = new CountDownLatch(n);
			int i = 0;
			
			for (final CompletionStage<?> future : conditions) {
				final int idx = i++;
				future.whenComplete((r, e) -> {
					if (null == e) {
						errors.set(idx, e);
						errorsCount[0]++;
					} else {
						values.set(idx, r);
						valuesCount[0]++;
					}
					latch.countDown();
				});
			}
			return CompletableFuture.supplyAsync(() -> {
				try {
					latch.await();
				} catch (final InterruptedException ex) {
					throw new CancellationException(ex.getMessage());
				}
				if (valuesCount[0] < n) {
					throw new CombinedCompletionException(errors);
				}
				return values;
			});
		}
	}
}

@@ -2232,73 +2232,8 @@
 ();%0A
-%09%09%09if (1 == 1)%0A%09%09%09throw new RuntimeException(%22UNIMPLEMENTED%22);%0A//
 %09%09%09c
@@ -2270,18 +2270,16 @@
 e) -%3E %7B%0A
-//
 %09%09%09%09if (
@@ -2295,31 +2295,40 @@
 ) %7B%0A
-//
 %09%09%09%09%09
-result.complete(
+final List%3CObject%3E value = 
 Coll
@@ -2351,21 +2351,47 @@
 nList(r)
+;%0A%09%09%09%09%09result.complete(value
 );%0A
-//
 %09%09%09%09%7D el
@@ -2395,18 +2395,16 @@
  else %7B%0A
-//
 %09%09%09%09%09res
@@ -2437,18 +2437,14 @@
 e);%0A
-//
 %09%09%09%09%7D%0A
-//
 %09%09%09%7D
@@ -2917,33 +2917,33 @@
 %7B%0A%09%09%09%09%09if (null 
-=
+!
 = e) %7B%0A%09%09%09%09%09%09err

Fix combined await condition (N of M and ExactlyOne)