/*
 * Copyright 2010-2015 Institut Pasteur.
 * 
 * This file is part of Icy.
 * 
 * Icy is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * Icy is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with Icy. If not, see <http://www.gnu.org/licenses/>.
 */
package icy.system.thread;

import icy.system.IcyExceptionHandler;
import icy.system.SystemUtil;

import java.awt.EventQueue;
import java.lang.reflect.InvocationTargetException;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

import javax.swing.SwingUtilities;

/**
 * Thread utilities class.
 * 
 * @author Stephane
 */
public class ThreadUtil
{
    /**
     * This class is used to catch exception in the EDT.
     */
    public static class CaughtRunnable implements Runnable
    {
        private final Runnable runnable;

        public CaughtRunnable(Runnable runnable)
        {
            super();

            this.runnable = runnable;
        }

        @Override
        public void run()
        {
            try
            {
                runnable.run();
            }
            catch (Throwable t)
            {
                IcyExceptionHandler.handleException(t, true);
            }
        }
    }

    /**
     * The minimum priority that a thread can have.
     */
    public final static int MIN_PRIORITY = Thread.MIN_PRIORITY;

    /**
     * The default priority that is assigned to a thread.
     */
    public final static int NORM_PRIORITY = Thread.NORM_PRIORITY;

    /**
     * The maximum priority that a thread can have.
     */
    public final static int MAX_PRIORITY = Thread.MAX_PRIORITY;

    // low priority background processor
    private static final Processor bgProcessor;
    // single Runnable / Callable instance processor
    private static final InstanceProcessor instanceProcessors[];
    // low priority single Runnable / Callable instance processor
    private static final InstanceProcessor bgInstanceProcessors[];

    static
    {
        if (SystemUtil.is32bits())
        {
            int wantedThread = SystemUtil.getNumberOfCPUs();
            wantedThread = Math.max(wantedThread, 2);

            // 32 bits JVM, limit the number of thread
            bgProcessor = new Processor(Math.min(wantedThread, 8));
            instanceProcessors = new InstanceProcessor[Math.min(wantedThread, 4)];
            bgInstanceProcessors = new InstanceProcessor[Math.min(wantedThread, 4)];
        }
        else
        {
            int wantedThread = SystemUtil.getNumberOfCPUs();
            wantedThread = Math.max(wantedThread, 4);

            // 64 bits JVM, can have higher limit
            bgProcessor = new Processor(Math.min(wantedThread, 16));
            instanceProcessors = new InstanceProcessor[Math.min(wantedThread, 8)];
            bgInstanceProcessors = new InstanceProcessor[Math.min(wantedThread, 8)];
        }

        bgProcessor.setPriority(MIN_PRIORITY);
        bgProcessor.setThreadName("Background processor");
        bgProcessor.setKeepAliveTime(3, TimeUnit.SECONDS);

        for (int i = 0; i < instanceProcessors.length; i++)
        {
            // keep these thread active
            instanceProcessors[i] = new InstanceProcessor(NORM_PRIORITY);
            instanceProcessors[i].setThreadName("Background instance processor");
            instanceProcessors[i].setKeepAliveTime(3, TimeUnit.SECONDS);
            bgInstanceProcessors[i] = new InstanceProcessor(MIN_PRIORITY);
            bgInstanceProcessors[i].setThreadName("Background instance processor (low priority)");
            bgInstanceProcessors[i].setKeepAliveTime(3, TimeUnit.SECONDS);
        }
    }

    /**
     * Shutdown all background runner.
     */
    public static void shutdown()
    {
        bgProcessor.shutdown();
        for (int i = 0; i < instanceProcessors.length; i++)
        {
            instanceProcessors[i].shutdown();
            bgInstanceProcessors[i].shutdown();
        }
    }

    /**
     * Return true if all background runner are shutdown and terminated.
     */
    public static boolean isShutdownAndTerminated()
    {
        for (int i = 0; i < instanceProcessors.length; i++)
        {
            if (!instanceProcessors[i].isTerminated())
                return false;
            if (!bgInstanceProcessors[i].isTerminated())
                return false;
        }
        return bgProcessor.isTerminated();
    }

    /**
     * @return true if the current thread is an AWT event dispatching thread.
     */
    public static boolean isEventDispatchThread()
    {
        return EventQueue.isDispatchThread();
    }

    /**
     * Invoke the specified <code>Runnable</code> on the AWT event dispatching thread.<br>
     * Any exception is automatically caught by Icy exception handler.
     * 
     * @param wait
     *        If set to true, the method wait until completion, in this case you have to take
     *        attention to not cause any dead lock.
     * @see #invokeLater(Runnable)
     * @see #invokeNow(Runnable)
     */
    public static void invoke(Runnable runnable, boolean wait)
    {
        if (wait)
            invokeNow(runnable);
        else
            invokeLater(runnable);
    }

    /**
     * @deprecated Use {@link #invokeNow(Runnable)} instead
     */
    @Deprecated
    public static void invokeAndWait(Runnable runnable)
    {
        invokeNow(runnable);
    }

    /**
     * Invoke the specified <code>Runnable</code> on the AWT event dispatching thread now and wait
     * until completion.<br>
     * Any exception is automatically caught by Icy exception handler, if you want to catch them use
     * {@link #invokeNow(Callable)} instead.<br>
     * Use this method carefully as it may lead to dead lock.
     */
    public static void invokeNow(Runnable runnable)
    {
        if (isEventDispatchThread())
        {
            try
            {
                runnable.run();
            }
            catch (Throwable t)
            {
                // the runnable thrown an exception
                IcyExceptionHandler.handleException(t, true);
            }
        }
        else
        {
            try
            {
                EventQueue.invokeAndWait(runnable);
            }
            catch (InvocationTargetException e)
            {
                // the runnable thrown an exception
                IcyExceptionHandler.handleException(e.getTargetException(), true);
            }
            catch (InterruptedException e)
            {
                // interrupt exception
                System.err.println("ThreadUtil.invokeNow(...) error :");
                IcyExceptionHandler.showErrorMessage(e, true);
            }
        }
    }

    /**
     * Invoke the specified <code>Runnable</code> on the AWT event dispatching thread.<br>
     * If we already are on the EDT the <code>Runnable</code> is executed immediately else it will
     * be executed later.
     * 
     * @see #invokeLater(Runnable, boolean)
     */
    public static void invokeLater(Runnable runnable)
    {
        invokeLater(runnable, false);
    }

    /**
     * Invoke the specified <code>Runnable</code> on the AWT event dispatching thread.<br>
     * Depending the <code>forceLater</code> parameter the <code>Runnable</code> can be executed
     * immediately if we are on the EDT.
     * 
     * @param forceLater
     *        If <code>true</code> the <code>Runnable</code> is forced to execute later even if we
     *        are on the Swing EDT.
     */
    public static void invokeLater(Runnable runnable, boolean forceLater)
    {
        final Runnable r = new CaughtRunnable(runnable);

        if ((!forceLater) && isEventDispatchThread())
            r.run();
        else
            EventQueue.invokeLater(r);
    }

    /**
     * Invoke the specified <code>Callable</code> on the AWT event dispatching thread now and return
     * the result.<br>
     * The returned result can be <code>null</code> when a {@link Throwable} exception happen.<br>
     * Use this method carefully as it may lead to dead lock.
     * 
     * @throws InterruptedException
     *         if the current thread was interrupted while waiting
     * @throws Exception
     *         if the computation threw an exception
     */
    public static <T> T invokeNow(Callable<T> callable) throws Exception
    {
        if (SwingUtilities.isEventDispatchThread())
            return callable.call();

        final FutureTask<T> task = new FutureTask<T>(callable);

        try
        {
            EventQueue.invokeAndWait(task);
        }
        catch (InvocationTargetException e)
        {
            if (e.getTargetException() instanceof Exception)
                throw (Exception) e.getTargetException();

            throw new Exception(e.getTargetException());
        }

        try
        {
            return task.get();
        }
        catch (ExecutionException e)
        {
            if (e.getCause() instanceof Exception)
                throw (Exception) e.getCause();

            throw new Exception(e.getCause());
        }
    }

    /**
     * Invoke the specified {@link Callable} on the AWT event dispatching thread.<br>
     * Depending the <code>forceLater</code> parameter the <code>Callable</code> can be executed
     * immediately if we are on the EDT.
     * 
     * @param forceLater
     *        If <code>true</code> the <code>Callable</code> is forced to execute later even if we
     *        are on the EDT.
     */
    public static <T> Future<T> invokeLater(Callable<T> callable, boolean forceLater)
    {
        final FutureTask<T> task = new FutureTask<T>(callable);
        invokeLater(task, forceLater);
        return task;
    }

    /**
     * Retrieve the instance processor (normal priority) to use for specified runnable.
     */
    private static InstanceProcessor getInstanceProcessor(Runnable runnable)
    {
        // get processor index from the hash code
        return instanceProcessors[runnable.hashCode() % instanceProcessors.length];
    }

    /**
     * Retrieve the instance processor (normal priority) to use for specified callable.
     */
    private static InstanceProcessor getInstanceProcessor(Callable<?> callable)
    {
        // get processor index from the hash code
        return instanceProcessors[callable.hashCode() % instanceProcessors.length];
    }

    /**
     * Retrieve the instance processor (low priority) to use for specified runnable.
     */
    private static InstanceProcessor getBgInstanceProcessor(Runnable runnable)
    {
        // get processor index from the hash code
        return bgInstanceProcessors[runnable.hashCode() % bgInstanceProcessors.length];
    }

    /**
     * Retrieve the instance processor (low priority) to use for specified callable.
     */
    private static InstanceProcessor getBgInstanceProcessor(Callable<?> callable)
    {
        // get processor index from the hash code
        return bgInstanceProcessors[callable.hashCode() % bgInstanceProcessors.length];
    }

    /**
     * @deprecated Use {@link #bgRun(Runnable)} instead and {@link #invokeNow(Runnable)} separately.
     * @see #bgRun(Runnable)
     */
    @Deprecated
    public static boolean bgRun(Runnable runnable, boolean onEDT)
    {
        return (bgProcessor.submit(runnable, onEDT) != null);
    }

    /**
     * @deprecated Use {@link #bgRun(Runnable)} instead and check for acceptance.
     */
    @Deprecated
    public static void bgRunWait(Runnable runnable)
    {
        while (!bgRun(runnable))
            ThreadUtil.sleep(1);
    }

    /**
     * Adds background processing (low priority) of specified Runnable.<br>
     * Returns <code>false</code> if background process queue is full.<br>
     * Don't use this method for long process (more than 1 second) as the number of thread is
     * limited and others processes may be executed too late.
     */
    public static boolean bgRun(Runnable runnable)
    {
        return (bgProcessor.submit(true, runnable) != null);
    }

    /**
     * @deprecated Use {@link #bgRun(Callable)} and {@link #invokeNow(Callable)} separately instead.
     * @see #bgRun(Callable)
     */
    @Deprecated
    public static <T> Future<T> bgRun(Callable<T> callable, boolean onEDT)
    {
        return bgProcessor.submit(callable, onEDT);
    }

    /**
     * Adds background processing (low priority) of specified Callable task.<br>
     * Returns a Future representing the pending result of the task or <code>null</code> if
     * background process queue is full.<br>
     * Don't use this method for long process (more than 1 second) as the number of thread is
     * limited and others processes may be executed too late.
     */
    public static <T> Future<T> bgRun(Callable<T> callable)
    {
        return bgProcessor.submit(callable);
    }

    /**
     * @deprecated Use {@link #runSingle(Runnable)} instead and {@link #invokeNow(Runnable)} separately.
     * @see #bgRunSingle(Runnable)
     */
    @Deprecated
    public static boolean bgRunSingle(Runnable runnable, boolean onEDT)
    {
        final InstanceProcessor processor = getInstanceProcessor(runnable);
        return (processor.submit(runnable, onEDT) != null);
    }

    /**
     * @deprecated Use {@link #runSingle(Callable)} and {@link #invokeNow(Callable)} separately
     *             instead.
     * @see #bgRunSingle(Callable)
     */
    @Deprecated
    public static <T> Future<T> bgRunSingle(Callable<T> callable, boolean onEDT)
    {
        final InstanceProcessor processor = getInstanceProcessor(callable);
        return processor.submit(callable, onEDT);
    }

    /**
     * Adds single processing (low priority) of specified Runnable.<br>
     * If this <code>Runnable</code> instance is already pending in single processes queue then
     * nothing is done.<br>
     * Returns <code>false</code> if single processes queue is full.<br>
     * Don't use this method for long process (more than 1 second) as the number of thread is
     * limited and others processes may be executed too late.
     */
    public static boolean bgRunSingle(Runnable runnable)
    {
        final InstanceProcessor processor = getBgInstanceProcessor(runnable);
        return (processor.submit(true, runnable) != null);
    }

    /**
     * Adds single processing (low priority) of specified Callable task.<br>
     * If this <code>Callable</code> instance is already pending in single processes queue then
     * nothing is done.<br>
     * Returns a Future representing the pending result of the task or <code>null</code> if
     * single processes queue is full.<br>
     * Don't use this method for long process (more than 1 second) as the number of thread is
     * limited and others processes may be executed too late.
     */
    public static <T> Future<T> bgRunSingle(Callable<T> callable)
    {
        final InstanceProcessor processor = getBgInstanceProcessor(callable);
        return processor.submit(callable);
    }

    /**
     * Add single processing (normal priority) of specified Runnable.<br>
     * If this <code>Runnable</code> instance is already pending in single processes queue then
     * nothing is done.<br>
     * Return <code>false</code> if single processes queue is full.<br>
     * Don't use this method for long process (more than 1 second) as the number of thread is
     * limited and others processes may be executed too late.
     */
    public static boolean runSingle(Runnable runnable)
    {
        final InstanceProcessor processor = getInstanceProcessor(runnable);
        return (processor.submit(true, runnable) != null);
    }

    /**
     * Add single processing (normal priority) of specified Callable task.<br>
     * If this <code>Callable</code> instance is already pending in single processes queue then
     * nothing is done.<br>
     * Return a Future representing the pending result of the task or <code>null</code> if
     * single processes queue is full.<br>
     * Don't use this method for long process (more than 1 second) as the number of thread is
     * limited and others processes may be executed too late.
     */
    public static <T> Future<T> runSingle(Callable<T> callable)
    {
        final InstanceProcessor processor = getInstanceProcessor(callable);
        return processor.submit(callable);
    }

    /**
     * Return true if the specified runnable is waiting to be processed in background processing.
     */
    public static boolean hasWaitingBgTask(Runnable runnable)
    {
        return bgProcessor.getWaitingTasksCount(runnable) > 0;
    }

    /**
     * Return true if the specified callable is waiting to be processed in background processing.
     */
    public static boolean hasWaitingBgTask(Callable<?> callable)
    {
        return bgProcessor.getWaitingTasksCount(callable) > 0;
    }

    /**
     * Return true if the specified runnable is waiting to be processed<br>
     * in single scheme background processing (low priority).
     */
    public static boolean hasWaitingBgSingleTask(Runnable runnable)
    {
        final InstanceProcessor processor = getBgInstanceProcessor(runnable);
        return processor.hasWaitingTasks(runnable);
    }

    /**
     * Return true if the specified callable is waiting to be processed<br>
     * in single scheme background processing (low priority).
     */
    public static boolean hasWaitingBgSingleTask(Callable<?> callable)
    {
        final InstanceProcessor processor = getBgInstanceProcessor(callable);
        return processor.hasWaitingTasks(callable);
    }

    /**
     * Return true if the specified runnable is waiting to be processed<br>
     * in single scheme background processing (normal priority).
     */
    public static boolean hasWaitingSingleTask(Runnable runnable)
    {
        final InstanceProcessor processor = getInstanceProcessor(runnable);
        return processor.hasWaitingTasks(runnable);
    }

    /**
     * Return true if the specified callable is waiting to be processed<br>
     * in single scheme background processing (normal priority).
     */
    public static boolean hasWaitingSingleTask(Callable<?> callable)
    {
        final InstanceProcessor processor = getInstanceProcessor(callable);
        return processor.hasWaitingTasks(callable);
    }

    /**
     * Return the number of active background tasks.
     */
    public static int getActiveBgTaskCount()
    {
        return bgProcessor.getActiveCount();
    }

    /**
     * Create a thread pool with the given name.<br>
     * The number of processing thread is automatically calculated given the number of core of the
     * system.
     * 
     * @see Processor#Processor(int, int, int)
     */
    public static ExecutorService createThreadPool(String name)
    {
        final Processor result = new Processor(SystemUtil.getNumberOfCPUs());

        result.setThreadName(name);

        return result;
    }

    /**
     * Same as {@link Thread#sleep(long)} except Exception is caught and ignored.
     */
    public static void sleep(long milli)
    {
        try
        {
            Thread.sleep(milli);
        }
        catch (InterruptedException e)
        {
            // have to interrupt the thread
            Thread.currentThread().interrupt();
        }
    }

    /**
     * Same as {@link Thread#sleep(long)} except Exception is caught and ignored.
     */
    public static void sleep(int milli)
    {
        try
        {
            Thread.sleep(milli);
        }
        catch (InterruptedException e)
        {
            // have to interrupt the thread
            Thread.currentThread().interrupt();
        }
    }
}