package com.limegroup.gnutella.util;

import com.sun.java.util.collections.*;

/**
 * A reuseable class that allows for a WorkGroup of WorkerInterface threads to
 * perform tasks.  All that needs to be done is to implement the WorkerInterface
 * (and optionally the CleanUpInterface) and supply instances as necessary.
 */
public class ThreadWorkGroup {

    /**
     * The interface that you should provide a implementation for.  Threads of
     * Workers will perform tasks (via doTask).
     */
    public static interface WorkerInterface extends Runnable {
        /** Given an Object[], check that the objects are in the correct order 
         *  and are of the correct type.  Please save the input for use during
         *  run.
         *  @return true if the input types are valid, false if not.
         */
        public boolean checkInput(Object[] input);

        /** Actually performs a 'task'.  Will call checkInput() with the
         *  Object[] before calling this.
         */
        public void run();
    }

    /** In case you want to provide a central cleanup mechanism.  The cleanUp()
     *  method will be called after any thread has performed doTask.
     */
    public static interface CleanUpInterface {
        public void cleanUp(Object[] usedTaskObjects);
    }

    // synchronizing access to the list of _tasks is necessary, so we may as
    // make all access synchronized by using a Vector.
    private List _tasks = new Vector();

    // the pool of worker threads.  use an expandable list so the user of this
    // class can add and kill threads.
    private List /* of WorkerInterface */_workers = new Vector();

    // the central cleanup mechanism - may be null
    private final CleanUpInterface _cleaner;

    // whether or not this work group is active.
    private boolean _stopped = false;

    /**
     * Starts workers.length threads that will execute upon input tasks (see
     * the addTask method).  isActive() is true after this call (and is true
     * until stop() is called).
     * @param workers The initial set of workers you want to be executing.
     * @param cleaner An optional cleaner (to be run after a worker performs a
     * task).  Can be null....
     * @throws IllegalArgumentException If workers is malformed.
     */    
    public ThreadWorkGroup(WorkerInterface[] workers, 
                           CleanUpInterface  cleaner) 
        throws IllegalArgumentException {
        // create worker threads....
        for (int i = 0; i < workers.length; i++) 
            addWorker(workers[i]);
        if (_workers.size() != workers.length)             
            throw new IllegalArgumentException("Invalid workers input!");
        
        _cleaner = cleaner;  // may be null but so what
    }

    /** @param task The input parameters to have a worker thread work on.
     *  The task will not immediately be worked on; rather when one of the
     *  worker threads becomes free it will execute the task.
     */    
    public void addTask(Object[] input) {
        synchronized (_tasks) {
            _tasks.add(input);
            // wake a worker up....
            _tasks.notify();
        }
    }

    /** @param worker A WorkerInterface instance to add to the mix.
     */
    public synchronized void addWorker(WorkerInterface worker) {
        if ((worker != null) && isActive()) {
            WorkerThread workerThread = new WorkerThread(worker);
            _workers.add(workerThread);
            workerThread.start();
        }
    }


    /** @return true if stop() has never been called.
     */
    public boolean isActive() {
        return !_stopped;
    }


    /** Once this is called, the ThreadWorkGroup is useless and all threads 
     *  are correctly extinguished (though they may finish what they are working
     *  on.
     *  @exception InterruptedException thrown because this call may block while
     *  threads are finishing....
     *  @param waitTime join's for up to waitTime millis, or forever if 0.
     *  @return true if none of the threads were alive upon returning from the
     *  method and after waiting waitTime per thread.
     */
    public synchronized boolean stop(int waitTime) throws InterruptedException {
        _stopped = true;
        boolean retVal = true;
        synchronized (_workers) {
            // interrupt everybody
            Iterator workers = _workers.iterator();
            while (workers.hasNext())
                ((Thread) workers.next()).interrupt();
            // wait for the stragglers....
            workers = _workers.iterator();
            while (workers.hasNext())
                ((Thread) workers.next()).join(waitTime);
            // make sure everybody is dead
            workers = _workers.iterator();
            while (workers.hasNext() && retVal)
                if (((Thread) workers.next()).isAlive())
                    retVal = false;
            _workers.clear();
        }
        return retVal;
    }

    private class WorkerThread extends ManagedThread {
        private final WorkerInterface _worker;
        
        public WorkerThread(WorkerInterface worker) {
            super("WorkerThread"); // TODO: Use name of WorkerInterface
            _worker = worker;
        }
        
        public void managedRun() {
            // isActive() is a global on/off switch
            while (isActive()) {
                try {
                    // get something to work with
                    Object[] workInput = null;
                    synchronized (_tasks) {
                        while (_tasks.isEmpty())
                            _tasks.wait();
                        workInput = (Object[]) _tasks.remove(0);
                    }

                    // if you can work on it, do it....
                    if (_worker.checkInput(workInput))
                        _worker.run();

                    // do any cleaning, if necessary...
                    if (_cleaner != null)
                        _cleaner.cleanUp(workInput);
                }
                catch (InterruptedException ie) {
                }
            }
        }
    }
    
    
}
