package cz.znj.kvr.sw.pof.concurrent.lwfuture.concurrent.test;

import cz.znj.kvr.sw.pof.concurrent.lwfuture.concurrent.SettableFuture;
import org.junit.Assert;
import org.junit.Test;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CancellationException;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.function.Function;


public class AbstractFutureAsyncTest
{
	protected static class ThreadGroup implements AutoCloseable
	{
		public                          ThreadGroup(int threadCount, int actionCount)
		{
			result = new long[threadCount][actionCount];
		}

		public void                     assertResult()
		{
			for (int i = 0; i < result.length; ++i) {
				for (int j = 0; j < result[i].length; ++j) {
					Assert.assertEquals(1, result[i][j]);
				}
			}
		}

		public void                     init(int count)
		{
			fineBarrier = new AtomicBoolean(false);
			threadBarrier = new CyclicBarrier(count, () -> {
				try {
					Thread.sleep(10);
				}
				catch (InterruptedException e) {
					throw new RuntimeException(e);
				}
				fineBarrier.set(true);
			});
			threads = new LinkedList<>();
		}

		public void                     startGroupThreads(Function<Integer, Void> runner)
		{
			for (int i = 0; i < result.length; ++i) {
				final int id = i;
				startThread(() -> runner.apply(id));
			}
		}

		public Thread                   startThread(Runnable runner)
		{
			Thread thread = new Thread(() -> {
				try {
					threadBarrier.await();
				}
				catch (InterruptedException e) {
					throw new RuntimeException(e);
				}
				catch (BrokenBarrierException e) {
					throw new RuntimeException(e);
				}
				runner.run();
			});
			thread.start();
			threads.add(thread);
			return thread;
		}

		public void                     waitThreads()
		{
			for (Thread thread: threads) {
				try {
					thread.join();
				}
				catch (InterruptedException e) {
					throw new RuntimeException(e);
				}
			}
			threads = null;
		}

		@Override
		public void                     close()
		{
			if (threads != null) {
				for (Thread thread : threads)
					thread.interrupt();
				waitThreads();
			}
		}

		public long[][]                 result;

		protected List<Thread>          threads;

		private AtomicBoolean           fineBarrier;

		private CyclicBarrier           threadBarrier;
	}

	@Test
	public void                     testSuccessMatchProcessors() throws ExecutionException, InterruptedException
	{
		for (int tries = 0; tries < 20; ++tries) {
			SettableFuture<Void> future = new SettableFuture<>();
			long hit[] = new long[2];
			try (ThreadGroup group = new ThreadGroup(Math.max(1, Runtime.getRuntime().availableProcessors()-1), 20)) {
				group.init(group.result.length+1);
				group.startGroupThreads((Integer id) -> {
					long[] r = group.result[id];
					for (int i = 0; i < r.length; ++i) {
						final int aid = i;
						future.addListener(() -> {
							r[aid] = 1;
						});
					}
					;
					return null;
				});
				group.startThread(() -> {
					future.addListener(() -> {
						hit[0] = 1;
					});
					future.set(null);
					future.addListener(() -> {
						hit[1] = 1;
					});
				});
				group.waitThreads();
				group.assertResult();
				Assert.assertArrayEquals(new long[]{1, 1}, hit);
			}
		}
	}

	@Test
	public void                     testSuccessOverloadedProcessors() throws ExecutionException, InterruptedException
	{
		for (int tries = 0; tries < 20; ++tries) {
			SettableFuture<Void> future = new SettableFuture<>();
			long hit[] = new long[2];
			try (ThreadGroup group = new ThreadGroup(Math.max(1, Runtime.getRuntime().availableProcessors()*4), 20)) {
				group.init(group.result.length+1);
				group.startGroupThreads((Integer id) -> {
					long[] r = group.result[id];
					for (int i = 0; i < r.length; ++i) {
						final int aid = i;
						future.addListener(() -> {
							r[aid] = 1;
						});
					}
					;
					return null;
				});
				group.startThread(() -> {
					future.addListener(() -> {
						hit[0] = 1;
					});
					future.set(null);
					future.addListener(() -> {
						hit[1] = 1;
					});
				});
				group.waitThreads();
				group.assertResult();
				Assert.assertArrayEquals(new long[]{1, 1}, hit);
			}
		}
	}
}

@@ -512,16 +512,305 @@
 tion;%0A%0A%0A
+/**%0A * In these tests we want to raise probability of race condition as high as possible.%0A *%0A * We start the threads at the beginning than they meet on CyclicBarrier and after 2 milliseconds of busy loops we finally give a green to them.%0A *%0A * Each of the tests is repeated 100 times.%0A */%0A
 public c
@@ -1424,18 +1424,17 @@
 d.sleep(
-10
+2
 );%0A%09%09%09%09%7D
@@ -2096,24 +2096,58 @@
 n(e);%0A%09%09%09%09%7D%0A
+%09%09%09%09while (!fineBarrier.get()) %7B%7D%0A
 %09%09%09%09runner.r
@@ -2957,33 +2957,34 @@
 es = 0; tries %3C 
-2
+10
 0; ++tries) %7B%0A%09%09
@@ -3878,32 +3878,32 @@
 tedException%0A%09%7B%0A
-
 %09%09for (int tries
@@ -3918,16 +3918,17 @@
 ies %3C 20
+0
 ; ++trie

improve asynchronous tests