/*
 * 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.main;

import icy.action.ActionManager;
import icy.common.Version;
import icy.file.FileUtil;
import icy.file.Loader;
import icy.gui.dialog.ConfirmDialog;
import icy.gui.dialog.IdConfirmDialog;
import icy.gui.dialog.IdConfirmDialog.Confirmer;
import icy.gui.frame.ExitFrame;
import icy.gui.frame.IcyExternalFrame;
import icy.gui.frame.SplashScreenFrame;
import icy.gui.frame.progress.AnnounceFrame;
import icy.gui.frame.progress.ToolTipFrame;
import icy.gui.main.MainFrame;
import icy.gui.main.MainInterface;
import icy.gui.main.MainInterfaceBatch;
import icy.gui.main.MainInterfaceGui;
import icy.gui.system.NewVersionFrame;
import icy.gui.util.LookAndFeelUtil;
import icy.imagej.ImageJPatcher;
import icy.math.UnitUtil;
import icy.network.NetworkUtil;
import icy.plugin.PluginDescriptor;
import icy.plugin.PluginInstaller;
import icy.plugin.PluginLauncher;
import icy.plugin.PluginLoader;
import icy.plugin.PluginUpdater;
import icy.plugin.abstract_.Plugin;
import icy.preferences.ApplicationPreferences;
import icy.preferences.GeneralPreferences;
import icy.preferences.IcyPreferences;
import icy.preferences.PluginPreferences;
import icy.sequence.Sequence;
import icy.system.AppleUtil;
import icy.system.IcyExceptionHandler;
import icy.system.IcySecurityManager;
import icy.system.SingleInstanceCheck;
import icy.system.SystemUtil;
import icy.system.audit.Audit;
import icy.system.thread.ThreadUtil;
import icy.update.IcyUpdater;
import icy.util.StringUtil;
import icy.workspace.WorkspaceInstaller;
import icy.workspace.WorkspaceLoader;
import ij.ImageJ;

import java.awt.EventQueue;
import java.beans.PropertyVetoException;
import java.io.File;
import java.nio.channels.FileLock;
import java.util.ArrayList;
import java.util.List;

import javax.swing.JDesktopPane;
import javax.swing.JFrame;
import javax.swing.JInternalFrame;
import javax.swing.JOptionPane;
import javax.swing.WindowConstants;

import vtk.vtkNativeLibrary;
import vtk.vtkVersion;

/**
 * <h3>Icy - copyright 2017 Institut Pasteur</h3> An open community platform for bio image analysis<br>
 * <i>http://icy.bioimageanalysis.org</i><br>
 * <br>
 * Icy has been created by the Bio Image Analysis team at Institut Pasteur<br>
 * <i>https://research.pasteur.fr/fr/team/bioimage-analysis</i><br>
 * <br>
 * Icy is free and open source, it has been funded both by Institut Pasteur and the FBI consortium<br>
 * <i>https://france-bioimaging.org</i><br>
 * <br>
 * Source code is always provided in the main application package (in the icy.jar archive file) but can be also browsed
 * from the GitHub repository<br>
 * <i>https://github.com/Icy-imaging/Icy-Kernel</i><br>
 * <br>
 *
 * @author Stephane Dallongeville
 * @author Fabrice de Chaumont
 */
public class Icy
{
    public static final String LIB_PATH = "lib";
    public static final int EXIT_FORCE_DELAY = 3000;

    /**
     * Icy Version
     */
    public static Version version = new Version("1.9.1.0");

    /**
     * Main interface
     */
    private static MainInterface mainInterface = null;

    /**
     * Unique instance checker
     */
    static FileLock lock = null;

    /**
     * private splash for initial loading
     */
    static SplashScreenFrame splashScreen = null;

    /**
     * VTK library loaded flag
     */
    static boolean vtkLibraryLoaded = false;

    /**
     * ITK library loaded flag
     */
    static boolean itkLibraryLoaded = false;

    /**
     * No splash screen flag (default = false)
     */
    static boolean noSplash = false;
    /**
     * Exiting flag
     */
    static boolean exiting = false;

    /**
     * Startup parameters
     */
    static String[] args;
    static String[] pluginArgs;
    static String startupPluginName;
    static Plugin startupPlugin;
    static String startupImage;

    /**
     * internals
     */
    static ExitFrame exitFrame = null;
    static Thread terminer = null;

    /**
     * @param args
     *        Received from the command line.
     */
    public static void main(String[] args)
    {
        boolean headless = false;

        try
        {
            System.out.println("Initializing...");
            System.out.println();

            // handle arguments (must be the first thing to do)
            headless = handleAppArgs(args);

            // force headless if we have a headless system
            if (!headless && SystemUtil.isHeadLess())
                headless = true;

            // initialize preferences
            IcyPreferences.init();

            // check if Icy is already running
            lock = SingleInstanceCheck.lock("icy");
            if (lock == null)
            {
                // we always accept multi instance in headless mode
                if (!headless)
                {
                    // we need to use our custom ConfirmDialog as
                    // Icy.getMainInterface().isHeadless() will return false here
                    final Confirmer confirmer = new Confirmer("Confirmation",
                            "Icy is already running on this computer. Start anyway ?", JOptionPane.YES_NO_OPTION,
                            ApplicationPreferences.ID_SINGLE_INSTANCE);

                    ThreadUtil.invokeNow(confirmer);

                    if (!confirmer.getResult())
                    {
                        System.out.println("Exiting...");
                        // save preferences
                        IcyPreferences.save();
                        // and quit
                        System.exit(0);
                        return;
                    }
                }
            }

            if (!headless && !noSplash)
            {
                // prepare splashScreen (ok to create it here as we are not yet in substance laf)
                splashScreen = new SplashScreenFrame();

                // It's important to initialize AWT now (with InvokeNow(...) for instance) to avoid
                // the JVM deadlock bug (id: 5104239). It happen when the AWT thread is initialized
                // while others threads load some new library with ClassLoader.loadLibrary

                // display splash NOW (don't use ThreadUtil as headless is still false here)
                EventQueue.invokeAndWait(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        // display splash screen
                        splashScreen.setVisible(true);
                    }
                });
            }

            // set LOCI debug level (do it immediately as it can quickly show some log messages)
            loci.common.DebugTools.enableLogging("ERROR");

            // initialize network (need preferences)
            NetworkUtil.init();
            // load plugins classes (need preferences init)
            PluginLoader.reloadAsynch();
            WorkspaceLoader.reloadAsynch();

            // patches ImageJ classes
            ImageJPatcher.applyPatches();

            // build main interface
            if (headless)
                mainInterface = new MainInterfaceBatch();
            else
                mainInterface = new MainInterfaceGui();
        }
        catch (Throwable t)
        {
            // any error at this point is fatal
            fatalError(t, headless);
        }

        if (!headless)
        {
            // do it on AWT thread NOW as this is what we want first
            ThreadUtil.invokeNow(new Runnable()
            {
                @Override
                public void run()
                {
                    try
                    {
                        // init Look And Feel (need mainInterface instance)
                        LookAndFeelUtil.init();
                        // init need "mainInterface" variable to be initialized
                        getMainInterface().init();
                    }
                    catch (Throwable t)
                    {
                        // any error here is fatal
                        fatalError(t, false);
                    }
                }
            });
        }
        else
        {
            // simple main interface init
            getMainInterface().init();
        }

        // splash screen initialized --> hide it
        if (splashScreen != null)
        {
            // then do less important stuff later
            ThreadUtil.invokeLater(new Runnable()
            {
                @Override
                public void run()
                {
                    // we can now hide splash as we have interface
                    splashScreen.dispose();
                    splashScreen = null;
                }
            });
        }

        // show general informations
        System.out.println(SystemUtil.getJavaName() + " " + SystemUtil.getJavaVersion() + " ("
                + SystemUtil.getJavaArchDataModel() + " bit)");
        System.out.println("Running on " + SystemUtil.getOSName() + " " + SystemUtil.getOSVersion() + " ("
                + SystemUtil.getOSArch() + ")");
        System.out.println("Number of processors : " + SystemUtil.getNumberOfCPUs());
        System.out.println("System total memory : " + UnitUtil.getBytesString(SystemUtil.getTotalMemory()));
        System.out.println("System available memory : " + UnitUtil.getBytesString(SystemUtil.getFreeMemory()));
        System.out.println("Max java memory : " + UnitUtil.getBytesString(SystemUtil.getJavaMaxMemory()));
        if (headless)
            System.out.println("Headless mode.");
        System.out.println();

        // initialize OSX specific GUI stuff
        if (!headless && SystemUtil.isMac())
            AppleUtil.init();
        // initialize security
        IcySecurityManager.init();
        // initialize exception handler
        IcyExceptionHandler.init();
        // initialize action manager
        ActionManager.init();
        // prepare native library files (need preferences init)
        nativeLibrariesInit();

        // changed version ?
        if (!ApplicationPreferences.getVersion().equals(Icy.version))
        {
            // not headless ?
            if (!headless)
            {
                // display the new version information
                final String changeLog = Icy.getChangeLog();

                // show the new version frame
                if (!StringUtil.isEmpty(changeLog))
                {
                    ThreadUtil.invokeNow(new Runnable()
                    {
                        @Override
                        public void run()
                        {
                            new NewVersionFrame(Icy.getChangeLog());
                        }
                    });
                }
            }

            // force check update for the new version
            GeneralPreferences.setLastUpdateCheckTime(0);
        }

        final long currentTime = System.currentTimeMillis();
        final long halfDayInterval = 1000 * 60 * 60 * 12;

        // check only once per 12 hours slice
        if (currentTime > (GeneralPreferences.getLastUpdateCheckTime() + halfDayInterval))
        {
            // check for core update
            if (GeneralPreferences.getAutomaticUpdate())
                IcyUpdater.checkUpdate(true);
            // check for plugin update
            if (PluginPreferences.getAutomaticUpdate())
                PluginUpdater.checkUpdate(true);

            // update last update check time
            GeneralPreferences.setLastUpdateCheckTime(currentTime);
        }

        // update version info
        ApplicationPreferences.setVersion(Icy.version);
        // set LOCI debug level
        // loci.common.DebugTools.enableLogging("ERROR");
        // set OGL debug level
        // SystemUtil.setProperty("jogl.verbose", "TRUE");
        // SystemUtil.setProperty("jogl.debug", "TRUE");

        System.out.println();
        System.out.println("Icy Version " + version + " started !");
        System.out.println();

        checkParameters();

        // handle startup arguments
        if (startupImage != null)
            Icy.getMainInterface().addSequence(Loader.loadSequence(FileUtil.getGenericPath(startupImage), 0, false));

        // wait while updates are occurring before starting command line plugin...
        while (PluginUpdater.isCheckingForUpdate() || PluginInstaller.isProcessing()
                || WorkspaceInstaller.isProcessing())
            ThreadUtil.sleep(1);

        if (startupPluginName != null)
        {
            PluginLoader.waitWhileLoading();

            final PluginDescriptor plugin = PluginLoader.getPlugin(startupPluginName);

            if (plugin == null)
            {
                System.err.println("Could not launch plugin '" + startupPluginName + "': the plugin was not found.");
                System.err.println("Be sure you correctly wrote the complete class name and respected the case.");
                System.err.println("Ex: plugins.mydevid.analysis.MyPluginClass");
            }
            else
                startupPlugin = PluginLauncher.start(plugin);
        }

        // headless mode ? we can exit now...
        if (headless)
            exit(false);
    }

    private static boolean handleAppArgs(String[] args)
    {
        final List<String> pluginArgsList = new ArrayList<String>();

        startupImage = null;
        startupPluginName = null;
        startupPlugin = null;
        boolean execute = false;
        boolean headless = false;

        // save the base arguments
        Icy.args = args;

        for (String arg : args)
        {
            // store plugin arguments
            if (startupPluginName != null)
                pluginArgsList.add(arg);
            else if (execute)
                startupPluginName = arg;
            // special flag to disabled JCL (needed for development)
            else if (arg.equalsIgnoreCase("--disableJCL") || arg.equalsIgnoreCase("-dJCL"))
                PluginLoader.setJCLDisabled(true);
            // headless mode
            else if (arg.equalsIgnoreCase("--headless") || arg.equalsIgnoreCase("-hl"))
                headless = true;
            // disable splash-screen
            else if (arg.equalsIgnoreCase("--nosplash") || arg.equalsIgnoreCase("-ns"))
                noSplash = true;
            // execute plugin
            else if (arg.equalsIgnoreCase("--execute") || arg.equalsIgnoreCase("-x"))
                execute = true;
            // assume image name ?
            else
                startupImage = arg;
        }

        // save the plugin arguments
        Icy.pluginArgs = pluginArgsList.toArray(new String[pluginArgsList.size()]);

        return headless;
    }

    static void checkParameters()
    {
        // we are using a 32 bits JVM with a 64 bits OS --> warn the user
        if (SystemUtil.isWindows64() && SystemUtil.is32bits())
        {
            final String text = "You're using a 32 bits Java with a 64 bits OS, try to upgrade to 64 bits java for better performance !";

            System.out.println("Warning: " + text);

            if (!Icy.getMainInterface().isHeadLess())
                new ToolTipFrame("<html>" + text + "</html>", 15, "badJavaArchTip");
        }

        // we are using java 6 on OSX --> warn the user
        final double javaVersion = SystemUtil.getJavaVersionAsNumber();

        if ((javaVersion > 0) && (javaVersion < 1.7d) && SystemUtil.isMac())
        {
            final String text = "It looks like you're using a old version of Java (1.6)<br>"
                    + "It's recommended to use last JDK 8 for OSX for a better user experience.<br>See the <a href=\"http://icy.bioimageanalysis.org/faq#35\">FAQ</a> to get information about how to update java.";

            System.out.println("Warning: " + text);

            if (!Icy.getMainInterface().isHeadLess())
                new ToolTipFrame("<html>" + text + "</html>", 15, "outdatedJavaOSX");
        }

        // detect bad memory setting
        if ((ApplicationPreferences.getMaxMemoryMB() <= 128) && (ApplicationPreferences.getMaxMemoryMBLimit() > 256))
        {
            final String text = "Your maximum memory setting is low, you should increase it in Preferences.";

            System.out.println("Warning: " + text);

            if (!Icy.getMainInterface().isHeadLess())
                new ToolTipFrame("<html>" + text + "</html>", 15, "lowMemoryTip");
        }
        else if (ApplicationPreferences.getMaxMemoryMB() < (ApplicationPreferences.getDefaultMemoryMB() / 2))
        {
            if (!Icy.getMainInterface().isHeadLess())
            {
                new ToolTipFrame(
                        "<html><b>Tip:</b> you can increase your maximum memory in preferences setting.</html>", 15,
                        "maxMemoryTip");
            }
        }

        if (!Icy.getMainInterface().isHeadLess())
        {
            // welcome tip !
            final ToolTipFrame tooltip = new ToolTipFrame("<html>Access the main menu by clicking on top left icon<br>"
                    + "<img src=\"" + Icy.class.getResource("/res/image/help/main_menu.png").toString()
                    + "\" /></html>", 30, "mainMenuTip");
            tooltip.setSize(456, 240);
        }
    }

    static void fatalError(Throwable t, boolean headless)
    {
        // hide splashScreen if needed
        if ((splashScreen != null) && (splashScreen.isVisible()))
            splashScreen.dispose();

        // show error in console
        IcyExceptionHandler.showErrorMessage(t, true);
        // and show error in dialog if not headless
        if (!headless)
        {
            JOptionPane.showMessageDialog(null, IcyExceptionHandler.getErrorMessage(t, true), "Fatal error",
                    JOptionPane.ERROR_MESSAGE);
        }

        // exit with error code 1
        System.exit(1);
    }

    /**
     * Restart application with user confirmation
     */
    public static void confirmRestart()
    {
        confirmRestart(null);
    }

    /**
     * Restart application with user confirmation (custom message)
     */
    public static void confirmRestart(String message)
    {
        final String mess;

        if (StringUtil.isEmpty(message))
            mess = "Application need to be restarted so changes can take effet. Do it now ?";
        else
            mess = message;

        if (ConfirmDialog.confirm(mess))
            // restart application now
            exit(true);
    }

    /**
     * Show announcement to restart application
     */
    public static void announceRestart()
    {
        announceRestart(null);
    }

    /**
     * Show announcement to restart application (custom message)
     */
    public static void announceRestart(String message)
    {
        final String mess;

        if (StringUtil.isEmpty(message))
            mess = "Application need to be restarted so changes can take effet.";
        else
            mess = message;

        if (Icy.getMainInterface().isHeadLess())
        {
            // just display this message
            System.out.println(mess);
        }
        else
        {
            new AnnounceFrame(mess, "Restart Now", new Runnable()
            {
                @Override
                public void run()
                {
                    // restart application now
                    exit(true);
                }
            }, 20);
        }
    }

    /**
     * Returns <code>true</code> if application can exit.<br>
     * Shows a confirmation dialog if setting requires it or if it's unsafe to exit now.
     */
    public static boolean canExit(boolean showConfirm)
    {
        // we first check if externals listeners allow existing
        if (!getMainInterface().canExitExternal())
            return false;

        // headless mode --> allow exit
        if (Icy.getMainInterface().isHeadLess())
            return true;

        // PluginInstaller or WorkspaceInstaller not running
        final boolean safeExit = (!PluginInstaller.isProcessing()) && (!WorkspaceInstaller.isProcessing());

        // not safe, need confirmation
        if (!safeExit)
        {
            if (!ConfirmDialog.confirm("Quit the application",
                    "Some processes are not yet completed, are you sure you want to quit ?",
                    ConfirmDialog.YES_NO_CANCEL_OPTION))
                return false;

            return true;
        }
        else if (showConfirm && GeneralPreferences.getExitConfirm())
        {
            // we need user confirmation
            if (!IdConfirmDialog.confirm("Quit the application ?", GeneralPreferences.ID_CONFIRM_EXIT))
                return false;

            return true;
        }

        return true;
    }

    /**
     * Exit Icy, returns <code>true</code> if the operation should success.<br>
     * Note that the method is asynchronous so you still have a bit of time to execute some stuff before the application
     * actually exit.
     */
    public static boolean exit(final boolean restart)
    {
        // check we can exit application
        if (!canExit(!restart))
            return false;

        // already existing
        if (exiting && terminer.isAlive())
        {
            // set focus on exit frame
            if (exitFrame != null)
                exitFrame.requestFocus();
            // return true;
            return true;
        }

        // we don't want to be in EDT here and avoid BG runner
        // as we test for BG runner completion
        terminer = new Thread(new Runnable()
        {
            @Override
            public void run()
            {
                // mark the application as exiting
                exiting = true;

                System.out.println("Exiting...");

                final ImageJ ij = Icy.getMainInterface().getImageJ();

                // clean ImageJ exit
                if (ij != null)
                    ij.quit();

                // get main frame
                final MainFrame mainFrame = Icy.getMainInterface().getMainFrame();

                // disconnect from chat (not needed but preferred)
                if (mainFrame != null)
                    mainFrame.getChat().disconnect("Icy closed");

                // close all icyFrames (force wait completion)
                ThreadUtil.invokeNow(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        // for (IcyFrame frame : IcyFrame.getAllFrames())
                        // frame.close();
                        // close all JInternalFrames
                        final JDesktopPane desktopPane = Icy.getMainInterface().getDesktopPane();

                        if (desktopPane != null)
                        {
                            for (JInternalFrame frame : desktopPane.getAllFrames())
                            {
                                // if (frame instanceof IcyInternalFrame)
                                // {
                                // final IcyInternalFrame iFrame = (IcyInternalFrame) frame;
                                // if (!iFrame.isClosed())
                                // iFrame.close(true);
                                // if (iFrame.getDefaultCloseOperation() !=
                                // WindowConstants.DISPOSE_ON_CLOSE)
                                // iFrame.dispose();
                                // }
                                // else
                                // {
                                try
                                {
                                    frame.setClosed(true);
                                }
                                catch (PropertyVetoException e)
                                {
                                    // if (frame.getDefaultCloseOperation() !=
                                    // WindowConstants.DISPOSE_ON_CLOSE)
                                    frame.dispose();
                                }
                                // }
                            }
                        }

                        // then close all external frames except main frame
                        for (JFrame frame : Icy.getMainInterface().getExternalFrames())
                        {
                            if (frame != mainFrame)
                            {
                                if (frame instanceof IcyExternalFrame)
                                {
                                    final IcyExternalFrame iFrame = (IcyExternalFrame) frame;
                                    iFrame.close();
                                    if (iFrame.getDefaultCloseOperation() != WindowConstants.DISPOSE_ON_CLOSE)
                                        iFrame.dispose();
                                }
                                else
                                    frame.dispose();
                            }
                        }
                    }
                });

                // stop daemon plugin
                PluginLoader.stopDaemons();
                // shutdown background processor after frame close
                ThreadUtil.shutdown();

                // headless mode
                if (Icy.getMainInterface().isHeadLess())
                {
                    // final long start = System.currentTimeMillis();
                    // // wait 10s max for background processors completed theirs tasks
                    // while (!ThreadUtil.isShutdownAndTerminated() && ((System.currentTimeMillis()
                    // - start) < 10 * 1000))
                    // ThreadUtil.sleep(1);

                    // wait that background processors completed theirs tasks
                    while (!ThreadUtil.isShutdownAndTerminated())
                        ThreadUtil.sleep(1);
                }
                else
                {
                    // need to create the exit frame in EDT
                    ThreadUtil.invokeNow(new Runnable()
                    {
                        @Override
                        public void run()
                        {
                            // create and display the exit frame
                            exitFrame = new ExitFrame(EXIT_FORCE_DELAY);
                        }
                    });

                    // wait that background processors completed theirs tasks
                    while (!ThreadUtil.isShutdownAndTerminated() && !exitFrame.isForced())
                        ThreadUtil.sleep(1);

                    // need to dispose the exit frame in EDT (else we can have deadlock)
                    ThreadUtil.invokeNow(new Runnable()
                    {
                        @Override
                        public void run()
                        {
                            // can close the exit frame now
                            exitFrame.dispose();
                        }
                    });
                }

                // need to dispose the main frame in EDT (else we can have deadlock)
                ThreadUtil.invokeNow(new Runnable()
                {
                    @Override
                    public void run()
                    {
                        // finally close the main frame
                        if (mainFrame != null)
                            mainFrame.dispose();
                    }
                });

                // save preferences
                IcyPreferences.save();
                // save audit data
                Audit.save();

                // clean up native library files
                // unPrepareNativeLibraries();

                if (lock != null)
                    SingleInstanceCheck.release(lock);

                final boolean doUpdate = IcyUpdater.getWantUpdate();

                // launch updater if needed
                if (doUpdate || restart)
                    IcyUpdater.launchUpdater(doUpdate, restart);

                // good exit
                System.exit(0);
            }
        });

        terminer.setName("Icy Shutdown");
        terminer.start();

        return true;
    }

    /**
     * @param force
     * @deprecated use <code>exit(boolean)</code> instead
     */
    @Deprecated
    public static boolean exit(final boolean restart, boolean force)
    {
        return exit(restart);
    }

    /**
     * Return true is VTK library loaded.
     */
    public static boolean isVtkLibraryLoaded()
    {
        return vtkLibraryLoaded;
    }

    /**
     * Return true is VTK library loaded.
     */
    public static boolean isItkLibraryLoaded()
    {
        return itkLibraryLoaded;
    }

    /**
     * @deprecated Use {@link MainInterface#isHeadLess()} instead.
     */
    @Deprecated
    public static boolean isHeadLess()
    {
        return getMainInterface().isHeadLess();
    }

    /**
     * Return true is the application is currently exiting.
     */
    public static boolean isExiting()
    {
        return exiting;
    }

    /**
     * Return the main Icy interface.
     */
    public static MainInterface getMainInterface()
    {
        // batch mode
        if (mainInterface == null)
            mainInterface = new MainInterfaceBatch();

        return mainInterface;
    }

    /**
     * Returns the command line arguments
     */
    public static String[] getCommandLineArgs()
    {
        return args;
    }

    /**
     * Returns the plugin command line arguments
     */
    public static String[] getCommandLinePluginArgs()
    {
        return pluginArgs;
    }

    /**
     * Clear the plugin command line arguments.<br>
     * This method should be called after the launching plugin actually 'consumed' the startup
     * arguments.
     */
    public static void clearCommandLinePluginArgs()
    {
        pluginArgs = new String[0];
    }

    /**
     * Returns the startup plugin if any
     */
    public static Plugin getStartupPlugin()
    {
        return startupPlugin;
    }

    /**
     * Return content of the <code>CHANGELOG.txt</code> file
     */
    public static String getChangeLog()
    {
        if (FileUtil.exists("CHANGELOG.txt"))
            return new String(FileUtil.load("CHANGELOG.txt", false));

        return "";
    }

    /**
     * Return content of the <code>COPYING.txt</code> file
     */
    public static String getLicense()
    {
        if (FileUtil.exists("COPYING.txt"))
            return new String(FileUtil.load("COPYING.txt", false));

        return "";
    }

    /**
     * Return content of the <code>README.txt</code> file
     */
    public static String getReadMe()
    {
        if (FileUtil.exists("README.txt"))
            return new String(FileUtil.load("README.txt", false));

        return "";
    }

    /**
     * @deprecated Uses <code>Icy.getMainInterface().addSequence(Sequence)</code> instead.
     */
    @Deprecated
    public static void addSequence(final Sequence sequence)
    {
        Icy.getMainInterface().addSequence(sequence);
    }

    static void nativeLibrariesInit()
    {
        // build the local native library path
        final String libPath = LIB_PATH + FileUtil.separator + SystemUtil.getOSArchIdString();
        final File libFile = new File(libPath);

        // get all files in local native library path
        final File[] files = FileUtil.getFiles(libFile, null, true, true, false);
        final ArrayList<String> directories = new ArrayList<String>();

        // add base local native library path to user library paths
        directories.add(libFile.getAbsolutePath());
        // add base temporary native library path to user library paths
        directories.add(SystemUtil.getTempLibraryDirectory());

        for (File f : files)
        {
            if (f.isDirectory())
            {
                // add all directories to user library paths
                final String filePath = f.getAbsolutePath();
                if (!directories.contains(filePath))
                    directories.add(filePath);
            }
        }

        // add lib folder for unix system
        if (SystemUtil.isUnix())
        {
            directories.add("/lib");
            directories.add("/usr/lib");

            if (SystemUtil.is64bits())
            {
                directories.add("/lib64");
                directories.add("/lib/x86_64");
                directories.add("/lib/x86_64-linux-gnu");
                directories.add("/usr/lib64");
                directories.add("/usr/lib/x86_64");
                directories.add("/usr/lib/x86_64-linux-gnu");
            }
            else
            {
                directories.add("/lib/x86");
                directories.add("/lib/x86-linux-gnu");
                directories.add("/usr/lib/x86");
                directories.add("/usr/lib/x86-linux-gnu");
            }
        }

        if (!SystemUtil.addToJavaLibraryPath(directories.toArray(new String[directories.size()])))
            System.err.println("Native libraries may not load correctly.");

        // load native libraries
        loadVtkLibrary(libPath);
        // loadItkLibrary(libPath);

        // disable native lib support for JAI as we don't provide them (for the moment)
        SystemUtil.setProperty("com.sun.media.jai.disableMediaLib", "true");
    }

    private static void loadVtkLibrary(String libPath)
    {
        final String vtkLibPath = libPath + FileUtil.separator + "vtk";

        // we load it directly from inner lib path if possible
        System.setProperty("vtk.lib.dir", vtkLibPath);

        vtkLibraryLoaded = false;
        try
        {
            // if (SystemUtil.isUnix())
            // {
            // vtkNativeLibrary.LoadAllNativeLibraries();
            //
            // // assume VTK loaded if at least 1 native library is loaded
            // for (vtkNativeLibrary lib : vtkNativeLibrary.values())
            // if (lib.IsLoaded())
            // vtkLibraryLoaded = true;
            // }
            // else
            {
                loadLibrary(vtkLibPath, "vtkalglib");
                loadLibrary(vtkLibPath, "vtkexpat");
                loadLibrary(vtkLibPath, "vtkDICOMParser");
                loadLibrary(vtkLibPath, "vtkjpeg");
                loadLibrary(vtkLibPath, "vtkjsoncpp");
                loadLibrary(vtkLibPath, "vtkzlib");
                loadLibrary(vtkLibPath, "vtkoggtheora", false);
                loadLibrary(vtkLibPath, "vtkverdict");
                loadLibrary(vtkLibPath, "vtkpng");

                loadLibrary(vtkLibPath, "vtkgl2ps", false); //

                loadLibrary(vtkLibPath, "vtktiff");
                loadLibrary(vtkLibPath, "vtklibxml2");
                loadLibrary(vtkLibPath, "vtkproj4");
                loadLibrary(vtkLibPath, "vtksys");
                loadLibrary(vtkLibPath, "vtkfreetype");
                loadLibrary(vtkLibPath, "vtkmetaio");
                loadLibrary(vtkLibPath, "vtkftgl", false);

                loadLibrary(vtkLibPath, "vtkftgl2", false); //

                loadLibrary(vtkLibPath, "vtkglew", false);
                loadLibrary(vtkLibPath, "vtkCommonCore");
                loadLibrary(vtkLibPath, "vtkWrappingJava");
                loadLibrary(vtkLibPath, "vtkCommonSystem");
                loadLibrary(vtkLibPath, "vtkCommonMath");
                loadLibrary(vtkLibPath, "vtkCommonMisc");
                loadLibrary(vtkLibPath, "vtkCommonTransforms");
                if (SystemUtil.isMac())
                {
                    loadLibrary(vtkLibPath, "vtkhdf5.8.0.2", false);
                    loadLibrary(vtkLibPath, "vtkhdf5_hl.8.0.2", false);
                }
                else
                {
                    loadLibrary(vtkLibPath, "vtkhdf5", false);
                    loadLibrary(vtkLibPath, "vtkhdf5_hl", false);
                }
                loadLibrary(vtkLibPath, "vtkNetCDF");
                loadLibrary(vtkLibPath, "vtkNetCDF_cxx");
                loadLibrary(vtkLibPath, "vtkCommonDataModel");
                loadLibrary(vtkLibPath, "vtkCommonColor");
                loadLibrary(vtkLibPath, "vtkCommonComputationalGeometry");
                loadLibrary(vtkLibPath, "vtkCommonExecutionModel");
                loadLibrary(vtkLibPath, "vtkexoIIc");
                loadLibrary(vtkLibPath, "vtkFiltersVerdict");
                loadLibrary(vtkLibPath, "vtkFiltersProgrammable");
                loadLibrary(vtkLibPath, "vtkImagingMath");
                loadLibrary(vtkLibPath, "vtkIOCore");
                loadLibrary(vtkLibPath, "vtkIOEnSight");
                loadLibrary(vtkLibPath, "vtkIOVideo");
                loadLibrary(vtkLibPath, "vtkIOLegacy");
                loadLibrary(vtkLibPath, "vtkIONetCDF");
                loadLibrary(vtkLibPath, "vtkIOXMLParser");
                loadLibrary(vtkLibPath, "vtkIOXML");
                loadLibrary(vtkLibPath, "vtkIOImage");
                loadLibrary(vtkLibPath, "vtksqlite", false);
                loadLibrary(vtkLibPath, "vtkIOSQL");
                loadLibrary(vtkLibPath, "vtkIOMovie");
                loadLibrary(vtkLibPath, "vtkParallelCore");
                loadLibrary(vtkLibPath, "vtkImagingCore");
                loadLibrary(vtkLibPath, "vtkFiltersCore");
                loadLibrary(vtkLibPath, "vtkImagingColor");
                loadLibrary(vtkLibPath, "vtkImagingFourier");
                loadLibrary(vtkLibPath, "vtkImagingSources");
                loadLibrary(vtkLibPath, "vtkImagingHybrid");
                loadLibrary(vtkLibPath, "vtkImagingStatistics");
                loadLibrary(vtkLibPath, "vtkIOGeometry");
                loadLibrary(vtkLibPath, "vtkIOPLY");
                loadLibrary(vtkLibPath, "vtkFiltersSelection");
                loadLibrary(vtkLibPath, "vtkImagingGeneral");
                loadLibrary(vtkLibPath, "vtkImagingStencil");
                loadLibrary(vtkLibPath, "vtkImagingMorphological");
                loadLibrary(vtkLibPath, "vtkFiltersGeometry");
                loadLibrary(vtkLibPath, "vtkFiltersStatistics");
                loadLibrary(vtkLibPath, "vtkFiltersImaging");
                loadLibrary(vtkLibPath, "vtkIOLSDyna");
                loadLibrary(vtkLibPath, "vtkFiltersGeneral");
                loadLibrary(vtkLibPath, "vtkFiltersHyperTree");
                loadLibrary(vtkLibPath, "vtkFiltersSMP");
                loadLibrary(vtkLibPath, "vtkFiltersTexture");
                loadLibrary(vtkLibPath, "vtkFiltersAMR");
                loadLibrary(vtkLibPath, "vtkFiltersExtraction");
                loadLibrary(vtkLibPath, "vtkFiltersSources");
                loadLibrary(vtkLibPath, "vtkIOExodus");
                loadLibrary(vtkLibPath, "vtkFiltersGeneric");
                loadLibrary(vtkLibPath, "vtkFiltersModeling");
                loadLibrary(vtkLibPath, "vtkIOAMR");
                loadLibrary(vtkLibPath, "vtkFiltersFlowPaths");
                loadLibrary(vtkLibPath, "vtkInfovisCore");
                loadLibrary(vtkLibPath, "vtkIOInfovis");
                loadLibrary(vtkLibPath, "vtkInfovisLayout");
                loadLibrary(vtkLibPath, "vtkRenderingCore");
                loadLibrary(vtkLibPath, "vtkRenderingLOD");
                loadLibrary(vtkLibPath, "vtkRenderingImage");
                loadLibrary(vtkLibPath, "vtkRenderingFreeType");
                loadLibrary(vtkLibPath, "vtkDomainsChemistry");

                loadLibrary(vtkLibPath, "vtkDomainsChemistryOpenGL2", false);

                loadLibrary(vtkLibPath, "vtkInteractionStyle");
                loadLibrary(vtkLibPath, "vtkIOImport");
                loadLibrary(vtkLibPath, "vtkRenderingAnnotation");
                loadLibrary(vtkLibPath, "vtkRenderingLabel");
                loadLibrary(vtkLibPath, "vtkFiltersHybrid");
                loadLibrary(vtkLibPath, "vtkFiltersParallel");
                loadLibrary(vtkLibPath, "vtkFiltersParallelImaging");
                loadLibrary(vtkLibPath, "vtkIOMINC");
                loadLibrary(vtkLibPath, "vtkIOParallel");
                loadLibrary(vtkLibPath, "vtkRenderingVolume");
                loadLibrary(vtkLibPath, "vtkRenderingVolumeAMR", false);
                loadLibrary(vtkLibPath, "vtkInteractionWidgets");
                loadLibrary(vtkLibPath, "vtkInteractionImage");
                loadLibrary(vtkLibPath, "vtkViewsCore");

                loadLibrary(vtkLibPath, "vtkRenderingOpenGL", false); //

                loadLibrary(vtkLibPath, "vtkRenderingOpenGL2", false);

                loadLibrary(vtkLibPath, "vtkRenderingLIC", false); //
                loadLibrary(vtkLibPath, "vtkRenderingVolumeOpenGL", false); //

                loadLibrary(vtkLibPath, "vtkRenderingVolumeOpenGL2", false);

                loadLibrary(vtkLibPath, "vtkRenderingContext2D");
                loadLibrary(vtkLibPath, "vtkRenderingContextOpenGL", false); //

                loadLibrary(vtkLibPath, "vtkRenderingContextOpenGL2", false);

                loadLibrary(vtkLibPath, "vtkRenderingGL2PS", false); //

                loadLibrary(vtkLibPath, "vtkViewsContext2D");
                loadLibrary(vtkLibPath, "vtkGeovisCore");
                loadLibrary(vtkLibPath, "vtkIOExport");
                loadLibrary(vtkLibPath, "vtkChartsCore");
                loadLibrary(vtkLibPath, "vtkViewsInfovis");
                loadLibrary(vtkLibPath, "vtkViewsGeovis", false);

                // JAVA wrapper
                loadLibrary(vtkLibPath, "vtkCommonCoreJava");
                loadLibrary(vtkLibPath, "vtkCommonSystemJava");
                loadLibrary(vtkLibPath, "vtkCommonMathJava");
                loadLibrary(vtkLibPath, "vtkCommonMiscJava");
                loadLibrary(vtkLibPath, "vtkCommonTransformsJava");
                loadLibrary(vtkLibPath, "vtkCommonDataModelJava");
                loadLibrary(vtkLibPath, "vtkCommonColorJava");
                loadLibrary(vtkLibPath, "vtkCommonComputationalGeometryJava");
                loadLibrary(vtkLibPath, "vtkCommonExecutionModelJava");
                loadLibrary(vtkLibPath, "vtkFiltersVerdictJava");
                loadLibrary(vtkLibPath, "vtkFiltersProgrammableJava");
                loadLibrary(vtkLibPath, "vtkImagingMathJava");
                loadLibrary(vtkLibPath, "vtkIOCoreJava");
                loadLibrary(vtkLibPath, "vtkIOEnSightJava");
                loadLibrary(vtkLibPath, "vtkIOVideoJava");
                loadLibrary(vtkLibPath, "vtkIOLegacyJava");
                loadLibrary(vtkLibPath, "vtkIONetCDFJava");
                loadLibrary(vtkLibPath, "vtkIOXMLParserJava");
                loadLibrary(vtkLibPath, "vtkIOXMLJava");
                loadLibrary(vtkLibPath, "vtkIOImageJava");
                loadLibrary(vtkLibPath, "vtkIOSQLJava");
                loadLibrary(vtkLibPath, "vtkIOMovieJava");
                loadLibrary(vtkLibPath, "vtkParallelCoreJava");
                loadLibrary(vtkLibPath, "vtkImagingCoreJava");
                loadLibrary(vtkLibPath, "vtkFiltersCoreJava");
                loadLibrary(vtkLibPath, "vtkImagingColorJava");
                loadLibrary(vtkLibPath, "vtkImagingFourierJava");
                loadLibrary(vtkLibPath, "vtkImagingSourcesJava");
                loadLibrary(vtkLibPath, "vtkImagingHybridJava");
                loadLibrary(vtkLibPath, "vtkImagingStatisticsJava");
                loadLibrary(vtkLibPath, "vtkIOGeometryJava");
                loadLibrary(vtkLibPath, "vtkIOPLYJava");
                loadLibrary(vtkLibPath, "vtkFiltersSelectionJava");
                loadLibrary(vtkLibPath, "vtkImagingGeneralJava");
                loadLibrary(vtkLibPath, "vtkImagingStencilJava");
                loadLibrary(vtkLibPath, "vtkImagingMorphologicalJava");
                loadLibrary(vtkLibPath, "vtkFiltersGeometryJava");
                loadLibrary(vtkLibPath, "vtkFiltersStatisticsJava");
                loadLibrary(vtkLibPath, "vtkFiltersImagingJava");
                loadLibrary(vtkLibPath, "vtkIOLSDynaJava");
                loadLibrary(vtkLibPath, "vtkFiltersGeneralJava");
                loadLibrary(vtkLibPath, "vtkFiltersHyperTreeJava");
                loadLibrary(vtkLibPath, "vtkFiltersSMPJava");
                loadLibrary(vtkLibPath, "vtkFiltersTextureJava");
                loadLibrary(vtkLibPath, "vtkFiltersAMRJava");
                loadLibrary(vtkLibPath, "vtkFiltersExtractionJava");
                loadLibrary(vtkLibPath, "vtkFiltersSourcesJava");
                loadLibrary(vtkLibPath, "vtkIOExodusJava");
                loadLibrary(vtkLibPath, "vtkFiltersGenericJava");
                loadLibrary(vtkLibPath, "vtkFiltersModelingJava");
                loadLibrary(vtkLibPath, "vtkIOAMRJava");
                loadLibrary(vtkLibPath, "vtkFiltersFlowPathsJava");
                loadLibrary(vtkLibPath, "vtkInfovisCoreJava");
                loadLibrary(vtkLibPath, "vtkIOInfovisJava");
                loadLibrary(vtkLibPath, "vtkInfovisLayoutJava");
                loadLibrary(vtkLibPath, "vtkRenderingCoreJava");
                loadLibrary(vtkLibPath, "vtkRenderingLODJava");
                loadLibrary(vtkLibPath, "vtkRenderingImageJava");
                loadLibrary(vtkLibPath, "vtkRenderingFreeTypeJava");
                loadLibrary(vtkLibPath, "vtkDomainsChemistryJava");

                loadLibrary(vtkLibPath, "vtkDomainsChemistryOpenGL2Java", false);

                loadLibrary(vtkLibPath, "vtkInteractionStyleJava");
                loadLibrary(vtkLibPath, "vtkIOImportJava");
                loadLibrary(vtkLibPath, "vtkRenderingAnnotationJava");
                loadLibrary(vtkLibPath, "vtkRenderingLabelJava");
                loadLibrary(vtkLibPath, "vtkFiltersHybridJava");
                loadLibrary(vtkLibPath, "vtkFiltersParallelJava");
                loadLibrary(vtkLibPath, "vtkFiltersParallelImagingJava");
                loadLibrary(vtkLibPath, "vtkIOMINCJava");
                loadLibrary(vtkLibPath, "vtkIOParallelJava");
                loadLibrary(vtkLibPath, "vtkRenderingVolumeJava");
                loadLibrary(vtkLibPath, "vtkRenderingVolumeAMRJava", false);
                loadLibrary(vtkLibPath, "vtkInteractionWidgetsJava");
                loadLibrary(vtkLibPath, "vtkInteractionImageJava");
                loadLibrary(vtkLibPath, "vtkViewsCoreJava");
                loadLibrary(vtkLibPath, "vtkRenderingOpenGLJava", false); //

                loadLibrary(vtkLibPath, "vtkRenderingOpenGL2Java", false);

                loadLibrary(vtkLibPath, "vtkRenderingLICJava", false); //
                loadLibrary(vtkLibPath, "vtkRenderingVolumeOpenGLJava", false); //

                loadLibrary(vtkLibPath, "vtkRenderingVolumeOpenGL2Java", false);

                loadLibrary(vtkLibPath, "vtkRenderingContext2DJava");

                loadLibrary(vtkLibPath, "vtkRenderingContextOpenGLJava", false); //

                loadLibrary(vtkLibPath, "vtkRenderingContextOpenGL2Java", false);

                loadLibrary(vtkLibPath, "vtkRenderingGL2PSJava", false); //

                loadLibrary(vtkLibPath, "vtkViewsContext2DJava");
                loadLibrary(vtkLibPath, "vtkGeovisCoreJava");
                loadLibrary(vtkLibPath, "vtkIOExportJava");
                loadLibrary(vtkLibPath, "vtkChartsCoreJava");
                loadLibrary(vtkLibPath, "vtkViewsInfovisJava");
                loadLibrary(vtkLibPath, "vtkViewsGeovisJava", false);

                // VTK library successfully loaded
                vtkLibraryLoaded = true;
            }

            // redirect vtk error log to file
            vtkNativeLibrary.DisableOutputWindow(new File("vtk.log"));
        }
        catch (Throwable e)
        {
            IcyExceptionHandler.showErrorMessage(e, false, false);
        }

        if (vtkLibraryLoaded)
        {
            final String vv = new vtkVersion().GetVTKVersion();

            System.out.println("VTK " + vv + " library successfully loaded...");

            // final vtkJavaGarbageCollector vtkJavaGarbageCollector =
            // vtkObjectBase.JAVA_OBJECT_MANAGER
            // .getAutoGarbageCollector();
            //
            // set auto garbage collection for VTK (every 20 seconds should be enough)
            // probably not a good idea...
            // vtkJavaGarbageCollector.SetScheduleTime(5, TimeUnit.SECONDS);
            // vtkJavaGarbageCollector.SetAutoGarbageCollection(true);
        }
        else
        {
            System.out.println("Cannot load VTK library...");

            if (SystemUtil.isMac())
            {
                final String osVer = SystemUtil.getOSVersion();

                if (osVer.startsWith("10.6") || osVer.startsWith("10.5"))
                    System.out.println("VTK 6.3 is not supported on OSX " + osVer
                            + ", version 10.7 or above is required.");
            }
        }
    }

    private static void loadItkLibrary(String osDir)
    {
        final String itkLibDir = osDir + FileUtil.separator + "itk";

        try
        {
            loadLibrary(itkLibDir, "SimpleITKJava", true);

            System.out.println("SimpleITK library successfully loaded...");
            itkLibraryLoaded = true;
        }
        catch (Throwable e)
        {
            System.out.println("Cannot load SimpleITK library...");
        }
    }

    private static void loadLibrary(String dir, String name, boolean mandatory, boolean showLog)
    {
        if (mandatory)
            SystemUtil.loadLibrary(dir, name);
        else
        {
            try
            {
                SystemUtil.loadLibrary(dir, name);
            }
            catch (Throwable e)
            {
                if (showLog)
                    System.out.println("cannot load " + name + ", skipping...");
            }
        }
    }

    private static void loadLibrary(String dir, String name, boolean mandatory)
    {
        loadLibrary(dir, name, mandatory, false);
    }

    private static void loadLibrary(String dir, String name)
    {
        loadLibrary(dir, name, true, false);
    }

    static void nativeLibrariesShutdown()
    {
        // build the native local library path
        final String path = LIB_PATH + FileUtil.separator + SystemUtil.getOSArchIdString();
        // get file list (we don't want hidden files if any)
        File[] libraryFiles = FileUtil.getFiles(new File(path), null, true, false, false);

        // remove previous copied files
        for (File libraryFile : libraryFiles)
        {
            // get file in root directory
            final File file = new File(libraryFile.getName());
            // invoke delete on exit if the file exists
            if (file.exists())
                file.deleteOnExit();
        }

        // get file list from temporary native library path
        libraryFiles = FileUtil.getFiles(new File(SystemUtil.getTempLibraryDirectory()), null, true, false, false);

        // remove previous copied files
        for (File libraryFile : libraryFiles)
        {
            // delete file
            if (!FileUtil.delete(libraryFile, false))
                libraryFile.deleteOnExit();
        }
    }
}