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

import icy.gui.frame.progress.AnnounceFrame;
import icy.gui.frame.progress.CancelableProgressFrame;
import icy.gui.frame.progress.ProgressFrame;
import icy.gui.plugin.PluginUpdateFrame;
import icy.main.Icy;
import icy.network.NetworkUtil;
import icy.system.thread.ThreadUtil;
import icy.util.Random;

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

/**
 * Plugin updater class.
 * 
 * @author Stephane.D
 */
public class PluginUpdater
{
    private static final int ANNOUNCE_SHOWTIME = 15;

    // internal
    private static boolean silent;
    private static boolean checking = false;
    private static Runnable checker = new Runnable()
    {
        @Override
        public void run()
        {
            processCheckUpdate();
        }
    };

    /**
     * return true if we are currently checking for update
     */
    public static boolean isCheckingForUpdate()
    {
        return checking || ThreadUtil.hasWaitingBgSingleTask(checker);
    }

    /**
     * Do the check update process
     */
    public static void checkUpdate(boolean silent)
    {
        if (!isCheckingForUpdate())
        {
            PluginUpdater.silent = silent;
            ThreadUtil.bgRunSingle(checker);
        }
    }

    /**
     * @deprecated Use {@link #checkUpdate(boolean)} instead
     */
    @Deprecated
    public static void checkUpdate(boolean showProgress, boolean auto)
    {
        checkUpdate(!showProgress || auto);
    }

    /**
     * Get update for the specified plugin.
     * 
     * @param plugin
     *        local plugin we are looking update for
     * @return
     *         plugin descriptor of update if any (null if no update)
     */
    public static PluginDescriptor getUpdate(PluginDescriptor plugin)
    {
        // don't check update for kernel plugin
        if (plugin.isKernelPlugin()) return null;
        
        // find equivalent online plugins
        final List<PluginDescriptor> onlinePlugins = PluginRepositoryLoader.getPlugins(plugin.getClassName());
        final PluginDescriptor onlinePlugin;

        // get the last version found
        if (onlinePlugins.size() > 0)
        {
            PluginDescriptor lastVersion = null;

            for (PluginDescriptor currentVersion : onlinePlugins)
                if ((lastVersion == null) || currentVersion.isNewer(lastVersion))
                    lastVersion = currentVersion;

            onlinePlugin = lastVersion;
        }
        else
            // not found in repositories
            onlinePlugin = null;

        // we have an update available ?
        if ((onlinePlugin != null) && onlinePlugin.getVersion().isGreater(plugin.getVersion()))
            return onlinePlugin;

// random forced update
//if (Random.nextBoolean())
//    return onlinePlugin;

        return null;
    }

    /**
     * Update the specified list of plugins.
     */
    public static void updatePlugins(List<PluginDescriptor> plugins, boolean showProgress)
    {
        try
        {
            // update plugins with ordered dependencies
            for (PluginDescriptor plugin : PluginInstaller.orderDependencies(plugins))
                PluginInstaller.install(plugin, showProgress);
        }
        finally
        {
            PluginLoader.reloadAsynch();
        }
    }

    /**
     * Check for plugins update process (synchronized method)
     */
    public static synchronized void processCheckUpdate()
    {
        checking = true;
        try
        {
            final List<PluginDescriptor> toInstallPlugins = new ArrayList<PluginDescriptor>();
            final List<PluginDescriptor> localPlugins = PluginLoader.getPlugins(false);
            final ProgressFrame checkingFrame;

            if (!silent && !Icy.getMainInterface().isHeadLess())
                checkingFrame = new CancelableProgressFrame("checking for plugins update...");
            else
                checkingFrame = null;
            try
            {
                // reload online plugins from all active repositories
                PluginRepositoryLoader.reload();
                // wait for basic infos
                PluginRepositoryLoader.waitLoaded();

                if (PluginRepositoryLoader.failed())
                {
                    if (!silent && !Icy.getMainInterface().isHeadLess())
                    {
                        if (!NetworkUtil.hasInternetAccess())
                            new AnnounceFrame("You are not connected to internet.", 10);
                        else
                            new AnnounceFrame("Can't access the repositories... You should verify your connection.", 10);
                    }

                    return;
                }

                for (PluginDescriptor localPlugin : localPlugins)
                {
                    // find update
                    final PluginDescriptor onlinePlugin = getUpdate(localPlugin);

                    // update found, add to the list
                    if (onlinePlugin != null)
                    {
                        // we load complete descriptor so we will have the changeslog
                        onlinePlugin.loadDescriptor();
                        toInstallPlugins.add(onlinePlugin);
                    }
                }

                final List<PluginDescriptor> onlinePlugins = PluginRepositoryLoader.getPlugins();

                for (PluginDescriptor onlinePlugin : onlinePlugins)
                {
                    // we found a plugin which is installed but not correctly loaded
                    // so we try to reinstall it
                    if (onlinePlugin.isInstalled() && !PluginLoader.isLoaded(onlinePlugin.getClassName()))
                    {
                        // we load complete descriptor so we will have the changeslog
                        onlinePlugin.loadDescriptor();
                        toInstallPlugins.add(onlinePlugin);
                    }
                }
            }
            finally
            {
                if (checkingFrame != null)
                    checkingFrame.close();
            }

            // some updates availables ?
            if (!toInstallPlugins.isEmpty())
            {
                // silent update or headless mode
                if (silent || Icy.getMainInterface().isHeadLess())
                {
                    // automatically install all updates (orderer depending dependencies)
                    updatePlugins(toInstallPlugins, true);
                }
                else
                {
                    // show announcement for 15 seconds
                    new AnnounceFrame(toInstallPlugins.size() + " plugin update are available", "View", new Runnable()
                    {
                        @Override
                        public void run()
                        {
                            // show pluginInstaller frame
                            new PluginUpdateFrame(toInstallPlugins);
                        }
                    }, ANNOUNCE_SHOWTIME);
                }
            }
            else
            {
                // inform that there is no plugin update available
                if (!silent && !Icy.getMainInterface().isHeadLess())
                    new AnnounceFrame("No plugin udpate available", 10);
            }
        }
        finally
        {
            checking = false;
        }
    }
}