package nl.idgis.publisher.utils;

import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;

import akka.actor.ActorSystem;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

public class FutureUtilsTest {
	
	private FutureUtils f;
	
	private CompletableFuture<Object> testFuture;
	
	@Before
	public void setUp() {
		
		ActorSystem system = ActorSystem.create();
		f = new FutureUtils(system);
		
		testFuture = new CompletableFuture<>();
	}
	
	@After
	public void doAssert() throws Throwable {
		try {
			testFuture.get(1, TimeUnit.SECONDS);
		} catch(ExecutionException e) {
			throw e.getCause();
		}
	}

	@Test
	public void testCollector() throws Throwable {
		
		f
			.collect(f.successful("Hello world!"))			
			.collect(f.successful(42))
			.thenApply((String s, Integer i) -> {
				try {
					assertEquals("Hello world!", s);
					assertEquals(new Integer(42), i);
					testFuture.complete(true);
				} catch(Throwable t) {
					testFuture.completeExceptionally(t);
				}
				
				return null;
			});
	}
	
	@Test
	public void testCollectFailure() throws Throwable {
		f
			.collect(f.successful("Hello world!"))			
			.collect(f.failed(new Exception("Failure")))
			.thenApply((String s, Object o) -> {
				try {
					fail("result received");						
				} catch(Throwable t) {
					testFuture.completeExceptionally(t);
				}
				
				return null;
			}).exceptionally(t -> testFuture.complete(true));		
	}
	
	@Test
	public void testCollectReturnValue() {
		f
			.collect(		
				f
					.collect(f.successful("Hello world!"))
					.collect(f.successful(42))
					.thenApply((String s, Integer i) -> {						
						return 47;
					}))
			.thenApply((Integer i) -> {
				try {
					assertEquals(new Integer(47), i);
					testFuture.complete(true);
				} catch(Throwable t) {
					testFuture.completeExceptionally(t);
				}
				
				return null;
			});
	}
	
	@Test
	public void testCollectFlatReturnValue() {
		f
			.collect(		
				f
					.collect(f.successful("Hello world!"))
					.collect(f.successful(42))
					.thenCompose((String s, Integer i) -> {
						return f.successful(47);						
					}))
			.thenApply((Integer i) -> {
				try {
					assertEquals(new Integer(47), i);
					testFuture.complete(true);
				} catch(Throwable t) {
					testFuture.completeExceptionally(t);
				}
				
				return null;
			});
	}
	
	@Test
	public void testMap() throws Exception {
		Map<String, CompletableFuture<String>> input = new HashMap<>();
		
		input.put("foo", f.successful("bar"));
		
		CompletableFuture<Map<String, String>> outputFuture = f.map(input);
		assertNotNull(outputFuture);
		
		outputFuture.thenApply((Map<String, String> output) -> {
			try {
				assertEquals("bar", output.get("foo"));
				testFuture.complete(true);
			} catch(Throwable t) {
				testFuture.completeExceptionally(t);
			}
			
			return null;
		});
	}
	
	@Test
	public void testSupplierSequence() {
		f.supplierSequence(
			Arrays.asList(
				() -> f.successful("Hello"),
				() -> f.successful("world")))
				
				.thenAccept(list -> {
					try {
						assertEquals(list, Arrays.asList("Hello", "world"));
						
						testFuture.complete(true);
					} catch(Throwable t) {
						testFuture.completeExceptionally(t);
					}
				});
	}
	
	@Test
	public void testSequence() {
		f.sequence(
			Arrays.asList(
				f.successful("Hello"),
				f.successful("world")))
				
				.thenAccept(list -> {
					try {
						assertEquals(list, Arrays.asList("Hello", "world"));
						
						testFuture.complete(true);
					} catch(Throwable t) {
						testFuture.completeExceptionally(t);
					}
				});
	}
	
	@Test
	public void testCollect() {
		Arrays.asList(
			f.successful("Hello"), 
			f.successful("world")).stream()
				.collect(f.collect()).thenAccept(stream -> {
					try {
						assertEquals(
							stream.collect(Collectors.toList()), 
							Arrays.asList("Hello", "world"));
						
						testFuture.complete(true);
					} catch(Throwable t) {
						testFuture.completeExceptionally(t);
					}
				});
	}
	
	@Test
	public void testSequenceFailure() {
		f.sequence(
				Arrays.asList(
					f.successful("Hello"),
					f.failed(new IllegalStateException())))
					
				.exceptionally(e -> {
					try {
						assertTrue(e instanceof IllegalStateException);
						
						testFuture.complete(true);
					} catch(Throwable t) {
						testFuture.completeExceptionally(t);
					}
					
					return null;
				});
	}
	
	@Test
	public void testCast() {
		f.cast(f.successful(42), Number.class)
			.handle((n, throwable) -> {				
				try {
					assertNull(throwable);
					assertEquals(42, n);
					
					testFuture.complete(true);
				} catch(Throwable t) {
					testFuture.completeExceptionally(t);
				}
				
				return null;
		});
	}
	
	@Test
	public void testCastFailure() {
		f.cast(f.successful("Hello, world!"), Integer.class)
			.exceptionally(e -> {
				try {
					assertTrue(e instanceof WrongResultException);
					assertEquals("Hello, world!", ((WrongResultException)e).getResult());
					
					testFuture.complete(true);
				} catch(Throwable t) {
					testFuture.completeExceptionally(t);
				}
				
				return null;
			});
	}
	
	@Test
	public void testConcat() {
		f.concat(
			f.successful(Stream.of("a", "b")),
			f.successful(Stream.of("c"))).handle((stream, throwable) -> {
				try {
					Set<String> result = stream.collect(Collectors.toSet());
					
					assertTrue(result.contains("a"));
					assertTrue(result.contains("b"));
					assertTrue(result.contains("c"));
					
					testFuture.complete(true);
				} catch(Exception e) {
					testFuture.completeExceptionally(e);
				}
				
				return null;
			});
	}
}

@@ -3503,16 +3503,24 @@
  %7B%0D%0A%09%09f.
+%3CString%3E
 supplier

make eclipse happy