package project2;

import jv.vecmath.PiVector;

public class MultiThread {

	/* Array to store prime numbers */
	PiVector primes = new PiVector();
	/* Reference to main thread */
	Thread mainThread = Thread.currentThread();
	/* Variable for thread-communication */
	boolean threadIsWorking;
	/* Object for synchronization lock */
	Object lock;
	/* Timer instance */
	static long startTime;
	/*
	 * Start and end value for prime number calculation; make sure that end-start
	 * offers integer divisible by 3
	 */
	int[] range = new int[] { 2, 62 };
	/* Arrays to store numbers according to range */
	int[] numbers1, numbers2, numbers3;

	/**
	 * Start method
	 * 
	 * @param args
	 */
	public static void main(String[] args) {
		/* Instance of class */
		MultiThread multiThread = new MultiThread();
		/* Choice of example; see method description */
		multiThread.process(0);
	}

	/**
	 * Parallel examples addressed by parameter choice. 0 - Single Thread Processing
	 * 1 - Multiple Threads (Extended, Implemented, Main Thread) 2 - Scheduler a):
	 * Main Thread faster than sub threads 3 - Scheduler b): Main Thread waiting for
	 * sub threads 4 - Communication: Three threads communicating via variable 5 -
	 * Synchronization: Three threads using a lock for prime number assignment
	 * 
	 * @param choice Integer choice parameter, see description.
	 */
	@SuppressWarnings("static-access")
	void process(int choice) {
		startTimer();

		/* Instantiate Threads used in some cases */
		PrimeThreadExtended thread1, thread2;

		/* Fill number arrays according to range */
		int size = (range[1] - range[0] + 1) / 3;
		numbers1 = new int[size];
		numbers2 = new int[size];
		numbers3 = new int[size];
		for (int i = 0; i < size; i++) {
			numbers1[i] = 3 * i + range[0];
			numbers2[i] = 3 * i + 1 + range[0];
			numbers3[i] = 3 * i + 2 + range[0];
		}

		/* Select example by choice */
		switch (choice) {
		case 0:
			/*
			 * 
			 * Single thread: Main thread processes all numbers
			 * 
			 */
			w("_=Example: Single Thread=_");

			calculatePrimes(numbers1);
			calculatePrimes(numbers2);
			calculatePrimes(numbers3);
			w(primes.toShortString());
			break;

		case 1:
			/*
			 * 
			 * Threads: Two threads (one extended, one implemented) and main tread process
			 * numbers
			 * 
			 */
			w("_=Example: Threads=_");

			// Definition and Initialization of an extended thread
			thread1 = new PrimeThreadExtended("Thread1", numbers1);
			thread1.start();
			w(thread1.getName() + " started");

			// Definition and Initialization of an implemented thread
			PrimeThreadImplemented cl = new PrimeThreadImplemented(numbers2);
			Thread thread = new Thread(cl);
			thread.setName("Thread2");
			thread.start();
			w(thread.getName() + " started");

			// Main thread calculation
			calculatePrimes(numbers3);

			// Console output
			w(primes.toShortString());
			break;

		case 2:
			/*
			 * Scheduler a) Shows how main thread might finish before sub-threads
			 */
			w("_=Example: Scheduler a)=_");

			// Definition and Initialization of sub-threads
			thread1 = new PrimeThreadExtended("Thread1", numbers1);
			thread1.start();
			w(thread1.getName() + " started.");
			thread2 = new PrimeThreadExtended("Thread2", numbers2);
			thread2.start();
			w(thread2.getName() + " started.");

			// Console output
			w("Main thread finished.");
			break;

		case 3:
			/*
			 * 
			 * Scheduler b) Shows how main thread waits for sub-threads
			 * 
			 */
			w("_=Example: Scheduler b)=_");

			// Definition and Initialization of sub-threads
			thread1 = new PrimeThreadExtended("Thread1", numbers1);
			thread1.start();
			w(thread1.getName() + " started.");
			thread2 = new PrimeThreadExtended("Thread2", numbers2);
			thread2.start();
			w(thread2.getName() + " started.");

			// Solution A: Wait for termination of thread 1 and 2
			try {
				thread1.join();
				thread2.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

			// Solution B: As long as thread 1 and 2 are alive repeat loop
//			while (thread1.isAlive() || thread2.isAlive()) {
//				// a) Empty condition
//
//				// b) Extract processor
//				mainThread.yield();
//
//				// c) Set to sleep for time
//				try {
//					mainThread.sleep(100);
//				} catch (InterruptedException e) {
//					e.printStackTrace();
//				}
//			}

			// Console output
			w("Main thread finished.");
			break;

		case 4:
			/*
			 * 
			 * Communication
			 *
			 */
			w("_=Example: Communication=_");
			// No sub-thread is currently working
			threadIsWorking = false;

			// Definition and Initialization of sub-threads
			PrimeThreadExtendedCommunication thread1Comm = new PrimeThreadExtendedCommunication("Thread1", numbers1);
			thread1Comm.start();
			w(thread1Comm.getName() + " started.");
			PrimeThreadExtendedCommunication thread2Comm = new PrimeThreadExtendedCommunication("Thread2", numbers2);
			thread2Comm.start();
			w(thread2Comm.getName() + " started.");
			PrimeThreadExtendedCommunication thread3Comm = new PrimeThreadExtendedCommunication("Thread3", numbers3);
			thread3Comm.start();
			w(thread3Comm.getName() + " started.");

			// Wait for sub-threads to finish
			try {
				thread1Comm.join();
				thread2Comm.join();
				thread3Comm.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

			break;

		case 5:
			/*
			 *
			 * Synchronization
			 *
			 */
			w("_=Example: Synchronization=_");

			/* Instantiate new lock object */
			lock = new Object();

			// Definition and Initialization of sub-threads
			PrimeThreadExtendedSynchronized thread1Sync = new PrimeThreadExtendedSynchronized("Thread1", numbers1);
			thread1Sync.start();
			w(thread1Sync.getName() + " started");
			PrimeThreadExtendedSynchronized thread2Sync = new PrimeThreadExtendedSynchronized("Thread2", numbers2);
			thread2Sync.start();
			w(thread2Sync.getName() + " started");
			PrimeThreadExtendedSynchronized thread3Sync = new PrimeThreadExtendedSynchronized("Thread3", numbers3);
			thread3Sync.start();
			w(thread3Sync.getName() + " started");

			// Wait for sub-threads to finish
			try {
				thread1Sync.join();
				thread2Sync.join();
				thread3Sync.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}

			// Console output
			w(primes.toShortString());
			primes.sort();
			w(primes.toShortString());
			break;
		}

		getTime();
	}

	/*
	 * 
	 * 
	 * 
	 * Thread Declaration
	 * 
	 * 
	 * 
	 */
	/* Thread class by extension */
	class PrimeThreadExtended extends Thread {
		int[] numbers;

		public PrimeThreadExtended(String name, int[] nbr) {
			this.setName(name);
			numbers = nbr;
		}

		public void run() {
			calculatePrimes(numbers);
			w(this.getName() + " finished.");
		}
	}

	/* Thread class by extension with communication */
	class PrimeThreadExtendedCommunication extends Thread {
		int[] numbers;

		public PrimeThreadExtendedCommunication(String name, int[] nbr) {
			this.setName(name);
			numbers = nbr;
		}

		public void run() {
			while (getThreadAtWork()) {
			}
			setThreadAtWork();
			w(this.getName() + " is working.");
			calculatePrimes(numbers);
			w(this.getName() + " finished.");
			workIsDone();
		}
	}

	/* Thread class by extension with synchronization */
	class PrimeThreadExtendedSynchronized extends Thread {
		int[] numbers;

		public PrimeThreadExtendedSynchronized(String name, int[] nbr) {
			this.setName(name);
			numbers = nbr;
		}

		public void run() {
			calculatePrimesSynchronized(numbers);
			w(this.getName() + " finished.");
		}
	}

	/* Thread class by implementation */
	class PrimeThreadImplemented implements Runnable {
		int[] numbers;

		public PrimeThreadImplemented(int[] nbr) {
			numbers = nbr;
		}

		public void run() {
			calculatePrimes(numbers);
		}
	}

	/*
	 * 
	 * 
	 * 
	 * Methods Declaration
	 * 
	 * 
	 * 
	 */
	/* Communication getter of global variable */
	synchronized boolean getThreadAtWork() {
		return threadIsWorking;
	}

	/* Communication setter of global variable (starting work) */
	synchronized void setThreadAtWork() {
		threadIsWorking = true;
	}

	/* Communication setter of global variable (finishing work) */
	synchronized void workIsDone() {
		threadIsWorking = false;
	}

	/**
	 * Determination of primes within a range. Writes to global variable.
	 * 
	 * @param start Integer start value of range.
	 * @param end   Integer end value, which is included.
	 */
	void calculatePrimes(int[] numbers) {
		for (int number : numbers)
			if (isPrime(number))
				primes.addEntry(number);
	}

	/**
	 * Determination of primes within a range synchronized. Writes to global variable.
	 * 
	 * @param start Integer start value of range.
	 * @param end   Integer end value, which is included.
	 */
	void calculatePrimesSynchronized(int[] numbers) {
		for (int number : numbers)
			if (isPrime(number))
				synchronized (lock) {
					primes.addEntry(number);
				}
	}

	/**
	 * Non-efficient way to determine whether given number is prime.
	 * 
	 * @param number Passed number to be checked.
	 * @return True if number is prime, false otherwise.
	 */
	boolean isPrime(int number) {
		boolean isPrime = true;

		// Divide number by every number from 2 to number - 1
		for (int i = 2; i < number; i++)
			if (number % i == 0)
				isPrime = false;

		return isPrime;
	}

	/* Gets the current time and stores it. */
	public static void startTimer() {
		startTime = System.currentTimeMillis();
	}

	/* Prints the time since start. */
	public static void getTime() {
		w("Time (sec): " + (System.currentTimeMillis() - startTime) / 1000.);
	}

	/**
	 * Writes a string to console in shorter way.
	 * 
	 * @param str The string to be written.
	 */
	public static void w(String str) {
		System.out.println(str);
	}
}
