package winstone;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.AbstractExecutorService;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * Wraps {@link Executor} so that we only ask the wrapped Executor to execute N number of tasks
 * at any given time.
 *
 * <p>
 * The intention is to use this with {@link ThreadPoolExecutor} with {@link SynchronousQueue}
 * with unbounded max capacity (so that for up to N tasks we keep creating more threads for work,
 * but beyond that we start to push the tasks into the queue of an infinite capacity.)
 * 
 * <p>
 * This is necessary because {@link ThreadPoolExecutor} tries to push work into the queue
 * first and only create more threads once the queue is full, so for a queue with infinite
 * capacity it'll never create threads beyond the core pool size.
 * See http://www.kimchy.org/juc-executorservice-gotcha/ for more discussion of this.
 * 
 * <p>
 * Because there's no call back to tell us when the wrapped {@link ExecutorService} has
 * finished executing something, this class needs to hand out the next task slightly
 * before the wrapped {@link ExecutorService} is done with the previous task. The net result
 * is that the wrapped {@link ExecutorService} will end up running N+1 threads (of which
 * 1 is almost always idle.) I'm not sure how to fix this.
 * 
 * @author Kohsuke Kawaguchi
 */
public class BoundedExecutorService extends AbstractExecutorService {
    /**
     * The FIFO queue of tasks waiting to be handed to the wrapped {@link ExecutorService}.
     */
    private final List<Runnable> tasks = new LinkedList<Runnable>();
    
    private final ExecutorService base;
    private final int max;

    /**
     * How many tasks the wrapped {@link ExecutorService} is executing right now?
     * Touched only in a synchronized block.
     */
    private int current;

    private boolean isShutdown = false;

    public BoundedExecutorService(ExecutorService base, int max) {
        this.base = base;
        this.max = max;
    }

    public synchronized void execute(final Runnable r) {
        if (isShutdown)
            throw new RejectedExecutionException("already shut down");
        tasks.add(r);
        if (current < max)
            scheduleNext();
    }

    private synchronized void scheduleNext() {
        if (tasks.isEmpty()) {
            if (isShutdown)
                base.shutdown();
            return;
        }
        final Runnable task = tasks.remove(0);
        base.execute(new Runnable() {
            public void run() {
                try {
                    task.run();
                } finally {
                    done();
                }
            }
        });
        current++;
    }

    private synchronized void done() {
        current--;
        scheduleNext(); // we already know that current<max
    }

    public synchronized void shutdown() {
        isShutdown = true;
        if (tasks.isEmpty())
            base.shutdown();
    }

    public synchronized List<Runnable> shutdownNow() {
        isShutdown = true;
        List<Runnable> r = base.shutdownNow();
        r.addAll(tasks);
        tasks.clear();
        return r;
    }

    public boolean isShutdown() {
        return isShutdown;
    }

    public boolean isTerminated() {
        return base.isTerminated();
    }

    public boolean awaitTermination(long timeout, TimeUnit unit) throws InterruptedException {
        return base.awaitTermination(timeout,unit);
    }
}