/*
 * 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 plugins.kernel.searchprovider;

import icy.gui.plugin.PluginDetailPanel;
import icy.main.Icy;
import icy.network.NetworkUtil;
import icy.plugin.PluginDescriptor;
import icy.plugin.PluginInstaller;
import icy.plugin.PluginLauncher;
import icy.plugin.PluginLoader;
import icy.plugin.PluginRepositoryLoader;
import icy.search.OnlineSearchResultProducer;
import icy.search.SearchEngine;
import icy.search.SearchResult;
import icy.search.SearchResultConsumer;
import icy.search.SearchResultProducer;
import icy.system.thread.ThreadUtil;
import icy.util.XMLUtil;

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

import org.w3c.dom.Document;
import org.w3c.dom.Element;

import plugins.kernel.searchprovider.LocalPluginSearchResultProducer.LocalPluginResult;

/**
 * This class is used to provide online plugin elements to the search engine.
 * 
 * @author Stephane
 */
public class OnlinePluginSearchResultProducer extends OnlineSearchResultProducer
{
    /**
     * @author Stephane
     */
    public static class OnlinePluginResult extends PluginSearchResult
    {
        public OnlinePluginResult(SearchResultProducer provider, PluginDescriptor plugin, String text,
                String searchWords[], int priority)
        {
            super(provider, plugin, text, searchWords, priority);
        }

        @Override
        public String getTooltip()
        {
            return "Left click: Install and Run   -   Right click: Online documentation";
        }

        @Override
        public void execute()
        {
            // can take sometime, better to execute it in background
            ThreadUtil.bgRun(new Runnable()
            {
                @Override
                public void run()
                {
                    // plugin locally installed ? (result transfer to local plugin provider)
                    if (PluginLoader.isLoaded(plugin.getClassName()))
                    {
                        if (plugin.isActionable())
                            PluginLauncher.start(plugin);
                        else
                        {
                            ThreadUtil.invokeLater(new Runnable()
                            {
                                @Override
                                public void run()
                                {
                                    new PluginDetailPanel(plugin);
                                }
                            });
                        }
                    }
                    else
                    {
                        // install and run the plugin (if user ok with install)
                        PluginInstaller.install(plugin, true);
                        // wait for installation complete
                        PluginInstaller.waitInstall();

                        // find the new installed plugin
                        final PluginDescriptor localPlugin = PluginLoader.getPlugin(plugin.getClassName());
                        // plugin found ?
                        if (localPlugin != null)
                        {
                            // launch it if actionable
                            if (localPlugin.isActionable())
                                PluginLauncher.start(localPlugin);
                            else
                            {
                                // just display info
                                ThreadUtil.invokeLater(new Runnable()
                                {
                                    @Override
                                    public void run()
                                    {
                                        new PluginDetailPanel(localPlugin);
                                    }
                                });
                            }
                        }
                    }
                }
            });
        }
    }

    private static final String ID_SEARCH_RESULT = "searchresult";
    private static final String ID_PLUGIN = "plugin";
    private static final String ID_CLASSNAME = "classname";
    // private static final String ID_NAME = "name";
    private static final String ID_TEXT = "string";

    @Override
    public int getOrder()
    {
        // should be right after local plugin
        return 6;
    }

    @Override
    public String getName()
    {
        return "Online plugins";
    }

    @Override
    public String getTooltipText()
    {
        return "Result(s) from online plugin";
    }

    @Override
    public void doSearch(Document doc, String[] words, SearchResultConsumer consumer)
    {
        // Online plugin loader failed --> exit
        if (!ensureOnlineLoaderLoaded())
            return;

        // no need to spent more time here...
        if (hasWaitingSearch())
            return;

        // get online plugins
        final List<PluginDescriptor> onlinePlugins = PluginRepositoryLoader.getPlugins();
        // get online result node
        final Element resultElement = XMLUtil.getElement(doc.getDocumentElement(), ID_SEARCH_RESULT);

        if (resultElement == null)
            return;

        // get the local plugin search provider from search engine
        final SearchEngine se = Icy.getMainInterface().getSearchEngine();
        LocalPluginSearchResultProducer lpsrp = null;

        if (se != null)
        {
            for (SearchResultProducer srp : se.getSearchResultProducers())
                if (srp instanceof LocalPluginSearchResultProducer)
                    lpsrp = (LocalPluginSearchResultProducer) srp;
        }

        final List<SearchResult> tmpResults = new ArrayList<SearchResult>();

        for (Element plugin : XMLUtil.getElements(resultElement, ID_PLUGIN))
        {
            // abort
            if (hasWaitingSearch())
                return;

            final SearchResult result = getResult(consumer, onlinePlugins, plugin, words, lpsrp);

            if (result != null)
                tmpResults.add(result);
        }

        // use a copy to avoid future concurrent accesses
        results = new ArrayList<SearchResult>(tmpResults);
        consumer.resultsChanged(this);

        // load descriptions
        for (SearchResult result : tmpResults)
        {
            // abort
            if (hasWaitingSearch())
                return;

            ((OnlinePluginResult) result).getPlugin().loadDescriptor();
            consumer.resultChanged(this, result);
        }

        // load images
        for (SearchResult result : tmpResults)
        {
            // abort
            if (hasWaitingSearch())
                return;

            ((OnlinePluginResult) result).getPlugin().loadImages();
            consumer.resultChanged(this, result);
        }
    }

    private static boolean ensureOnlineLoaderLoaded()
    {
        PluginRepositoryLoader.waitLoaded();

        // repository loader failed --> retry once
        if (PluginRepositoryLoader.failed() && NetworkUtil.hasInternetAccess())
        {
            PluginRepositoryLoader.reload();
            PluginRepositoryLoader.waitLoaded();
        }

        return !PluginRepositoryLoader.failed();
    }

    private OnlinePluginResult getResult(SearchResultConsumer consumer, List<PluginDescriptor> onlinePlugins,
            Element pluginNode, String words[], LocalPluginSearchResultProducer lpsrp)
    {
        final String className = XMLUtil.getElementValue(pluginNode, ID_CLASSNAME, "");
        final String text = XMLUtil.getElementValue(pluginNode, ID_TEXT, "");
        final boolean shortSearch = PluginSearchResultProducerHelper.getShortSearch(words);
        int priority;

        final PluginDescriptor localPlugin = PluginLoader.getPlugin(className);
        // exists in local ?
        if (localPlugin != null)
        {
            // if we have the local search provider, we try to add result if not already existing
            if (lpsrp != null)
            {
                final List<SearchResult> localResults = lpsrp.getResults();
                boolean alreadyExists = false;

                synchronized (localResults)
                {
                    for (SearchResult result : localResults)
                    {
                        if (((LocalPluginResult) result).getPlugin() == localPlugin)
                        {
                            alreadyExists = true;
                            break;
                        }
                    }
                }

                // not already present in local result --> add it
                if (!alreadyExists)
                {
                    priority = PluginSearchResultProducerHelper.searchInPlugin(localPlugin, words, shortSearch);

                    // not found in local description --> assume low priority
                    if (priority == 0)
                        priority = 1;

                    lpsrp.addResult(new LocalPluginResult(lpsrp, localPlugin, text, words, priority), consumer);
                }
            }

            // don't return it for online result
            return null;
        }

        final PluginDescriptor onlinePlugin = PluginDescriptor.getPlugin(onlinePlugins, className);
        // cannot be found in online ? --> no result
        if (onlinePlugin == null)
            return null;

        // try to get priority on result
        onlinePlugin.loadDescriptor();
        priority = PluginSearchResultProducerHelper.searchInPlugin(onlinePlugin, words, shortSearch);

        // only keep high priority info from local data
        if (priority <= 5)
            priority = 1;

        return new OnlinePluginResult(this, onlinePlugin, text, words, priority);
    }
}