/*
 * Copyright (c) 2011-2014 Pivotal Software, Inc.
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */

package reactor.core.dispatch;

import reactor.core.support.NamedDaemonThreadFactory;

import java.util.concurrent.*;

/**
 * A {@code Dispatcher} that uses a {@link ThreadPoolExecutor} with an unbounded queue to dispatch events.
 *
 * @author Andy Wilkinson
 * @author Jon Brisbin
 * @author Stephane Maldini
 */
public class ThreadPoolExecutorDispatcher extends MultiThreadDispatcher {

	private final ExecutorService         executor;
	private final BlockingQueue<Runnable> workQueue;

	/**
	 * Creates a new {@literal ThreadPoolExecutorDispatcher} with the given {@literal poolSize} and {@literal backlog}.
	 * By default, a {@link java.util.concurrent.RejectedExecutionHandler} is created which runs the submitted {@code
	 * Runnable} in the calling thread. To change this behavior, specify your own.
	 *
	 * @param poolSize
	 * 		the pool size
	 * @param backlog
	 * 		the backlog size
	 */
	public ThreadPoolExecutorDispatcher(int poolSize, int backlog) {
		this(poolSize, backlog, "threadPoolExecutorDispatcher");
	}

	/**
	 * Create a new {@literal ThreadPoolExecutorDispatcher} with the given size, backlog, name, and {@link
	 * java.util.concurrent.RejectedExecutionHandler}.
	 *
	 * @param poolSize
	 * 		the pool size
	 * @param backlog
	 * 		the backlog size
	 * @param threadName
	 * 		the name prefix to use when creating threads
	 */
	public ThreadPoolExecutorDispatcher(int poolSize,
	                                    int backlog,
	                                    String threadName) {
		this(poolSize,
		     backlog,
		     threadName,
		     new LinkedBlockingQueue<Runnable>(backlog),
		     new RejectedExecutionHandler() {
			     @Override
			     public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
				     r.run();
			     }
		     });
	}

	/**
	 * Create a new {@literal ThreadPoolExecutorDispatcher} with the given size, backlog, name, and {@link
	 * java.util.concurrent.RejectedExecutionHandler}.
	 *
	 * @param poolSize
	 * 		the pool size
	 * @param backlog
	 * 		the backlog size
	 * @param threadName
	 * 		the name prefix to use when creating threads
	 * @param rejectedExecutionHandler
	 * 		the {@code RejectedExecutionHandler} to use when jobs can't be submitted to the thread pool
	 */
	public ThreadPoolExecutorDispatcher(int poolSize,
	                                    int backlog,
	                                    String threadName,
	                                    BlockingQueue<Runnable> workQueue,
	                                    RejectedExecutionHandler rejectedExecutionHandler) {
		super(poolSize, backlog);
		this.workQueue = workQueue;
		this.executor = new ThreadPoolExecutor(
				poolSize,
				poolSize,
				0L,
				TimeUnit.MILLISECONDS,
				workQueue,
				new NamedDaemonThreadFactory(threadName, getContext()),
				rejectedExecutionHandler
		);
	}

	/**
	 * Create a new {@literal ThreadPoolTaskExecutor} with the given backlog and {@link
	 * java.util.concurrent.ExecutorService}.
	 *
	 * @param backlog
	 * 		the task backlog
	 * @param poolSize
	 * 		the number of threads
	 * @param executor
	 * 		the executor to use to execute tasks
	 */
	public ThreadPoolExecutorDispatcher(int backlog, int poolSize, ExecutorService executor) {
		super(poolSize, backlog);
		this.executor = executor;
		this.workQueue = null;
	}

	@Override
	public boolean awaitAndShutdown(long timeout, TimeUnit timeUnit) {
		shutdown();
		try {
			if(!executor.awaitTermination(timeout, timeUnit)) {
				return false;
			}
		} catch(InterruptedException e) {
			Thread.currentThread().interrupt();
			return false;
		}
		return true;
	}

	@Override
	public void shutdown() {
		executor.shutdown();
		super.shutdown();
	}

	@Override
	public void forceShutdown() {
		executor.shutdownNow();
		super.forceShutdown();
	}

	@Override
	public long remainingSlots() {
		return workQueue != null ? workQueue.remainingCapacity() : Long.MAX_VALUE;
	}

	@Override
	protected void execute(Task task) {
		executor.execute(task);
	}

	@Override
	public void execute(Runnable command) {
		executor.execute(command);
	}

}
