// GPars - Groovy Parallel Systems
//
// Copyright © 2008--2011  The original author or authors
//
// 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 groovyx.gpars.scheduler;

import groovyx.gpars.util.PoolUtils;
import jsr166y.ForkJoinPool;

import java.util.concurrent.TimeUnit;

/**
 * Represents the actors' thread pool, which performs tasks on behalf of the actors. Uses a ForkJoinPool from JSR-166y
 * The actors' thread pool size defaults to the n + 1, where n is the number of processors/cores available on the machine.
 * The VM parameter -Dgpars.poolsize can be used the configure the default size of the actors' thread pool.
 * The resize() and resetDefaultSize() methods can be used to configure size of the thread pool at runtime.
 *
 * @author Vaclav Pech
 *         Date: Feb 27, 2009
 */
public class FJPool implements Pool {
    protected final ForkJoinPool pool;
    private final int configuredPoolSize;
    private static final long DEFAULT_SHUTDOWN_TIMEOUT = 30L;

    /**
     * Creates the pool with default number of threads.
     */
    public FJPool() {
        this(PoolUtils.retrieveDefaultPoolSize());
    }

    /**
     * Creates the pool with specified number of threads.
     *
     * @param configuredPoolSize The required size of the pool
     */
    public FJPool(final int configuredPoolSize) {
        PoolUtils.checkValidPoolSize(configuredPoolSize);
        this.configuredPoolSize = configuredPoolSize;
        pool = FJPool.createPool(configuredPoolSize);
    }

    /**
     * Creates the pool wrapping the provided ForkJoinPool
     *
     * @param pool The ForkJoinPool instance to wrap
     */
    public FJPool(final ForkJoinPool pool) {
        this.pool = pool;
        this.configuredPoolSize = pool.getPoolSize();
    }

    /**
     * Creates a fork/join pool of given size. Each thread will have the uncaught exception handler set
     * to print the unhandled exception to standard error output.
     *
     * @param poolSize The required pool size  @return The created thread pool
     * @return The newly created thread pool
     */
    private static ForkJoinPool createPool(final int poolSize) {
        assert poolSize > 0;

        final Thread.UncaughtExceptionHandler uncaughtExceptionHandler = new Thread.UncaughtExceptionHandler() {
            @Override
            @SuppressWarnings({"UseOfSystemOutOrSystemErr"})
            public void uncaughtException(final Thread t, final Throwable e) {
                System.err.println(Pool.UNCAUGHT_EXCEPTION_OCCURRED_IN_GPARS_POOL + t.getName());
                e.printStackTrace(System.err);
            }
        };

        return new ForkJoinPool(poolSize, ForkJoinPool.defaultForkJoinWorkerThreadFactory, uncaughtExceptionHandler, false);
    }

    /**
     * Resizes the thread pool to the specified value
     *
     * @param poolSize The new pool size
     */
    @Override
    public final void resize(final int poolSize) {
        throw new UnsupportedOperationException("ForkJoin pools can't change size");
    }

    /**
     * Sets the pool size to the default
     */
    @Override
    public final void resetDefaultSize() {
        resize(PoolUtils.retrieveDefaultPoolSize());
    }

    /**
     * Retrieves the current thread pool size
     *
     * @return The pool size
     */
    @Override
    public int getPoolSize() {
        return pool.getPoolSize();
    }

    /**
     * schedules a new task for processing with the pool
     *
     * @param task The task to schedule
     */
    @Override
    public void execute(final Runnable task) {
        pool.submit(task);
    }

    /**
     * Retrieves the internal executor service.
     *
     * @return The underlying thread pool
     */
    public final ForkJoinPool getForkJoinPool() {
        return pool;
    }

    /**
     * Gently stops the pool
     */
    @Override
    public final void shutdown() {
        pool.shutdown();
        try {
            pool.awaitTermination(FJPool.DEFAULT_SHUTDOWN_TIMEOUT, TimeUnit.SECONDS);
        } catch (InterruptedException ignored) {
            Thread.currentThread().interrupt();  // set the interrupted flag
        }
    }

    protected final int getConfiguredPoolSize() {
        return configuredPoolSize;
    }
}
