/*
 * @author Ranides Atterwim <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.atomic.AtomicInteger;
import java.util.function.Consumer;
import java.util.function.Supplier;
import static net.ranides.assira.junit.NewAssert.*;
import org.junit.Test;

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

	@Test
	public void testUnique() {
		AtomicInteger ai = new AtomicInteger(5);
		Supplier<Integer> ref = LazyReference.unique(() -> ai.getAndIncrement());
		assertEquals(5, ref.get().intValue());
		assertEquals(5, ref.get().intValue());
		assertEquals(5, ref.get().intValue());
	}
	
	@Test
	public void testLocal() throws InterruptedException, Throwable {
		List<Throwable> errors = Collections.synchronizedList(new ArrayList<>());
		
		AtomicInteger ai = new AtomicInteger(5);
		Supplier<Integer> ref = LazyReference.local(() -> ai.getAndIncrement());
		
		thread(0, errors::add, ()->{
			for(int i=0; i<5; i++) {
				sleep(50);
				assertEquals(5, ref.get().intValue());
			}
		});
		thread(50, errors::add, ()->{
			for(int i=0; i<5; i++) {
				sleep(50);
				assertEquals(6, ref.get().intValue());
			}
		});
		thread(100, errors::add, ()->{
			for(int i=0; i<5; i++) {
				sleep(50);
				assertEquals(7, ref.get().intValue());
			}
		}).join();

		if(!errors.isEmpty()) {
			throw errors.get(0);
		}
	}
	
	@Test
	public void testSharedVolatile() throws InterruptedException, Throwable {
		List<Throwable> errors = Collections.synchronizedList(new ArrayList<>());
		
		AtomicInteger ai = new AtomicInteger(5);
		Supplier<Integer> ref = LazyReference.sharedVolatile(() -> ai.getAndIncrement());
		
		for(int t=0; t<20; t++) {
			thread(50, errors::add, ()->{
				for(int i=0; i<10; i++) {
					sleep(50);
					Integer v = ref.get();
					assertEquals(5, v.intValue());
				}
			});
		}
		sleep(1000);

		if(!errors.isEmpty()) {
			throw errors.get(0);
		}
	}
	
	@Test
	public void testShared() throws InterruptedException, Throwable {
		List<Throwable> errors = Collections.synchronizedList(new ArrayList<>());
		
		AtomicInteger ai = new AtomicInteger(5);
		Supplier<Integer> ref = LazyReference.shared(() -> ai.getAndIncrement());
		
		for(int t=0; t<20; t++) {
			thread(50, errors::add, ()->{
				for(int i=0; i<10; i++) {
					sleep(50);
					Integer v = ref.get();
					assertEquals(5, v.intValue());
				}
			});
		}
		sleep(1000);

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

		for(int t=0; t<10; t++) {
			thread(50, errors::add, ()->{
				for(int i=0; i<10; i++) {
					sleep(10);
					int v = ref.get().intValue();
					if(v > 10) {
						fail("ref.value = " + v);
					}
				}
			});
		}
		sleep(500);

		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;
	}
	
}
