package com.limegroup.gnutella.util;

import com.limegroup.gnutella.util.ManagedThread;

import com.sun.java.util.collections.List;
import com.sun.java.util.collections.Vector;

/**
 * A queue of items to be processed.
 *
 * The queue processes the items in a seperate thread, allowing
 * the thread to be released when all items are processed.
 */
public class ProcessingQueue {
    
    /**
     * The list of items to be processed.
     */
    private final List QUEUE = new Vector();
    
    /**
     * The name of the thread to be created.
     */
    private final String NAME;
    
    /**
     * Whether or not the constructed thread should be a managed thread or
     * not.
     */
    private final boolean MANAGED;
    
    /**
     * The thread doing the processing.
     */
    private Thread _runner = null;
    
    /**
     * Constructs a new ProcessingQueue using a ManagedThread.
     */
    public ProcessingQueue(String name) {
        this(name, true);
    }
    
    /**
     * Constructs a new ProcessingQueue of the given name.  If managed
     * is true, uses a ManagedThread for processing.  Otherwise uses
     * a normal thread.
     */
    public ProcessingQueue(String name, boolean managed) {
        NAME = name;
        MANAGED = managed;
    }
    
    /**
     * Add the specified runnable to be processed.
     */
    public synchronized void add(Runnable r) {
        QUEUE.add(r);
        notify();
        if(_runner == null)
            startRunner();
    }
    
    /**
     * Starts a new runner.
     */
    private synchronized void startRunner() {
        if(MANAGED)
            _runner = new ManagedThread(new Processor(), NAME);
        else
            _runner = new Thread(new Processor(), NAME);

        _runner.setDaemon(true);
        _runner.start();
    }
    
    /**
     * The runnable that processes the queue.
     */
    private class Processor implements Runnable {
        public void run() {
            try {
                while(true) {
                    while(QUEUE.size() > 0) {
                        Runnable next = (Runnable)QUEUE.remove(0);
                        next.run();
                    }

                    // Ideally this would be in a finally clause -- but if it
                    // is then we can potentially ignore exceptions that were
                    // thrown.
                    synchronized(ProcessingQueue.this) {
                        // If something was added before we grabbed the lock,
                        // process those items immediately instead of waiting
                        if(!QUEUE.isEmpty())
                            continue;
                        
                        // Wait a little bit to see if something new is going
                        // to come in, so we don't needlessly kill/recreate
                        // threads.
                        try {
                            ProcessingQueue.this.wait(5 * 1000);
                        } catch(InterruptedException ignored) {}
                        
                        // If something was added and notified us, process it
                        // instead of exiting.
                        if(!QUEUE.isEmpty())
                            continue;
                        // Otherwise, exit
                        else
                            break;
                    }
                }
            } finally {
                // We must restart a new runner if something was added.
                // It's highly unlikely that something was added between
                // the try of one synchronized block & the finally of another,
                // but it technically is possible.
                // We cannot loop here because we'd lose any exceptions
                // that may have been thrown.
                synchronized(ProcessingQueue.this) {
                    if(!QUEUE.isEmpty())
                        startRunner();
                    else
                        _runner = null;
                }
            }
        }
    }
}
