/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.
 */

#ifndef _DECAF_UTIL_CONCURRENT_EXECUTORS_H_
#define _DECAF_UTIL_CONCURRENT_EXECUTORS_H_

#include <decaf/util/Config.h>

#include <decaf/lang/Thread.h>
#include <decaf/lang/Runnable.h>
#include <decaf/util/concurrent/Callable.h>

#include <decaf/lang/exceptions/NullPointerException.h>

namespace decaf {
namespace util {
namespace concurrent {

    class ThreadFactory;
    class ExecutorService;

    /**
     * Implements a set of utilities for use with Executors, ExecutorService, ThreadFactory,
     * and Callable types, as well as providing factory methods for instance of these
     * types configured for the most common use cases.
     *
     * @since 1.0
     */
    class DECAF_API Executors {
    private:

        Executors();
        Executors(const Executors&);
        Executors& operator= (const Executors&);

    private:

        /**
         * A Callable subclass that runs given task and returns given result
         */
        template<typename E>
        class RunnableAdapter : public decaf::util::concurrent::Callable<E> {
        private:

            decaf::lang::Runnable* task;
            bool owns;
            E result;

        private:

            RunnableAdapter(const RunnableAdapter&);
            RunnableAdapter operator= (const RunnableAdapter&);

        public:

            RunnableAdapter(decaf::lang::Runnable* task, bool owns, const E& result) :
                decaf::util::concurrent::Callable<E>(), task(task), owns(owns), result(result) {
            }

            virtual ~RunnableAdapter() {
                try{
                    if (owns) {
                        delete this->task;
                    }
                }
                DECAF_CATCHALL_NOTHROW()
            }

            virtual E call() {
                this->task->run();
                return result;
            }
        };

    public:

        virtual ~Executors();

        /**
         * Creates and returns a new ThreadFactory that expresses the default behavior for
         * ThreadFactories used in Executor classes.  The default factory create a new
         * non-daemon thread with normal priority and a name whose value is equal to
         * pool-N-thread-M, where N is the sequence number of this factory, and M is the
         * sequence number of the thread created by this factory.
         *
         * @return a new instance of the default thread factory used in Executors, the
         *          caller takes ownership of the returned pointer.
         */
        static ThreadFactory* getDefaultThreadFactory();

        /**
         * Creates a new ThreadPoolExecutor with a fixed number of threads to process incoming
         * tasks.  The thread pool will use an unbounded queue to store pending tasks.  At any
         * given time the maximum threads in the pool will be equal to the number given to this
         * factory method.  If a thread in the pool dies a new one will be spawned to take its
         * place in the pool.  Tasks that are submitted when all pooled threads are busy will
         * be held until a thread is freed if the pool has allocated its assigned number of
         * threads already.
         *
         * @param nThreads
         *      The number of threads to assign as the max for the new ExecutorService.
         *
         * @return pointer to a new ExecutorService that is owned by the caller.
         *
         * @throws IllegalArgumentException if nThreads is less than or equal to zero.
         */
        static ExecutorService* newFixedThreadPool(int nThreads);

        /**
         * Creates a new ThreadPoolExecutor with a fixed number of threads to process incoming
         * tasks.  The thread pool will use an unbounded queue to store pending tasks.  At any
         * given time the maximum threads in the pool will be equal to the number given to this
         * factory method.  If a thread in the pool dies a new one will be spawned to take its
         * place in the pool.  Tasks that are submitted when all pooled threads are busy will
         * be held until a thread is freed if the pool has allocated its assigned number of
         * threads already.
         *
         * @param nThreads
         *      The number of threads to assign as the max for the new ExecutorService.
         * @param threadFactory
         *      Instance of a ThreadFactory that will be used by the Executor to spawn new
         *      worker threads.  This parameter cannot be NULL.
         *
         * @return pointer to a new ExecutorService that is owned by the caller.
         *
         * @throws NullPointerException if threadFactory is NULL.
         * @throws IllegalArgumentException if nThreads is less than or equal to zero.
         */
        static ExecutorService* newFixedThreadPool(int nThreads, ThreadFactory* threadFactory);

        /**
         * Creates an Executor that uses a single worker thread operating off an unbounded queue
         * owned by the executor.  If the Executor's single thread should terminate for some reason
         * such as failure during the execution of a task, a new Thread will be created if the Executor
         * has not been shutdown and there are more tasks in the queue.  The Executor returned from this
         * method is owned by the caller but unlike the Executor returned from the method
         * newFixedThreadPool(1) this one cannot be reconfigurable to use more threads later on.
         *
         * @return a new Executor pointer that is owned by the caller.
         */
        static ExecutorService* newSingleThreadExecutor();

        /**
         * Creates an Executor that uses a single worker thread operating off an unbounded queue
         * owned by the executor.  If the Executor's single thread should terminate for some reason
         * such as failure during the execution of a task, a new Thread will be created if the Executor
         * has not been shutdown and there are more tasks in the queue.  The Executor returned from this
         * method is owned by the caller but unlike the Executor returned from the method
         * newFixedThreadPool(1) this one cannot be reconfigurable to use more threads later on.
         *
         * @param threadFactory
         *      Instance of a ThreadFactory that will be used by the Executor to spawn new
         *      worker threads.  This parameter cannot be NULL and ownership passes to the Executor.
         *
         * @return a new Executor pointer that is owned by the caller.
         *
         * @throws NullPointerException if threadFactory is NULL.
         */
        static ExecutorService* newSingleThreadExecutor(ThreadFactory* threadFactory);

        /**
         * Returns a new ExecutorService derived instance that wraps and takes ownership of the given
         * ExecutorService pointer.  The returned ExecutorService delegates all calls to the wrapped
         * ExecutorService instance but does not allow any configuration changes.  This method provides
         * a means of locking an ExecutorService instance configuration and prevents changes that might
         * be accomplished with casting.
         *
         * @param executor
         *      The ExecutorService pointer to wrap and take ownership of.
         *
         * @return a new ExecutorService pointer that is owned by the caller.
         *
         * @throws NullPointerException if ExecutorService is NULL.
         */
        static ExecutorService* unconfigurableExecutorService(ExecutorService* executor);

    public:

        /**
         * Returns a Callable object that, when called, runs the given task and returns the default
         * value of the template type E (or E()).
         *
         * @param task
         *      The Runnable task that is to be executed.
         * @param owns
         *      Does the callable instance own the given Runnable task pointer, default is true.
         *
         * @return a new Callable<E> pointer that is owned by the caller.
         *
         * @throws NullPointerException if the Runnable task is NULL
         */
        template<typename E>
        static Callable<E>* callable(decaf::lang::Runnable* task, bool owns = true) {

            if (task == NULL) {
                throw decaf::lang::exceptions::NullPointerException(__FILE__, __LINE__,
                    "The Runnable task argument cannot be NULL");
            }

            return new RunnableAdapter<E>(task, owns, E());
        }

        /**
         * Returns a Callable object that, when called, runs the given task and returns the default
         * value of the template type E (or E()).
         *
         * @param task
         *      The Runnable task that is to be executed.
         * @param result
         *      The value that is returned from the callable upon completion.
         * @param owns
         *      Does the callable instance own the given Runnable task pointer, default is true.
         *
         * @return a new Callable<E> pointer that is owned by the caller.
         *
         * @throws NullPointerException if the Runnable task is NULL
         */
        template<typename E>
        static Callable<E>* callable(decaf::lang::Runnable* task, const E& result, bool owns = true) {

            if (task == NULL) {
                throw decaf::lang::exceptions::NullPointerException(__FILE__, __LINE__,
                    "The Runnable task argument cannot be NULL");
            }

            return new RunnableAdapter<E>(task, owns, result);
        }

    private:

        static void initialize();
        static void shutdown();

        friend class decaf::internal::util::concurrent::Threading;

    };

}}}

#endif /* _DECAF_UTIL_CONCURRENT_EXECUTORS_H_ */
