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

import icy.file.FileUtil;
import icy.gui.frame.IcyFrame;
import icy.gui.viewer.Viewer;
import icy.image.IcyBufferedImage;
import icy.image.ImageUtil;
import icy.main.Icy;
import icy.network.NetworkUtil;
import icy.plugin.PluginDescriptor;
import icy.plugin.PluginLauncher;
import icy.plugin.PluginLoader;
import icy.plugin.interface_.PluginBundled;
import icy.plugin.interface_.PluginThreaded;
import icy.preferences.PluginsPreferences;
import icy.preferences.XMLPreferences;
import icy.resource.ResourceUtil;
import icy.sequence.Sequence;
import icy.system.IcyExceptionHandler;
import icy.system.SystemUtil;
import icy.system.audit.Audit;
import icy.util.ClassUtil;

import java.awt.image.BufferedImage;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.util.ArrayList;
import java.util.Enumeration;
import java.util.List;

import javax.swing.ImageIcon;

/**
 * Base class for Plugin, provide some helper methods.<br>
 * By default the constructor of a Plugin class is called in the EDT (Event Dispatch Thread).<br>
 * If the plugin implements the {@link PluginThreaded} there is no more guarantee that is the case.
 * 
 * @author Fabrice de Chaumont & Stephane
 */
public abstract class Plugin
{
    public static Plugin getPlugin(List<Plugin> list, String className)
    {
        for (Plugin plugin : list)
            if (plugin.getClass().getName().equals(className))
                return plugin;

        return null;
    }

    private PluginDescriptor descriptor;

    /**
     * Default Plugin constructor.<br>
     * The {@link PluginLauncher} is normally responsible of Plugin class instantiation.
     */
    public Plugin()
    {
        super();

        // get descriptor from loader
        descriptor = PluginLoader.getPlugin(getClass().getName());

        if (descriptor == null)
        {
            // descriptor not found (don't check for anonymous plugin class) ?
            if (!getClass().isAnonymousClass())
            {
                System.out.println("Warning : Plugin '" + getClass().getName()
                        + "' started but not found in PluginLoader !");
                System.out.println("Local XML plugin description file is probably incorrect.");
            }

            // create dummy descriptor
            descriptor = new PluginDescriptor(this.getClass());
            descriptor.setName(getClass().getSimpleName());
        }

        // audit
        Audit.pluginInstancied(this);
    }

    @Override
    protected void finalize() throws Throwable
    {
        // unregister plugin (weak reference so we can do it here)
        Icy.getMainInterface().unRegisterPlugin(this);

        super.finalize();
    }

    /**
     * @return the descriptor
     */
    public PluginDescriptor getDescriptor()
    {
        return descriptor;
    }

    /**
     * @return the plugin name (from its descriptor)
     */
    public String getName()
    {
        return descriptor.getName();
    }

    /**
     * @return <code>true</code> if this is a bundled plugin (see {@link PluginBundled}).
     */
    public boolean isBundled()
    {
        return this instanceof PluginBundled;
    }

    /**
     * @return the class name of the plugin owner.<br>
     *         If this Plugin is not bundled (see {@link PluginBundled}) then it just returns the
     *         current class name otherwise it will returns the plugin owner class name.
     */
    public String getOwnerClassName()
    {
        if (isBundled())
            return ((PluginBundled) this).getMainPluginClassName();

        return getClass().getName();
    }

    /**
     * @return the folder where the plugin is installed (or should be installed).
     */
    public String getInstallFolder()
    {
        return ClassUtil.getPathFromQualifiedName(ClassUtil.getPackageName(getClass().getName()));
    }

    public Viewer getActiveViewer()
    {
        return Icy.getMainInterface().getActiveViewer();
    }

    public Sequence getActiveSequence()
    {
        return Icy.getMainInterface().getActiveSequence();
    }

    public IcyBufferedImage getActiveImage()
    {
        return Icy.getMainInterface().getActiveImage();
    }

    /**
     * @deprecated Use {@link #getActiveViewer()} instead
     */
    @Deprecated
    public Viewer getFocusedViewer()
    {
        return getActiveViewer();
    }

    /**
     * @deprecated Use {@link #getActiveSequence()} instead
     */
    @Deprecated
    public Sequence getFocusedSequence()
    {
        return getActiveSequence();
    }

    /**
     * @deprecated Use {@link #getActiveImage()} instead
     */
    @Deprecated
    public IcyBufferedImage getFocusedImage()
    {
        return getActiveImage();
    }

    public void addIcyFrame(final IcyFrame frame)
    {
        frame.addToDesktopPane();
    }

    public void addSequence(final Sequence sequence)
    {
        Icy.getMainInterface().addSequence(sequence);
    }

    public void removeSequence(final Sequence sequence)
    {
        sequence.close();
    }

    public ArrayList<Sequence> getSequences()
    {
        return Icy.getMainInterface().getSequences();
    }

    /**
     * Return the resource URL from given resource name.<br>
     * Ex: <code>getResource("plugins/author/resources/def.xml");</code>
     * 
     * @param name
     *        resource name
     */
    public URL getResource(String name)
    {
        return getClass().getClassLoader().getResource(name);
    }

    /**
     * Return resources corresponding to given resource name.<br>
     * Ex: <code>getResources("plugins/author/resources/def.xml");</code>
     * 
     * @param name
     *        resource name
     * @throws IOException
     */
    public Enumeration<URL> getResources(String name) throws IOException
    {
        return getClass().getClassLoader().getResources(name);
    }

    /**
     * Return the resource as data stream from given resource name.<br>
     * Ex: <code>getResourceAsStream("plugins/author/resources/def.xml");</code>
     * 
     * @param name
     *        resource name
     */
    public InputStream getResourceAsStream(String name)
    {
        return getClass().getClassLoader().getResourceAsStream(name);
    }

    /**
     * Return the image resource from given resource name
     * Ex: <code>getResourceAsStream("plugins/author/resources/image.png");</code>
     * 
     * @param resourceName
     *        resource name
     */
    public BufferedImage getImageResource(String resourceName)
    {
        return ImageUtil.load(getResourceAsStream(resourceName));
    }

    /**
     * Return the icon resource from given resource name
     * Ex: <code>getResourceAsStream("plugins/author/resources/icon.png");</code>
     * 
     * @param resourceName
     *        resource name
     */
    public ImageIcon getIconResource(String resourceName)
    {
        return ResourceUtil.getImageIcon(getImageResource(resourceName));
    }

    /**
     * Retrieve the preferences root for this plugin.<br>
     */
    public XMLPreferences getPreferencesRoot()
    {
        return PluginsPreferences.root(this);
    }

    /**
     * Retrieve the plugin preferences node for specified name.<br>
     * i.e : getPreferences("window") will return node
     * "plugins.[authorPackage].[pluginClass].window"
     */
    public XMLPreferences getPreferences(String name)
    {
        return getPreferencesRoot().node(name);
    }

    /**
     * Returns the base resource path for plugin native libraries.<br/>
     * Depending the Operating System it can returns these values:
     * <ul>
     * <li>lib/unix32</li>
     * <li>lib/unix64</li>
     * <li>lib/mac32</li>
     * <li>lib/mac64</li>
     * <li>lib/win32</li>
     * <li>lib/win64</li>
     * </ul>
     */
    protected String getResourceLibraryPath()
    {
        return "lib" + FileUtil.separator + SystemUtil.getOSArchIdString();
    }

    /**
     * Load a packed native library from the JAR file.<br/>
     * Native libraries should be packaged with the following directory & file structure:
     * 
     * <pre>
     * /lib/unix32
     *   libxxx.so
     * /lib/unix64
     *   libxxx.so
     * /lib/mac32
     *   libxxx.dylib
     * /lib/mac64
     *   libxxx.dylib
     * /lib/win32
     *   xxx.dll
     * /lib/win64
     *   xxx.dll
     * /plugins/myname/mypackage    
     *   MyPlugin.class
     *   ....
     * </pre>
     * 
     * Here "xxx" is the name of the native library.<br/>
     * Current approach is to unpack the native library into a temporary file and load from there.
     * 
     * @param libName
     * @return true if the library was correctly loaded.
     * @see SystemUtil#loadLibrary(String)
     */
    public boolean loadLibrary(String libName)
    {
        try
        {
            // get mapped library name
            String mappedlibName = System.mapLibraryName(libName);
            // get base resource path for native library
            final String basePath = getResourceLibraryPath() + FileUtil.separator;

            // search for library in resource
            URL libUrl = getResource(basePath + mappedlibName);

            // not found ?
            if (libUrl == null)
            {
                // jnilib extension may not work, try with "dylib" extension instead
                if (mappedlibName.endsWith(".jnilib"))
                {
                    mappedlibName = mappedlibName.substring(0, mappedlibName.length() - 7) + ".dylib";
                    libUrl = getResource(basePath + mappedlibName);
                }
                // do the contrary in case we have an old "jnilib" file and system use "dylib" by default
                else if (mappedlibName.endsWith(".dylib"))
                {
                    mappedlibName = mappedlibName.substring(0, mappedlibName.length() - 6) + ".jnilib";
                    libUrl = getResource(basePath + mappedlibName);
                }                
            }

            // resource not found --> error
            if (libUrl == null)
                throw new IOException("Couldn't find resource " + basePath + mappedlibName);

            // extract resource
            final File extractedFile = extractResource(SystemUtil.getTempLibraryDirectory() + FileUtil.separator
                    + mappedlibName, libUrl);
            // and load it
            System.load(extractedFile.getPath());

            return true;
        }
        catch (IOException e)
        {
            System.err.println("Error while loading packed library " + libName + ": " + e);
        }

        return false;
    }

    /**
     * Extract a resource to the specified path
     * 
     * @param outputPath
     *        the file to extract the resource to
     * @param resource
     *        the resource URL
     * @return the extracted file
     * @throws IOException
     */
    protected File extractResource(String outputPath, URL resource) throws IOException
    {
        // open resource stream
        final InputStream in = resource.openStream();
        // load resource
        final byte data[] = NetworkUtil.download(in);
        // create output file
        final File result = new File(outputPath);

        // file already exist ??
        if (result.exists())
        {
            // same size --> assume it's the same
            if (result.length() == data.length)
                return result;

            if (!FileUtil.delete(result, false))
                throw new IOException("Cannot overwrite " + result + " file !");
        }

        // save resource to file
        FileUtil.save(result, data, true);

        return result;
    }

    /**
     * Report an error log for this plugin (reported to Icy web site which report then to the
     * author of the plugin).
     * 
     * @see IcyExceptionHandler#report(PluginDescriptor, String)
     */
    public void report(String errorLog)
    {
        IcyExceptionHandler.report(descriptor, errorLog);
    }

    @Override
    public String toString()
    {
        return getDescriptor().getName();
    }
}