package freenet.support;

import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

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

import freenet.node.PrioRunnable;
import freenet.support.io.NativeThread;

public class PrioritizedSerialExecutorTest {
	private Executor realExec;
	private PrioritizedSerialExecutor exec;

	private SynchronousQueue<String> completingJob;
	private List<String> completedJobs;

	private class J implements PrioRunnable {
		private int prio;
		private String name;

		J(String name, int prio) {
			this.name = name;
			this.prio = prio;
		}

		@Override
		public int getPriority() {
			return prio;
		}

		@Override
		public void run() {
			synchronized (this) {
				notifyAll();
			}
			try {
				assertTrue(exec.onThread());
				completingJob.put(name);
			} catch (InterruptedException e) {
				fail(e.toString());
			}
		}
	}

	@Before
	public void setUp() throws Exception {
		realExec = new PooledExecutor();
		completedJobs = new ArrayList<String>();
		completingJob = new SynchronousQueue<String>();
		exec = new PrioritizedSerialExecutor(NativeThread.MAX_PRIORITY, 10, 5, true);
	}

	private void Q(String j, int i, boolean waitForStart) throws InterruptedException {
		J job = new J(j, i);

		synchronized (job) {
			exec.execute(job, j);
			if (waitForStart)
				job.wait(5000);
		}
	}

	private void waitFor(int count) throws InterruptedException {
		int completed = 0;
		while (completed < count) {
			String s = completingJob.poll(5, TimeUnit.SECONDS);
			if (s == null)
				fail("Hang?");

			completed++;
			completedJobs.add(s);
		}
		System.out.println(completedJobs);
	}

	@Test
	public void testRun() throws InterruptedException {
		assertTrue(completedJobs.isEmpty());

		Q("J1", 0, false);
		Q("J2", 0, false);
		Q("J3", 0, false);
		Q("J4", 0, false);
		Thread.yield();
		Thread.sleep(10);

		assertTrue(completedJobs.isEmpty()); // not started yet!

		exec.start(realExec, "testRun"); // start !
		waitFor(4);

		assertTrue(completedJobs.contains("J1"));
		assertTrue(completedJobs.contains("J2"));
		assertTrue(completedJobs.contains("J3"));
		assertTrue(completedJobs.contains("J4"));

		assertFalse(exec.onThread());
	}

	@Test
	public void testRunPrio() throws InterruptedException {
		assertTrue(completedJobs.isEmpty());

		Q("JM", 9, false);
		Q("J8", 8, false);

		assertTrue(completedJobs.isEmpty()); // not started yet!

		assertEquals(0, exec.getWaitingThreadsCount());
		exec.start(realExec, "testRunPrio"); // start !

		waitFor(1); // JM

		Q("J2", 2, false);
		Q("JN", 4, false);
		Q("JO", 2, false);
		Q("JP", 3, false);

		assertEquals(0, exec.getQueueSize(9));
		assertEquals(1, exec.getQueueSize(3));
		assertEquals(2, exec.getQueueSize(2));

		waitFor(2); // J8,JN

		assertEquals(0, exec.getQueueSize(9));
		assertEquals(0, exec.getQueueSize(4));
		assertEquals(2, exec.getQueueSize(2));

		Thread.yield();
		Thread.sleep(10);

		Q("JQ", 4, false);
		Q("JR", 0, false);
		assertEquals(1, exec.getQueueSize(4));
		assertEquals(2, exec.getQueueSize(2));
		assertEquals(0, exec.getQueueSize(1));
		assertEquals(1, exec.getQueueSize(0));

		int[] r = exec.getQueuedJobsCountByPriority();
		assertTrue(
			Arrays.equals(new int[] { 1, 0, 2, 0, 1, 0, 0, 0, 0, 0 }, r) ||
			Arrays.equals(new int[] { 1, 0, 2, 1, 1, 0, 0, 0, 0, 0 }, r)
		);

		waitFor(5); // JP, JQ, J2, JO, JR

		int i = 0;
		for (String s : new String[] { "JM", "J8", "JN", "JP", "JQ", "J2", "JO", "JR" })
			assertEquals(s, s, completedJobs.get(i++));
	}
}
