/* -*- tab-width: 4 -*-
 *
 * Electric(tm) VLSI Design System
 *
 * File: ThreadPool.java
 *
 * Copyright (c) 2010, Static Free Software. All rights reserved.
 *
 * Electric(tm) is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 3 of the License, or
 * (at your option) any later version.
 *
 * Electric(tm) is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
package com.sun.electric.tool.util.concurrent.runtime.taskParallel;

import java.util.ArrayList;
import java.util.concurrent.Semaphore;

import com.sun.electric.tool.util.concurrent.datastructures.IStructure;
import com.sun.electric.tool.util.concurrent.debug.Debug;
import com.sun.electric.tool.util.concurrent.debug.LoadBalancing;
import com.sun.electric.tool.util.concurrent.exceptions.PoolExistsException;
import com.sun.electric.tool.util.concurrent.patterns.PTask;
import com.sun.electric.tool.util.concurrent.runtime.Scheduler;
import com.sun.electric.tool.util.concurrent.runtime.Scheduler.SchedulingStrategy;
import com.sun.electric.tool.util.concurrent.runtime.Scheduler.UnknownSchedulerException;
import com.sun.electric.tool.util.concurrent.runtime.ThreadID;
import com.sun.electric.tool.util.concurrent.utils.ConcurrentCollectionFactory;
import com.sun.electric.util.UniqueIDGenerator;

/**
 * 
 * Magic thread pool
 * 
 * @author Felix Schmidt
 * 
 */
public class ThreadPool extends IThreadPool {

	/**
	 * states of the thread pool. This is very similar to states of processes or
	 * tasks.
	 */
	public enum ThreadPoolState {
		New, Init, Started, Closed, Sleeps;
	}

	public enum ThreadPoolType {
		simplePool, synchronizedPool, userDefined
	}

	private IStructure<PTask> taskPool = null;
	private int numOfThreads = 0;
	private ArrayList<Worker> workers = null;
	private ThreadPoolState state;
	private UniqueIDGenerator generator;
	private ThreadPoolType type;

	/**
	 * prevent from creating thread pools via constructor
	 * 
	 * @param taskPool
	 * @param numOfThreads
	 */
	protected ThreadPool(IStructure<PTask> taskPool, int numOfThreads, ThreadPoolType type) {
		state = ThreadPoolState.New;
		this.taskPool = taskPool;
		this.numOfThreads = numOfThreads;
		this.generator = new UniqueIDGenerator(0);
		this.type = type;

		// reset thread id
		ThreadID.reset();

		workers = ConcurrentCollectionFactory.createArrayList();

		for (int i = 0; i < numOfThreads; i++) {
			workers.add(new Worker(this));
		}
		state = ThreadPoolState.Init;
	}

	/**
	 * start the thread pool
	 */
	public void start() {
		if (state == ThreadPoolState.Init) {
			for (Worker worker : workers) {
				worker.start();
			}
		}
		state = ThreadPoolState.Started;
	}

	/**
	 * shutdown the thread pool
	 */
	public void shutdown() throws InterruptedException {
		for (Worker worker : workers) {
			worker.shutdown();
		}

		taskPool.shutdown();

		if (workers.size() > 0)
			workers.get(0).strategy.trigger();

		this.join();
		state = ThreadPoolState.Closed;

		// print statistics in debug mode
		if (Debug.isDebug()) {
			LoadBalancing.getInstance().printStatistics();
			LoadBalancing.getInstance().reset();
		}
	}

	/**
	 * wait for termination
	 * 
	 * @throws InterruptedException
	 */
	public void join() throws InterruptedException {
		for (Worker worker : workers) {
			worker.join();
		}
	}

	/**
	 * Set thread pool to state sleep. Constraint: current State = started
	 */
	public void sleep() {
		if (state == ThreadPoolState.Started) {
			for (Worker worker : workers) {
				worker.sleep();
			}
			this.state = ThreadPoolState.Sleeps;
		}
	}

	/**
	 * Wake up the thread pool. Constraint: current State = sleeps
	 */
	public void weakUp() {
		if (this.state == ThreadPoolState.Sleeps) {
			for (Worker worker : workers) {
				worker.weakUp();
			}
			this.state = ThreadPoolState.Started;
		}
	}

	/**
	 * trigger workers (used for the synchronization)
	 */
	public void trigger() {
		if (workers.size() > 0)
			workers.get(0).strategy.trigger();
	}

	/**
	 * add a task to the pool
	 * 
	 * @param item
	 */
	public void add(PTask item) {
		taskPool.add(item);
	}

	/**
	 * add a task to the pool
	 * 
	 * @param item
	 */
	public void add(PTask item, int threadId) {
		taskPool.add(item, threadId);
	}

	/**
	 * 
	 * @return the current thread pool size (#threads)
	 */
	public int getPoolSize() {
		return this.numOfThreads;
	}

	/**
	 * Worker class. This class uses a worker strategy to determine how to
	 * process tasks in the pool.
	 */
	protected class Worker extends Thread {

		private ThreadPool pool;
		private PoolWorkerStrategy strategy;

		public Worker(ThreadPool pool) {
			this.pool = pool;
			ThreadID.set(generator.getUniqueId());
			strategy = PoolWorkerStrategyFactory.createStrategy(taskPool, type);
			if (Debug.isDebug()) {
				LoadBalancing.getInstance().registerWorker(strategy);
			}
		}

		@Override
		public void run() {

			pool.taskPool.registerThread();

			// execute worker strategy (all process of a worker is defined in a
			// strategy)
			strategy.execute();
		}

		/**
		 * shutdown the current worker
		 */
		public void shutdown() {
			strategy.shutdown();
			this.interrupt();
		}

		/**
		 * Danger: Could cause deadlocks
		 */
		public void sleep() {
			strategy.pleaseWait();
		}

		/**
		 * Danger: Could cause deadlocks
		 */
		public void weakUp() {
			strategy.pleaseWakeUp();
			synchronized (strategy) {
				strategy.notifyAll();
			}
		}

	}

	/**
	 * Factory class for worker strategy
	 */
	public static class PoolWorkerStrategyFactory {
		private static Semaphore trigger = new Semaphore(0);

		public static PoolWorkerStrategy userDefinedStrategy = null;

		public static PoolWorkerStrategy createStrategy(IStructure<PTask> taskPool, ThreadPoolType type) {
			if (type == ThreadPoolType.synchronizedPool)
				return new SynchronizedWorker(taskPool, trigger);
			else if (type == ThreadPoolType.simplePool) {
				return new SimpleWorker(taskPool);
			} else {
				if (userDefinedStrategy == null) {
					return createStrategy(taskPool, ThreadPoolType.simplePool);
				}

				userDefinedStrategy.setTaskPool(taskPool);

				return userDefinedStrategy;
			}
		}
	}

	private static ThreadPool instance = null;

	/**
	 * initialize thread pool, default initialization
	 * 
	 * @return initialized thread pool
	 * @throws PoolExistsException
	 * @throws UnknownSchedulerException
	 */
	public static ThreadPool initialize() throws PoolExistsException, UnknownSchedulerException {
		IStructure<PTask> scheduler = Scheduler.createScheduler(SchedulingStrategy.queue, getNumOfThreads());
		return ThreadPool.initialize(scheduler);
	}

	/**
	 * initialize thread pool with number of threads
	 * 
	 * @param num
	 *            of threads
	 * @return initialized thread pool
	 * @throws PoolExistsException
	 */
	public static ThreadPool initialize(int num) throws PoolExistsException {
		IStructure<PTask> taskPool = ConcurrentCollectionFactory.createLockFreeQueue();
		return ThreadPool.initialize(taskPool, num);
	}

	/**
	 * initialize thread pool with specific task pool
	 * 
	 * @param taskPool
	 *            to be used
	 * @return initialized thread pool
	 * @throws PoolExistsException
	 */
	public static ThreadPool initialize(IStructure<PTask> taskPool) throws PoolExistsException {
		return ThreadPool.initialize(taskPool, getNumOfThreads());
	}

	public static synchronized ThreadPool initialize(SchedulingStrategy taskPool, int numOfThreads)
			throws UnknownSchedulerException, PoolExistsException {
		IStructure<PTask> scheduler = Scheduler.createScheduler(taskPool, numOfThreads);
		return ThreadPool.initialize(scheduler, numOfThreads);
	}

	public static synchronized ThreadPool initialize(SchedulingStrategy taskPool, int numOfThreads,
			ThreadPoolType type) throws UnknownSchedulerException, PoolExistsException {
		IStructure<PTask> scheduler = Scheduler.createScheduler(taskPool, numOfThreads);
		return ThreadPool.initialize(scheduler, numOfThreads, type);
	}

	/**
	 * initialize thread pool with specific task pool and number of threads
	 * 
	 * @param taskPool
	 *            to be used
	 * @param numOfThreads
	 * @return initialized thread pool
	 * @throws PoolExistsException
	 */
	public static synchronized ThreadPool initialize(IStructure<PTask> taskPool, int numOfThreads)
			throws PoolExistsException {
		return ThreadPool.initialize(taskPool, numOfThreads, ThreadPoolType.simplePool);
	}

	/**
	 * initialize thread pool with specific task pool and number of threads
	 * 
	 * @param taskPool
	 *            to be used
	 * @param numOfThreads
	 * @return initialized thread pool
	 * @throws PoolExistsException
	 */
	public static synchronized ThreadPool initialize(IStructure<PTask> taskPool, int numOfThreads,
			ThreadPoolType type) throws PoolExistsException {
		if (ThreadPool.instance == null || instance.state != ThreadPoolState.Started) {
			System.out.println("ThreadPool: start thread pool");
			instance = new ThreadPool(taskPool, numOfThreads, type);
			instance.start();
		}

		return instance;
	}

	/**
	 * create a double thread pool (two thread pool side by side)
	 * 
	 * @param taskPool1
	 * @param numOfThreads1
	 * @param type1
	 * @param taskPool2
	 * @param numOfThreads2
	 * @param type2
	 * @return a double thread pool
	 */
	public static synchronized ThreadPool[] initialize(IStructure<PTask> taskPool1, int numOfThreads1,
			ThreadPoolType type1, IStructure<PTask> taskPool2, int numOfThreads2, ThreadPoolType type2) {

		ThreadPool[] result = new ThreadPool[2];

		result[0] = new ThreadPool(taskPool1, numOfThreads1, type1);
		result[1] = new ThreadPool(taskPool2, numOfThreads2, type2);

		result[0].start();
		result[1].start();

		return result;

	}

	/**
	 * hard shutdown of thread pool
	 */
	public static synchronized void killPool() {
		try {
			ThreadPool.instance.shutdown();
		} catch (InterruptedException e) {
		}
		ThreadPool.instance = null;
		IThreadPool.NUM_THREADS = null;
	}

	private static int getNumOfThreads() {
		if (IThreadPool.NUM_THREADS == null) {
			return Runtime.getRuntime().availableProcessors();
		}
		return IThreadPool.NUM_THREADS;
	}

	/**
	 * returns the current thread pool
	 * 
	 * @return thread pool
	 */
	public static ThreadPool getThreadPool() {
		return instance;
	}

	/**
	 * Get current state of the thread pool
	 * 
	 * @return current state of the thread pool
	 */
	public ThreadPoolState getState() {
		return state;
	}
}
