/*
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.generic;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Consumer;
import static net.ranides.assira.junit.NewAssert.*;

import org.junit.Test;

/**
 *
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
public class LazyReferenceTest {

	@Test
	public void testUnique() {
		AtomicInteger ai = new AtomicInteger(5);
		LazySupplier<Integer> ref = LazySuppliers.unique(() -> ai.getAndIncrement());
		assertEquals(5, ref.compute().intValue());
		assertEquals(5, ref.compute().intValue());
		assertEquals(5, ref.compute().intValue());
	}
	
	@Test
	public void testLocal() throws Throwable {
		List<Throwable> errors = Collections.synchronizedList(new ArrayList<>());
		
		AtomicInteger ai = new AtomicInteger(5);
		LazySupplier<Integer> ref = LazySuppliers.local(() -> ai.getAndIncrement());

		CountDownLatch latch = new CountDownLatch(3);

		thread(0, errors::add, ()->{
			for(int i=0; i<5; i++) {
				sleep(50);
				assertEquals(5, ref.compute().intValue());
			}
			latch.countDown();
		});
		thread(50, errors::add, ()->{
			for(int i=0; i<5; i++) {
				sleep(50);
				assertEquals(6, ref.compute().intValue());
			}
			latch.countDown();
		});
		thread(100, errors::add, ()->{
			for(int i=0; i<5; i++) {
				sleep(50);
				assertEquals(7, ref.compute().intValue());
			}
			latch.countDown();
		});

		latch.await();

		if(!errors.isEmpty()) {
			throw errors.get(0);
		}
	}
	
	@Test
	public void testSharedVolatile() throws Throwable {
		List<Throwable> errors = Collections.synchronizedList(new ArrayList<>());
		
		AtomicInteger ai = new AtomicInteger(5);
		LazySupplier<Integer> ref = LazySuppliers.sharedVolatile(() -> ai.getAndIncrement());

		CountDownLatch latch = new CountDownLatch(20);

		for(long t=0, n=latch.getCount(); t<n; t++) {
			thread(50, errors::add, ()->{
				for(int i=0; i<10; i++) {
					sleep(50);
					Integer v = ref.compute();
					assertEquals(5, v.intValue());
				}
				latch.countDown();
			});
		}
		latch.await();

		if(!errors.isEmpty()) {
			throw errors.get(0);
		}
	}
	
	@Test
	public void testShared() throws Throwable {
		List<Throwable> errors = Collections.synchronizedList(new ArrayList<>());
		
		AtomicInteger ai = new AtomicInteger(5);
		LazySupplier<Integer> ref = LazySuppliers.shared(() -> ai.getAndIncrement());

		CountDownLatch latch = new CountDownLatch(20);

		for(long t=0, n=latch.getCount(); t<n; t++) {
			thread(50, errors::add, ()->{
				for(int i=0; i<10; i++) {
					sleep(50);
					Integer v = ref.compute();
					assertEquals(5, v.intValue());
				}
				latch.countDown();
			});
		}
		latch.await();

		if(!errors.isEmpty()) {
			throw errors.get(0);
		}
	}
	
	@Test
	public void testConcurrent() throws Throwable {
		List<Throwable> errors = Collections.synchronizedList(new ArrayList<>());
		
		AtomicInteger ai = new AtomicInteger(5);
		LazySupplier<Integer> ref = LazySuppliers.concurrent(() -> ai.getAndIncrement());

		CountDownLatch latch = new CountDownLatch(10);

		for(long t=0, n=latch.getCount(); t<n; t++) {
			thread(50, errors::add, ()->{
				for(int i=0; i<10; i++) {
					sleep(10);
					int v = ref.compute();
					if(v > 10) {
						fail("ref.value = " + v);
					}
				}
				latch.countDown();
			});
		}
		latch.await();

		if(!errors.isEmpty()) {
			throw errors.get(0);
		}
	}
	
	private static void sleep(long ms) {
		try {
			Thread.sleep(ms);
		} catch (InterruptedException $0) {
			/* do nothing */
		}
	}
	
	private static Thread thread(long delay, Consumer<Throwable> handler, Runnable runnable) {
		Thread thread = new Thread(()->{
			sleep(delay);
			runnable.run();
		});
		thread.setUncaughtExceptionHandler((t,e) -> handler.accept(e));
		thread.start();
		return thread;
	}
	
}
