package de.renew.engine.thread;

import java.util.concurrent.Callable;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.RunnableFuture;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

import org.apache.log4j.Logger;

import de.renew.util.ClassSource;
import de.renew.util.Semaphor;


/**
 * The <code>SimulationThreadPool</code> uses the factory pattern to provide
 * a way to execute simulation threads.
 * <p>
 * <strong>Thread properties:</strong>
 * All threads created and managed within this pool will have the same context
 * classloader and the same priority.  The <code>classloader</code>
 * is obtained from the {@link ClassSource} whenever the thread pool singleton
 * instance is created.  The priority must be configured after pool creation via
 * {@link #setMaxPriority(int)}.  Simulation threads carry a reference
 * to their current ancestor thread, if the ancestor thread is waiting for the
 * completion of the current task that the simulation thread executes.
 * <p>
 * <strong>Convenience wrapping:</strong>
 * The <code>SimulationThreadPool</code> provides four methods that
 * automatically execute a given piece of code within a simulation thread.
 * Which method to use depends on the amount of information a caller needs
 * about successful execution:
 * a) Is there need for a return value or an exception?
 * b) Is there need to wait until execution completes?
 * The methods are organized as follows (follow the links for more details):
 * </p>
 * <table>
 * <caption>Method organization</caption>
 * <tr><th>                   </th><th>asynchronous    </th><th>wait for completion    </th></tr>
 * <tr><th><code>void</code>  </th><td>{@link #execute}</td><td>{@link #executeAndWait}</td></tr>
 * <tr><th>result or exception</th><td>{@link #submit} </td><td>{@link #submitAndWait} </td></tr>
 * </table>
 * <p>
 * <strong>Softened singleton property:</strong>
 * The <code>SimulationThreadPool</code> follows the Singleton pattern.
 * There may be at most one instance which can be obtained (and created,
 * if necessary) via the static method {@link #getCurrent}.  The instance
 * can be discarded by calling {@link #cleanup} (with the side effect of
 * terminating all threads belonging to the discarded pool).
 * </p>
 * <p>
 * When a new simulation is set up, a preliminary thread pool may be
 * obtained (and created) via {@link #getNew}.  A subsequent call to
 * {@link #cleanup} makes this preliminary instance the current thread
 * pool instance, so that it replaces the previous one.  If the setup
 * needs to be rolled back, a call to {@link #discardNew()} disposes
 * of the preliminary instance.
 * </p>
 * <p>
 * The static method {@link #isSimulationThread()} may be used to check
 * whether the calling thread belongs to the current <code>SimulationThreadPool</code>
 * singleton.
 * </p>
 *
 * @author Benjamin Schleinzer
 * @author Matthias Wester-Ebbinghaus
 * @author Michael Duvigneau
 */
public class SimulationThreadPool extends ThreadPoolExecutor {
    private static final Logger LOGGER = Logger.getLogger(SimulationThreadPool.class);

    /**
     * Used to synchronise access to the static <code>singleton</code>
     * variable.
     */
    private static final Object LOCK = new Object();

    /**
     * Holds a reference to the one and only SimulationThreadPool instance. Set
     * by the method {@link #getCurrent} and reset by the method
     * {@link #cleanup}.
     */
    private static SimulationThreadPool _singleton = null;

    /**
     * Holds a temporary reference to the SimulationThreadPool instance that becomes
     * the next one and only singleton. Set
     * by the method {@link #getNew} and reset by the method
     * {@link #cleanup} or {@link #discardNew}.
     */
    private static SimulationThreadPool _newSingleton = null;

    /**
     * Holds a reference to the created SimulatorThreadFactory
     */
    private final SimulatorThreadFactory _factory;

    /**
     * Create a SimulationThreadPool with given priority
     *
     * @param priority
     *            the maximum allowed priority
     */
    //NOTICE signature
    private SimulationThreadPool(int priority) {
        super(
            0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<>(),
            new SimulatorThreadFactory());

        _factory = (SimulatorThreadFactory) getThreadFactory();
    }

    /**
     * Execute a Runnable and return when it's finished. If this method is not
     * called from a simulation thread a new thread will be started. Otherwise,
     * the old thread will be used.
     * <p>
     * If a separate simulation thread is used for execution, that thread will
     * reference the calling thread as ancestor to enable the advanced locking
     * scheme of {@link InheritableSimulationThreadLock}. The ancestor relation
     * has a lifetime limited to the execution time of this method. The calling
     * Thread is put on hold for exactly this time.
     * </p>
     *
     * @param task the task to execute
     */
    public void executeAndWait(Runnable task) {
        if (isMyThread()) {
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("Running simulation runnable directly: " + task);
            }
            task.run();
        } else {
            Semaphor semaphore = new Semaphor();
            BlockingSimulationRunnable thread =
                new BlockingSimulationRunnable(task, semaphore, Thread.currentThread());
            this.execute(thread);
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace("Running simulation runnable indirectly: " + task);
            }
            semaphore.P();
        }
        if (LOGGER.isTraceEnabled()) {
            LOGGER.trace("Running simulation runnable returned: " + task);
        }
    }

    /**
     * Execute a Callable and return when its Future object when finished. If
     * this method is not called from a simulation thread a new thread will be
     * started. Otherwise, the old thread will be used.
     * <p>
     * If a separate simulation thread is used for execution, that thread will
     * reference the calling thread as ancestor to enable the advanced locking
     * scheme of {@link InheritableSimulationThreadLock}. The ancestor relation
     * has a lifetime limited to the execution time of this method. The calling
     * Thread is put on hold for exactly this time.
     * </p>
     *
     * @param task the task to execute
     * @param <T> the return type of the Callable
     * @return the Future object from the executed task
     */
    public <T> Future<T> submitAndWait(Callable<T> task) {
        Future<T> returnValue;
        if (isMyThread()) {
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace(
                    "Running simulation callable directly:      " + task + " in "
                        + Thread.currentThread());
            }
            returnValue = new FutureTask<>(task);
            ((FutureTask<T>) returnValue).run();
        } else {
            //We need to switch to the right thread group
            Semaphor semaphore = new Semaphor();

            //Wrap callable in a callable that will block until the run method is done
            BlockingSimulationCallable<T> thread =
                new BlockingSimulationCallable<>(task, semaphore, Thread.currentThread());

            //Switch to right thread group
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace(
                    "Running simulation callable indirectly:     " + task + " in "
                        + Thread.currentThread());
            }
            returnValue = this.submit(thread);
            if (LOGGER.isTraceEnabled()) {
                LOGGER.trace(
                    "Waiting for simulation callable completion: " + task + " in "
                        + Thread.currentThread());
            }
            //Block till run method of callable is done
            semaphore.P();
        }
        LOGGER.trace(
            "Running simulation callable returned:       " + task + " in "
                + Thread.currentThread());
        return returnValue;
    }

    /**
     * {@inheritDoc}
     * <p>
     * If the given <code>task</code> is a {@link BlockingSimulationCallable},
     * wrap it as a {@link WrappedFutureTask} to keep the ancestor thread
     * relation intact. In all other cases, behave like the default
     * implementation.
     * </p>
     */
    @Override
    protected <T> RunnableFuture<T> newTaskFor(Callable<T> task) {
        RunnableFuture<T> futureTask;
        if (task instanceof BlockingSimulationCallable<T> blockingTask) {
            //We have a wrapped Callable we need to wrap the call to the Futures get() method
            //otherwise the user would get the Future of the BlockingSimulationCallable
            //instead of the expected Future of the submitted Callable
            futureTask = new WrappedFutureTask<>(blockingTask);
        } else {
            futureTask = super.newTaskFor(task);
        }
        return futureTask;
    }

    /**
     * Provides a temporary reference to the SimulationThreadPool instance that
     * becomes the next one and only singleton.
     * <p>
     * The temporary reference becomes final when {@link #cleanup()} is called
     * on the previous instance. If the preparation should be cancelled, the
     * temporary reference must be invalidated by a call to
     * {@link #discardNew()}.
     * </p>
     * {@link SimulationThreadPool} class documentation for details.
     *
     * @return the active SimulationThreadPool instance, if there is any.
     *         Returns <code>null</code> otherwise.
     */
    public static SimulationThreadPool getNew() {
        synchronized (LOCK) {
            if (_newSingleton == null) {
                _newSingleton = new SimulationThreadPool(Thread.NORM_PRIORITY); //NOTICE signature
            }
            return _newSingleton;
        }
    }

    /**
     * Provides a reference to the current SimulationThreadPool instance.
     *
     * @return the active SimulationThreadPool instance.   If there is none,
     *         one is created.
     */
    public static SimulationThreadPool getCurrent() {
        synchronized (LOCK) {
            if (_singleton == null) {
                _singleton = new SimulationThreadPool(Thread.NORM_PRIORITY); //NOTICE signature
            }
            return _singleton;
        }
    }

    /**
     * Provides a reference to the current SimulationThreadPool instance.
     *
     * @return the active SimulationThreadPool instance, if there is any.
     *         Returns <code>null</code> otherwise.
     */
    public static SimulationThreadPool getSimulationThreadPool() {
        return SimulationThreadPool.getCurrent();
    }

    /**
     * Checks whether the calling thread is a simulation
     * thread (belongs to the simulation thread group
     * provided by the SimulatorThreadFactory).
     * Use this call to ensure that a given task is being
     * executed (or not being) by a simulation thread.
     * Note: This method does <em>not</em> have the side effect of thread
     * pool instantiation (like, for example, {@link #getCurrent}).
     *
     * @return {@code true}, if the calling thread is a simulation thread, {@code false} otherwise
     */
    public static boolean isSimulationThread() {
        return (_singleton != null) && (_singleton.isMyThread());
    }

    /**
     * Checks whether the calling thread belongs to this thread pool instance.
     * It is checked whether the thread belongs to the simulation thread group
     * provided by this pool's {@link SimulatorThreadFactory}.
     * <p>
     * For most cases, a direct call to the static method {@link #isSimulationThread}
     * is easier and provides the correct result as well.  The result of this
     * per-instance method may differ only for the short period of time when a
     * second thread pool instance is under preparation.
     * </p>
     * @return <code>true</code>, if the calling thread belongs to this thread pool,  <code>false</code>, otherwise.
     */
    public boolean isMyThread() {
        return Thread.currentThread().getThreadGroup().equals(_factory.getThreadGroup());
    }

    /**
     * Checks whether the given thread belongs to this thread pool instance.
     * It is checked whether the thread belongs to the simulation thread group
     * provided by this pool's {@link SimulatorThreadFactory}.
     *
     * @param thread the given {@link Thread} to be checked whether it belongs to the simulation thread group
     * @return <code>true</code>, if the given thread belongs to this thread pool, <code>false</code>, otherwise.
     *
     */
    public boolean isMyThread(Thread thread) {
        return thread.getThreadGroup().equals(_factory.getThreadGroup());
    }

    /**
     * Destroys the SimulationThreadPool instance and tries to stop all
     * remaining threads gracefully. The next call of the methods
     * {@link #getCurrent()} and {@link #getSimulationThreadPool()} will
     * result in the creation of a new instance of the SimulationThreadPool.
     *
     * <p>
     * Reminder: If this method is called from within a simulation thread
     * the thread will naturally belong to the set of old threads and
     * should be discarded afterwards.
     * </p>
     *
     * @return true if successful
     */
    public static boolean cleanup() {
        synchronized (LOCK) {
            // if (result=false) return immediately before releasing
            // the singleton!
            SimulationThreadPool oldSingleton = _singleton;
            _singleton = _newSingleton;
            _newSingleton = null;
            try {
                oldSingleton.shutdownNow();
                return true;
            } catch (SecurityException e) {
                LOGGER.error(e);
                return false;
            }
        }
    }

    /**
     * Roll back an erroneous thread pool setup.  Must be called after
     * preparing a new pool instance via {@link #getNew()}, if the setup should
     * be cancelled for some reason.
     *
     * @see SimulationThreadPool class documentation
     **/
    public static void discardNew() {
        synchronized (LOCK) {
            SimulationThreadPool oldSingleton = _newSingleton;
            _newSingleton = null;
            oldSingleton.shutdown();
        }
    }

    /**
     * Set the maximum priority new threads can have. Already running threads
     * are not affected.
     * If the priority argument is less than {@literal @link Thread.MIN_PRIORITY}
     * or greater than {@literal @link Thread.MAX_PRIORITY}, the maximum priority
     * of the group remains unchanged.
     * The defaults are 1 as minimum priority and 10 as maximum priority.
     *
     * @param priority new maximum priority
     */
    public void setMaxPriority(int priority) {
        _factory.setMaxPriority(priority);
    }

    /**
     * Returns the maximum priority any new thread can have. Already running
     * threads might have higher priorities
     *
     * @return the maximum thread priority
     */
    public int getMaxPriority() {
        return _factory.getMaxPriority();
    }

    /**
     * Returns the logger for this class.
     *
     * @return the logger for this class
     */
    public static Logger getLogger() {
        return LOGGER;
    }

    @Override
    protected void beforeExecute(Thread thread, Runnable runnable) {
        SimulationThread simulationThread = (SimulationThread) thread;

        if (runnable instanceof BlockingSimulationRunnable bsr) {
            simulationThread.setAncestor(bsr.getAncestor());
        } else if (runnable instanceof WrappedFutureTask<?> wft) {
            simulationThread.setAncestor(wft.getCallable().getAncestor());
        }
    }

    /**
     * Inner class that provides a ThreadFactory with some special methods
     *
     * @author Benjamin Schleinzer
     *
     */
    static class SimulatorThreadFactory implements ThreadFactory {
        private final ThreadGroup _group;
        private final AtomicInteger _threadNumber = new AtomicInteger(1);
        private final ClassLoader _classLoader;

        private static final String THREAD_GROUP_NAME = "simulation-thread-group";
        private static final String NAME_PREFIX = "simulation-thread-";

        public SimulatorThreadFactory() {
            ThreadGroup top = Thread.currentThread().getThreadGroup();
            while (top.getParent() != null) {
                top = top.getParent();
            }

            // group = new ThreadGroup(top, "simulation-thread-group");
            _group = new ThreadGroup(THREAD_GROUP_NAME);
            // Load appropriate ClassLoader
            _classLoader = ClassSource.getClassLoader();
        }

        /**
         * {@inheritDoc}
         */
        @Override
        public Thread newThread(Runnable r) {
            Thread thread =
                new SimulationThread(_group, r, NAME_PREFIX + _threadNumber.getAndIncrement(), 0);
            thread.setContextClassLoader(_classLoader);
            if (thread.isDaemon()) {
                thread.setDaemon(false);
            }
            if (thread.getPriority() != Thread.NORM_PRIORITY) {
                thread.setPriority(Thread.NORM_PRIORITY);
            }
            assert (thread.getThreadGroup().equals(this.getThreadGroup()))
                : "Thread must belong to thread group of factory";
            return thread;
        }

        public ThreadGroup getThreadGroup() {
            return _group;
        }

        /**
         * Set the maximum priority new threads can have. Already running
         * threads are not affected
         *
         * @param priority
         *            new maximum priority
         */
        public void setMaxPriority(int priority) {
            _group.setMaxPriority(priority);
        }

        /**
         * Returns the maximum priority any new thread can have. Already
         * running threads might have higher priorities
         *
         * @return the maximum thread priority
         */
        public int getMaxPriority() {
            return _group.getMaxPriority();
        }
    }
}