package imseProc.core;

import java.util.Random;

import junit.framework.TestCase;

public class IMSEProcTest extends TestCase {

	private boolean updatesDone[];
	
	private int nUpdatesRun;
	private int lastCompleteUpdateID;
	private int isUpdating;
	private int nQueued;
	private int lastQueuedUpdateID;
	
	public void testNoOverlap() {
		updatesDone = new boolean[3];
		lastCompleteUpdateID = -1;
		isUpdating = -1;
		try{
			IMSEProc.ensureFinalUpdate(this, new Delay(0, 500));
			Thread.sleep(1000);
			assertTrue(updatesDone[0]);
			
			IMSEProc.ensureFinalUpdate(this, new Delay(1, 500));
			Thread.sleep(1000);
			assertTrue(updatesDone[1]);
			
			IMSEProc.ensureFinalUpdate(this, new Delay(2, 500));
			Thread.sleep(1000);
			assertTrue(updatesDone[2]);
			
		}catch(InterruptedException e){}
	}
	

	public void testOneAndFinal() {
		updatesDone = new boolean[2];
		lastCompleteUpdateID = -1;
		isUpdating = -1;
		try{
			IMSEProc.ensureFinalUpdate(this, new Delay(0, 400));
			Thread.sleep(10);
			IMSEProc.ensureFinalUpdate(this, new Delay(1, 400));
			Thread.sleep(500);
			assertTrue(updatesDone[0]);
			assertFalse(updatesDone[1]);
			
			Thread.sleep(500);
			assertTrue(updatesDone[0]);
			assertTrue(updatesDone[1]);
			
			
		}catch(InterruptedException e){}
	}
	
	public void testTwoAndFinalMissTwo() {
		updatesDone = new boolean[3];
		lastCompleteUpdateID = -1;
		isUpdating = -1;
		try{
			IMSEProc.ensureFinalUpdate(this, new Delay(0, 400));
			Thread.sleep(10);
			IMSEProc.ensureFinalUpdate(this, new Delay(1, 400)); //this one will get skipped
			IMSEProc.ensureFinalUpdate(this, new Delay(2, 400));
			Thread.sleep(500);
			assertTrue(updatesDone[0]);
			assertFalse(updatesDone[1]);
			assertFalse(updatesDone[2]);
			
			Thread.sleep(500);
			assertTrue(updatesDone[0]);
			assertFalse(updatesDone[1]);
			assertTrue(updatesDone[2]);
			
			
		}catch(InterruptedException e){}
	}
	
	public void testTwoAndFinalDoTwo() {
		updatesDone = new boolean[3];
		lastCompleteUpdateID = -1;
		isUpdating = -1;
		try{
			IMSEProc.ensureFinalUpdate(this, new Delay(0, 400));
			Thread.sleep(10);
			IMSEProc.ensureFinalUpdate(this, new Delay(1, 400));
			Thread.sleep(500);
			assertTrue(updatesDone[0]);
			assertFalse(updatesDone[1]);
			assertFalse(updatesDone[2]);
			
			//queue 2 while the 1 is running, so all 3 get run in the end
			IMSEProc.ensureFinalUpdate(this, new Delay(2, 400)); 
			Thread.sleep(500); //wait for 1 to finish
			
			assertTrue(updatesDone[0]);
			assertTrue(updatesDone[1]);
			assertFalse(updatesDone[2]);
			//wait for 2 to finish
			
			Thread.sleep(500);
			assertTrue(updatesDone[0]);
			assertTrue(updatesDone[1]);
			assertTrue(updatesDone[2]);
			
			
		}catch(InterruptedException e){}
	}

	/** Tests the acutal point of the updaters
	 * 
	 * a) Two updates should never be run at the same time
	 * b) After all is done, the last update should be the last queued
	 */
	public void testAgressively() {
		updatesDone = new boolean[0]; //don't need these
		nUpdatesRun = 0;
		lastCompleteUpdateID = -1;
		nQueued = 0;
		isUpdating = -1;
		
		(new Thread(new UpdateRequestor(1000, 100))).start();
		(new Thread(new UpdateRequestor(2000, 100))).start();
		(new Thread(new UpdateRequestor(3000, 100))).start();
		(new Thread(new UpdateRequestor(4000, 120))).start();
		
		for(int i=0; i < 70; i++){
			System.out.println(i + ": Queued = " + nQueued + ", Last queued = " + lastQueuedUpdateID + ",\tRun = " + nUpdatesRun + ", Last run =" + lastCompleteUpdateID);
			try {
				Thread.sleep(10);
			} catch (InterruptedException e) { }
		}
		
		
		System.out.println("Final state: Run = " + nUpdatesRun + ", last=" + lastCompleteUpdateID);
		
		//The last should definitely be the last of one of the threads 
		assertTrue(lastCompleteUpdateID == 1099 ||
					lastCompleteUpdateID == 2099 ||
					lastCompleteUpdateID == 3099 ||
					lastCompleteUpdateID == 4119);
								
		
		
		//However, this last check might occasionally fail if the 4th block actually finishes earlier despite its larger count 
		//(which it can do if iot always randomly chooses short time) and then something manages to queue and get interrupted
		//by the actual last queuer before it gets chance to write its lastQueuedUpdateID
		//So don't worry too much if this fires /occasionally/, just run it again
		assertEquals(lastCompleteUpdateID, lastQueuedUpdateID); 
	}
	
	
	/** Thread to try to fire updates simultaneously */
	private class UpdateRequestor implements Runnable {
		private int idBase, nToQueue;
		public UpdateRequestor(int idBase, int nToQueue) {
			this.idBase = idBase;
			this.nToQueue = nToQueue;
		}
		public void run() {
			Random r = new Random();
			for(int i=0; i < nToQueue; i++){
				IMSEProc.ensureFinalUpdate(this, new Delay(idBase + i, r.nextInt(10)));
				lastQueuedUpdateID = idBase + i;
				nQueued++;
				try {
					Thread.sleep(r.nextInt(10));
				} catch (InterruptedException e) { }
			}
		
		}
	}
	
	private class Delay implements Runnable {
		long time;
		int id;
		public Delay(int id, long time) {
			this.time = time;
			this.id = id;
		}

		@Override
		public void run() {
			synchronized (IMSEProcTest.this) {
				if(isUpdating >= 0)
					fail("In ID "+id+" but " + isUpdating + " is already updating at the same time!");
				isUpdating = id;
			}
			
			System.out.println("+" + id + ".run("+time+")");
			try {
				Thread.sleep(time);
			} catch (InterruptedException e) { }
			if(id < updatesDone.length)
				updatesDone[id] = true;
			nUpdatesRun++;
			lastCompleteUpdateID = id;
			System.out.println("-" + id + ".run()");
			
			synchronized (IMSEProcTest.this) {
				isUpdating = -1;
			}
			
		}
		
	}

}
