package cases;

import utest.Assert;

class TestThreads implements utest.ITest
{
	public function new() { }

	function testSort() {
		var lock = new Lock();
		Thread.create(() -> {
			doTestSort();
			lock.release();
		});
		Assert.isTrue(lock.wait(40.0));
	}

	private function doTestSort()
	{
		var ts = new ThreadSort();
#if java
		ts.maxVal *= 10;
#end
		for (creatorWait in [.02, .05, 0])
			for (creatorLoad in [false, true])
				for (consumerWait in [.02,.05,0])
					for (useTls in [false,true])
						for (q in [new QDeque() #if java, new QLockFree()#end])
							for (lock in [ [new DequeSemaphore(), new LockSemaphore()], [new LockSemaphore(), new DequeSemaphore() ] ])
								{
									ts.creatorWait = creatorWait;
									ts.creatorLoad = creatorLoad;
									ts.consumerWait = consumerWait;
									ts.useTls = useTls;
									ts.queue = q;
									var lock1 = lock[0], lock2 = lock[1];
									ts.lock1 = lock1;
									ts.lock2 = lock2;
									try
									{
										ts.run();
										Assert.pass("ok");
									}
									catch(e:Dynamic)
									{
										Assert.fail('Error $e for para\\meters: $creatorWait, $creatorLoad, $consumerWait, $useTls, $q, $lock1, $lock2');
									}
								}
	}
}

class ThreadSort
{
	//params
	public var creatorWait:Seconds = .2;
	//should an extra load be performed on the creator?
	public var creatorLoad:Bool = false;
	public var consumerWait:Seconds = 0;
	//should an extra load be performed on the consumer?
	public var consumerLoad:Bool = false;
	public var useTls:Bool = false;

	public var nThreads:Int = 10;
	public var maxVal:Int = 1000;

	public var queue:QueueStrategy<Int>;
	public var lock1:SemaphoreStrategy;
	public var lock2:SemaphoreStrategy;

	public function new()
	{

	}

	public function run()
	{
		//spawning creators
		lock1.reset(); lock2.reset(); queue.reset();
		lock1.setReleaseCount(nThreads);
		lock2.setReleaseCount(nThreads);
		var finishedMutex = new Mutex();
		finishedMutex.acquire();

		var tls = new Tls();
		for (i in 0...nThreads)
		{
			Thread.create(function() {
				tls.value = i;
				Sys.sleep(creatorWait.secs());
				var i = useTls ? tls.value : i;
				for (j in 1...maxVal)
				{
					if (j % nThreads == i)
					{
						queue.add(j);
					}
					if (j % Std.int(maxVal / 10) == 0 && creatorLoad)
						Sys.sleep(.01);
				}
				//creator thread finished
				lock1.release();
			});
		}

		//spawning consumers
		if (consumerWait != 0)
			Sys.sleep(consumerWait.secs());

		var arr = [];
		for (i in 0...nThreads)
		{
			var myarr = [];
			arr.push(myarr);
			Thread.create(function() {
				var i = 0;
				while(true)
				{
					var val = queue.pop();
					if (val != null) {
						myarr.push(val);
					} else if (finishedMutex.tryAcquire()) {
						finishedMutex.release();
						break;
					}
					#if eval
					Thread.yield(); // no system threads, we need this or the scheduler has issues
					#end
					if (++i % Std.int(maxVal / 10) == 0 && consumerLoad)
						Sys.sleep(.01);
				}
				lock2.release();
			});
		}

		//wait creators to finish
		lock1.block();

		//release finishedMutex
		finishedMutex.release();

		//wait consumers to finish
		lock2.block();

		//start checking
		var mainarr = [];
		mainarr[maxVal] = maxVal; //prealloc

		for(a in arr)
		{
			for (a in a)
			{
				var val = mainarr[a];
				if (val == a)
					throw 'Same value detected for $val and $a';
				mainarr[a] = a;
			}
		}

		//no repeats, ok!

		for (i in 1...mainarr.length)
		{
			if (i != mainarr[i])
				throw 'No value found for $i and ${mainarr[i]}';
		}
	}
}

private interface SemaphoreStrategy //may be released by another thread
{
	function block():Void; //block until all semaphores are released
	function release():Void;
	function setReleaseCount(i:Int):Void;
	function reset():Void;
}

private class DequeSemaphore implements SemaphoreStrategy
{
	var d:Deque<Bool>;
	var c:Int = 0;

	public function new()
	{
		this.d = new Deque();
		this.c = 0;
	}

	public function block()
	{
		for (i in 0...c)
		{
			d.pop(true);
		}
		this.c = 0;
	}

	public function release()
	{
		d.push(true);
	}

	public function setReleaseCount(c:Int)
	{
		this.c = c;
	}

	public function reset()
	{
		this.d = new Deque();
		this.c = 0;
	}

	@:keep public function toString()
	{
		return "DequeSemaphore";
	}
}

private class LockSemaphore implements SemaphoreStrategy
{
	var l:Lock;
	var c:Int = 0;

	public function new()
	{
		this.l = new Lock();
		this.l.release();
	}

	public function block()
	{
		for(_ in 0...c)
		{
			Assert.isTrue(l.wait(1.));
		}
		this.c = 0;
	}

	public function release()
	{
		this.l.release();
	}

	public function setReleaseCount(c:Int)
	{
		this.c = c;
	}

	public function reset()
	{
		this.l = new Lock();
		this.c = 0;
	}

	@:keep public function toString()
	{
		return "LockSemaphore";
	}
}


private interface QueueStrategy<T>
{
	function add(t:T):Void;
	function pop():Null<T>; //not blocking
	function reset():Void;
}

private class QDeque<T> implements QueueStrategy<T>
{
	var q:Deque<T>;
	public function new()
	{
		this.q = new Deque();
	}

	public function add(t:T)
	{
		this.q.add(t);
	}

	public function pop():Null<T>
	{
		return this.q.pop(false);
	}

	public function reset()
	{
		this.q = new Deque();
	}

	@:keep public function toString()
	{
		return "QDeque";
	}
}

#if java
private class QLockFree<T> implements QueueStrategy<T>
{
	var q:java.vm.AtomicList<T>;

	public function new()
	{
		this.q = new java.vm.AtomicList();
	}

	public function add(t:T)
	{
		this.q.add(t);
	}

	public function pop():Null<T>
	{
		return this.q.pop();
	}

	public function reset()
	{
		this.q = new java.vm.AtomicList();
	}

	@:keep public function toString()
	{
		return "QLockFree";
	}
}
#end

private abstract Seconds(Float) from Float
{
	public inline function secs():Float
	{
		return this;
	}

	public inline function ms():Float
	{
		return this * 1000;
	}
}
