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

import icy.file.FileUtil;
import icy.gui.dialog.ConfirmDialog;
import icy.gui.frame.progress.FailedAnnounceFrame;
import icy.gui.frame.progress.ProgressFrame;
import icy.gui.frame.progress.SuccessfullAnnounceFrame;
import icy.main.Icy;
import icy.network.NetworkUtil;
import icy.plugin.PluginDescriptor;
import icy.plugin.PluginInstaller;
import icy.plugin.PluginLoader;
import icy.preferences.WorkspaceLocalPreferences;
import icy.system.thread.ThreadUtil;
import icy.util.StringUtil;
import icy.workspace.Workspace.TaskDefinition.BandDefinition.ItemDefinition;

import java.util.ArrayList;
import java.util.EventListener;
import java.util.List;

import javax.swing.event.EventListenerList;

/**
 * @author Stephane
 */
public class WorkspaceInstaller implements Runnable
{
    public static interface WorkspaceInstallerListener extends EventListener
    {
        public void workspaceInstalled(WorkspaceInstallerEvent e);

        public void workspaceRemoved(WorkspaceInstallerEvent e);
    }

    public static class WorkspaceInstallerEvent
    {
        private final Workspace workspace;
        private final boolean successed;

        public WorkspaceInstallerEvent(Workspace workspace, boolean successed)
        {
            super();

            this.workspace = workspace;
            this.successed = successed;
        }

        /**
         * @return the workspace
         */
        public Workspace getWorkspace()
        {
            return workspace;
        }

        /**
         * @return the success
         */
        public boolean getSuccessed()
        {
            return successed;
        }
    }

    private static class WorkspaceInstallInfo
    {
        final Workspace workspace;
        final boolean showConfirm;

        public WorkspaceInstallInfo(Workspace workspace, boolean showConfirm)
        {
            super();

            this.workspace = workspace;
            this.showConfirm = showConfirm;
        }
    }

    /**
     * static class
     */
    private static final WorkspaceInstaller instance = new WorkspaceInstaller();

    /**
     * workspace to install FIFO
     */
    private final List<WorkspaceInstallInfo> installFIFO;
    /**
     * workspace to delete FIFO
     */
    private final List<WorkspaceInstallInfo> removeFIFO;

    /**
     * listeners
     */
    private final EventListenerList listeners;

    /**
     * internals
     */
    private Workspace installingWorkspace;
    private Workspace desinstallingWorkspace;

    private boolean installing;
    private boolean deinstalling;

    /**
     * static class
     */
    private WorkspaceInstaller()
    {
        super();

        installFIFO = new ArrayList<WorkspaceInstallInfo>();
        removeFIFO = new ArrayList<WorkspaceInstallInfo>();

        listeners = new EventListenerList();

        installingWorkspace = null;
        desinstallingWorkspace = null;

        installing = false;
        deinstalling = false;

        // launch installer thread
        new Thread(this, "Workspace installer").start();
    }

    /**
     * install a workspace (asynchronous)
     */
    public static void install(Workspace workspace, boolean showConfirm)
    {
        if (workspace != null)
        {
            if (!NetworkUtil.hasInternetAccess())
            {
                final String text = "Cannot install '" + workspace.getName()
                        + "' workspace : you are not connected to Internet.";

                if (Icy.getMainInterface().isHeadLess())
                    System.err.println(text);
                else
                    new FailedAnnounceFrame(text, 10);

                return;
            }

            synchronized (instance.installFIFO)
            {
                instance.installFIFO.add(new WorkspaceInstallInfo(workspace, showConfirm));
            }
        }
    }

    /**
     * return true if WorkspaceInstaller is processing
     */
    public static boolean isProcessing()
    {
        return isInstalling() || isDesinstalling();
    }

    /**
     * return true if WorkspaceInstaller is installing workspace(s)
     */
    public static boolean isInstalling()
    {
        return (!instance.installFIFO.isEmpty()) || instance.installing;
    }

    /**
     * return true if 'workspace' is in the install FIFO
     */
    public static boolean isWaitingForInstall(Workspace workspace)
    {
        final String workspaceName = workspace.getName();

        synchronized (instance.installFIFO)
        {
            for (WorkspaceInstallInfo info : instance.installFIFO)
                if (workspaceName.equals(info.workspace.getName()))
                    return true;
        }

        return false;
    }

    /**
     * return the current installed workspace (null if none)
     */
    public static Workspace getCurrentInstallingWorkspace()
    {
        return instance.installingWorkspace;
    }

    /**
     * return true if specified workspace is currently being installed or will be installed
     */
    public static boolean isInstallingWorkspace(Workspace workspace)
    {
        if (instance.installingWorkspace != null)
        {
            if (instance.installingWorkspace.getName().equals(workspace.getName()))
                return true;
        }

        return isWaitingForInstall(workspace);
    }

    /**
     * uninstall a workspace (asynchronous)
     */
    public static void desinstall(Workspace workspace, boolean showConfirm)
    {
        if (workspace != null)
        {
            synchronized (instance.removeFIFO)
            {
                instance.removeFIFO.add(new WorkspaceInstallInfo(workspace, showConfirm));
            }
        }
    }

    /**
     * return true if WorkspaceInstaller is desinstalling workspace(s)
     */
    public static boolean isDesinstalling()
    {
        return (!instance.removeFIFO.isEmpty()) || instance.deinstalling;
    }

    /**
     * return true if 'workspace' is in the remove FIFO
     */
    public static boolean isWaitingForDesinstall(Workspace workspace)
    {
        final String workspaceName = workspace.getName();

        synchronized (instance.removeFIFO)
        {
            for (WorkspaceInstallInfo info : instance.removeFIFO)
                if (workspaceName.equals(info.workspace.getName()))
                    return true;
        }

        return false;
    }

    /**
     * return true if specified workspace is currently being desinstalled or will be desinstalled
     */
    public static boolean isDesinstallingWorkspace(Workspace workspace)
    {
        if (instance.desinstallingWorkspace != null)
        {
            if (instance.desinstallingWorkspace.getName().equals(workspace.getName()))
                return true;
        }

        return isWaitingForDesinstall(workspace);
    }

    @Override
    public void run()
    {
        while (!Thread.interrupted())
        {
            boolean empty;
            boolean result;
            WorkspaceInstallInfo installInfo = null;

            // process installations
            empty = installFIFO.isEmpty();

            if (!empty)
            {
                installing = true;
                try
                {
                    while (!empty)
                    {
                        synchronized (installFIFO)
                        {
                            installInfo = installFIFO.remove(0);
                            empty = installFIFO.isEmpty();
                        }

                        // don't install if the workspace is already installed
                        if (!WorkspaceLoader.isLoaded(installInfo.workspace))
                        {
                            result = installInternal(installInfo);
                            // process on workspace installation
                            installed(installInfo.workspace, result);
                        }

                        synchronized (installFIFO)
                        {
                        }
                    }
                }
                finally
                {
                    installing = false;
                }
            }

            // process deletions
            empty = removeFIFO.isEmpty();

            if (!empty)
            {
                deinstalling = true;
                try
                {
                    while (!empty)
                    {
                        synchronized (removeFIFO)
                        {
                            installInfo = removeFIFO.remove(0);
                            empty = removeFIFO.isEmpty();
                        }

                        result = desinstallInternal(installInfo);
                        // process on workspace deletion
                        desinstalled(installInfo.workspace, result);
                    }
                }
                finally
                {
                    deinstalling = false;
                }
            }

            ThreadUtil.sleep(200);
        }
    }

    private boolean deleteWorkspace(Workspace workspace)
    {
        if (!FileUtil.delete(workspace.getLocalFilename(), false))
            System.err.println("deleteWorkspace : Can't delete " + workspace.getLocalFilename());

        // reload workspace list
        WorkspaceLoader.reload();

        return true;
    }

    /**
     * Return local plugins of specified workspace
     */
    private ArrayList<PluginDescriptor> getLocalPlugins(Workspace workspace)
    {
        final ArrayList<PluginDescriptor> result = new ArrayList<PluginDescriptor>();

        for (ItemDefinition item : workspace.getAllItems())
        {
            if (!item.isSeparator())
            {
                final PluginDescriptor plugin = PluginLoader.getPlugin(item.getClassName());

                // plugin found
                if (plugin != null)
                {
                    // add all its dependences
                    PluginInstaller.getLocalDependenciesOf(result, plugin);
                    // the add the plugin itselft
                    PluginDescriptor.addToList(result, plugin);
                }
            }
        }

        return result;
    }

    /**
     * Return independent plugins of workspace (so they can be deleted)
     */
    private ArrayList<PluginDescriptor> getIndependentPlugins(Workspace workspace)
    {
        // get plugins of specified workspace
        final ArrayList<PluginDescriptor> result = getLocalPlugins(workspace);
        final ArrayList<PluginDescriptor> others = new ArrayList<PluginDescriptor>();

        // get plugins of all others workspaces
        for (Workspace ws : WorkspaceLoader.getWorkspaces())
            // we can use name as id here
            if (!StringUtil.equals(ws.getName(), workspace.getName()))
                others.addAll(getLocalPlugins(ws));

        for (PluginDescriptor plugin : others)
        {
            for (int i = result.size() - 1; i >= 0; i--)
            {
                final PluginDescriptor depPlug = result.get(i);

                // have a dependence, remove from list...
                if (plugin.equals(depPlug) || plugin.requires(depPlug))
                    result.remove(i);
            }
        }

        return result;
    }

    private boolean installInternal(WorkspaceInstallInfo installInfo)
    {
        final Workspace workspace = installInfo.workspace;
        final boolean showConfirm = installInfo.showConfirm;

        // installation start
        installingWorkspace = workspace;
        try
        {
            ProgressFrame taskFrame = null;
            int result = 0;
            final String workspaceName = workspace.getName();

            if (showConfirm && !Icy.getMainInterface().isHeadLess())
                taskFrame = new ProgressFrame("installing workspace '" + workspaceName + "'...");
            try
            {
                // install workspace (actually install dependent plugins)
                result = workspace.install(taskFrame);

                if (result > 0)
                {
                    if (taskFrame != null)
                        taskFrame.setMessage("saving workspace '" + workspaceName + "'...");

                    // save workspace locally
                    workspace.save();

                    if (taskFrame != null)
                        taskFrame.setMessage("reloading workspaces list...");

                    // reload workspace list
                    WorkspaceLoader.reload();
                }
            }
            finally
            {
                if (taskFrame != null)
                    taskFrame.close();
            }

            final String resMess = "Workspace '" + workspaceName + "' installation";

            if (showConfirm && !Icy.getMainInterface().isHeadLess())
            {
                switch (result)
                {
                    default:
                        new FailedAnnounceFrame(resMess + " failed !");
                        break;

                    case 1:
                        new SuccessfullAnnounceFrame(resMess + " succeed !", 10);
                        break;

                    case 2:
                        new SuccessfullAnnounceFrame(resMess + " succeed but some plugins cannot be installed.", 10);
                        break;
                }
            }
            else
            {
                switch (result)
                {
                    default:
                        System.err.println(resMess + " failed !");
                        break;

                    case 1:
                        System.out.println(resMess + " succeed !");
                        break;

                    case 2:
                        System.out.println(resMess + " partially succeed (some plugins cannot be installed) !");
                        break;
                }
            }

            return result > 0;
        }
        finally
        {
            // installation end
            installingWorkspace = null;
        }
    }

    private boolean desinstallInternal(WorkspaceInstallInfo installInfo)
    {
        final Workspace workspace = installInfo.workspace;
        final boolean showConfirm = installInfo.showConfirm;

        // desinstall start
        desinstallingWorkspace = workspace;
        try
        {
            final ArrayList<PluginDescriptor> independentPlugins = new ArrayList<PluginDescriptor>();
            final String workspaceDesc = workspace.getName();

            final boolean deletePlugin;
            final boolean result;
            ProgressFrame taskFrame = null;

            if (showConfirm)
            {
                String message = "<html>Do you want to also remove the associated plugins ?</html>";

                deletePlugin = ConfirmDialog.confirm(message);
            }
            else
                deletePlugin = true;

            if (deletePlugin)
            {
                if (showConfirm && !Icy.getMainInterface().isHeadLess())
                    taskFrame = new ProgressFrame("checking plugins dependences...");
                try
                {
                    independentPlugins.addAll(getIndependentPlugins(workspace));
                }
                finally
                {
                    if (taskFrame != null)
                        taskFrame.close();
                }
            }

            if (showConfirm)
                taskFrame = new ProgressFrame("removing workspace '" + workspaceDesc + "'...");
            try
            {
                // remove workspace independent plugins
                for (PluginDescriptor plugin : independentPlugins)
                    if (plugin.isInstalled())
                        PluginInstaller.desinstall(plugin, false, false);

                // wait for plugins desintallation
                PluginInstaller.waitDesinstall();

                // delete workspace
                result = deleteWorkspace(workspace);
            }
            finally
            {
                if (taskFrame != null)
                    taskFrame.close();
            }

            final String resMess = "Workspace '" + workspaceDesc + "' remove";

            if (showConfirm && !Icy.getMainInterface().isHeadLess())
            {
                if (result)
                    new SuccessfullAnnounceFrame(resMess + " succeed !", 10);
                else
                    new FailedAnnounceFrame(resMess + " failed !");
            }
            else
            {
                if (result)
                    System.out.println(resMess + " succeed !");
                else
                    System.err.println(resMess + " failed !");
            }

            return result;
        }
        finally
        {
            // desintall end
            desinstallingWorkspace = null;
        }
    }

    /**
     * process on workspace install
     */
    private void installed(Workspace workspace, boolean result)
    {
        if (result)
        {
            // enable the installed workspace by default
            WorkspaceLocalPreferences.setWorkspaceEnable(workspace.getName(), true);
            // show an announcement for restart
            Icy.announceRestart();
        }

        fireInstalledEvent(new WorkspaceInstallerEvent(workspace, result));
    }

    /**
     * process on workspace remove
     */
    private void desinstalled(Workspace workspace, boolean result)
    {
        if (result)
            // show an announcement for restart
            Icy.announceRestart();

        fireRemovedEvent(new WorkspaceInstallerEvent(workspace, result));
    }

    /**
     * Add a listener
     * 
     * @param listener
     */
    public static void addListener(WorkspaceInstallerListener listener)
    {
        synchronized (instance.listeners)
        {
            instance.listeners.add(WorkspaceInstallerListener.class, listener);
        }
    }

    /**
     * Remove a listener
     * 
     * @param listener
     */
    public static void removeListener(WorkspaceInstallerListener listener)
    {
        synchronized (instance.listeners)
        {
            instance.listeners.remove(WorkspaceInstallerListener.class, listener);
        }
    }

    /**
     * fire workspace installed event
     */
    private void fireInstalledEvent(WorkspaceInstallerEvent e)
    {
        for (WorkspaceInstallerListener listener : listeners.getListeners(WorkspaceInstallerListener.class))
            listener.workspaceInstalled(e);
    }

    /**
     * fire workspace removed event
     */
    private void fireRemovedEvent(WorkspaceInstallerEvent e)
    {
        for (WorkspaceInstallerListener listener : listeners.getListeners(WorkspaceInstallerListener.class))
            listener.workspaceRemoved(e);
    }

}