import java.util.*;
import org.apache.log4j.Logger;

/**
 * Copyright 2004 BayBridging.
 *
 * This software is furnished under a license and may be used
 * and copied only in accordance with the terms of such license.
 * This software or any other copies thereof in any form, may not be
 * provided or otherwise made available, to any other person or company
 * without written consent from BayBridging.
 *
 * BayBridging assumes no responsibility for the use or reliability of
 * software which has been modified without approval.
 */


public class ThreadPool
{
    private static final Log log = Logger.getLogger(ThreadPool.class);

    /**
     * Number of threads the pool contains
     */
    private int threadCount;

    /**
     * the request queue
     */
    private RequestQueue requestQueue;

    /**
     * list of worker threads
     */
    private List threadList;

    /**
     * flags whether the pool is alive
     */
    private boolean alive;

    /**
     * SuicideException used by the suicide pill to break out of the accept loop
     */
    private static class SuicideException
            extends RuntimeException
    {
    }

    /**
     * SuicidePill is a do nothing request that forces the an worker thread to quit.
     * It performs this by throwing a SuicideException.
     */
    private static class SuicidePill
            implements ThreadRequest
    {
        boolean executed = false;
        public synchronized void execute()
        {
            executed = true;
            throw new SuicideException(); // Terminate this thread cleanly
        }

        public String toString()
        {
            return "Suicide Pill";
        }

        /**
         * isExecuted
         *
         * @return boolean
         */
        public synchronized boolean isExecuted()
        {
            return executed;
        }
    }

    /**
     * @param threadCount int Number of threads to initialize the pool to.
     */
    public ThreadPool(int threadCount)
    {
        if (threadCount <= 0)
        {
            throw new IllegalArgumentException("Thread count must be greater than zero");
        }

        alive = false;
        this.threadCount = threadCount;
    }

    /**
     * Set request queue for the pool
     * @param queue RequestQueue
     * @throws ThreadPoolException thrown if the pool is running
     */
    public void setRequestQueue(RequestQueue queue) throws ThreadPoolException
    {
        if (queue == null)
    {
            throw new IllegalArgumentException("null request queue is not allowed");
        }

        if (!alive)
        {
            requestQueue = queue;
        }
        else
        {
            throw new ThreadPoolException("Thread pool is running! Request queue cant be changed.");
        }
    }

    /**
     * Make the pool start to work.
     */
    public void startup()
    {
        if (!alive)
        {
            alive = true;

            if (requestQueue == null)
            {
                // create default request queue if the queue is not set
                requestQueue = new SimpleRequestQueue();
            }
            
            // create thread list
            threadList = new ArrayList();

            // create worker threads
            for (int i = 0; i < threadCount; i++)
            {
                Thread t = new WorkerThread(this);
                synchronized (threadList)
                {
                    threadList.add(t);
                }

                log.debug("Start thread: " + t);
                t.start();
            }
        }
    }

    /**
     * Shut down the pool.
     * @param discardUndoneRequests flags whether to discard all undone requests
     */
    public void shutdown(boolean discardUndoneRequests)
    {
        if (!alive)
        {
            log.warn("Pool is not started");
            return;
        }

        if (discardUndoneRequests)
        {
            log.debug("Discard all undone requests");

            // remove all requests
            requestQueue.clear();
        }

        for (int i = 0; i < threadCount; i++)
        {
            try
            {
                // fill with Suicide pill to make worker threads quit
                submitRequest(new SuicidePill());
            }
            catch (ThreadPoolException e)
            {
                // we should not get here
            }
        }

        // wait for all threads to quit
        synchronized (threadList)
        {
            while (threadList.size() > 0)
            {
                try
                {
                    threadList.wait();
                }
                catch (InterruptedException ex)
                {
                    log.error(ex);
                }
            }
        }
        log.debug("All worker threads have quit.");

        requestQueue.clear();

        alive = false;
    }

    /**
     * Submit a reqeust to the pool.
     * @param request ThreadRequest
     * @throws ThreadPoolException Thrown if the pool is not alive.
     */
    public void submitRequest(ThreadRequest request) throws ThreadPoolException
    {
        if (!alive)
        {
            throw new ThreadPoolException("The thread pool is not alive.");
        }

        requestQueue.addRequest(request);
    }

    /**
     * Get requests from request queue and execute them.
     */
    private void executeRequests()
    {
        ThreadRequest request;
        while (true)
        {
            request = requestQueue.getRequest(keyForRequestQueue());
            // if no request or the request has been executed, continue to next request
            if (request == null || request.isExecuted())
            {
                continue;
            }

            try
            {
                if (log.isDebugEnabled())
                {
                    log.debug("Execute request: " + request + ", " + Thread.currentThread());
                }
                // execute the request
                request.execute();
            }
            catch (SuicideException e)
            {
                // got a suicide pill, the worker thread should quit
                log.debug("Terminate worker thread: " + Thread.currentThread());
                return;
            }
            catch (Throwable ex)
            {
                // we catch all other exceptions to prevent the worker thread quitting
                log.error(ex);
            }
        }
    }

    /**
     * get the object used to retrieve request from the request queue.<br>
     * Default returns current thread object.
     * @return Object
     */
    protected Object keyForRequestQueue()
    {
        return Thread.currentThread();
    }

    /**
     * called by a worker thread when it has finished execution.
     * @param t Thread
     */
    private void threadFinished(Thread t)
    {
        synchronized (threadList)
        {
            threadList.remove(t);
            threadList.notify();
        }
    }

    /**
     * <p>Title: </p>
     * <p>Description: Worker thread to execute requests.</p>
     */
    private static class WorkerThread
            extends Thread
    {
        private ThreadPool pool;

        WorkerThread(ThreadPool pool)
        {
            this.pool = pool;
        }

        public void run()
        {
            try
            {
                pool.executeRequests();
            }
            catch (Throwable e)
            {
                ThreadPool.log.error(e);
            }
            
            pool.threadFinished(this);
            pool = null;
        }
    }

}
