/**
 *  Licensed under GPL. For more information, see
 *    http://jaxodraw.sourceforge.net/license.html
 *  or the LICENSE file in the jaxodraw distribution.
 */
package net.sf.jaxodraw.plugin;

import java.io.File;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Set;

import net.sf.jaxodraw.io.imports.JaxoImport;
import net.sf.jaxodraw.io.exports.JaxoExport;
import net.sf.jaxodraw.util.JaxoInfo;
import net.sf.jaxodraw.util.JaxoLanguage;
import net.sf.jaxodraw.util.JaxoLog;


/** Collects information about available plugins.
 * @since 2.0
 */
public final class JaxoPluginInfo {
    // implemented as a singleton so we only load the plugins once
    private static JaxoPluginInfo pluginInfo;

    // class fields
    private final List<Object> exportFormats = new ArrayList<Object>(8);
    private final List<JaxoImportPlugin> importFormats = new ArrayList<JaxoImportPlugin>(2);
    private final Set<String> externalPlugins = new HashSet<String>(4);

    /** Private constructor. */
    private JaxoPluginInfo() {
        init();
    }

    /** The only method to access the JaxoPluginInfo object.
     * @return The singleton instance variable of this JaxoPluginInfo object.
     */
    public static JaxoPluginInfo getPluginInfo() {
        synchronized (JaxoPluginInfo.class) {
            if (pluginInfo == null) {
                pluginInfo = new JaxoPluginInfo();
            }
            return pluginInfo;
        }
    }

    private void init() {
        setupPluginFormats();
    }

    private void setupPluginFormats() {

        // built-in formats
        exportFormats.addAll(JaxoExport.getBuiltInFormats());
        importFormats.addAll(JaxoImport.getBuiltInFormats());

        // create plugin dir if it doesn't exist
        createPluginDir();

        final JaxoPluginLoader pluginLoader = new JaxoPluginLoader();
        final List<JaxoPlugin> plugins = pluginLoader.loadPlugins(JaxoInfo.PLUGIN_DIR);

        for (final Iterator<JaxoPlugin> it = plugins.iterator(); it.hasNext();) {
            addPlugin(it.next());
        }
    }

    /**
     * Re-scans the plugin dir, loading any found plugins.
     */
    public void reValidate() {
        exportFormats.clear();
        importFormats.clear();
        externalPlugins.clear();

        // built-in formats
        exportFormats.addAll(JaxoExport.getBuiltInFormats());
        importFormats.addAll(JaxoImport.getBuiltInFormats());

        // create plugin dir if it doesn't exist
        createPluginDir();

        final JaxoPluginLoader pluginLoader = new JaxoPluginLoader();
        final List<JaxoPlugin> plugins = pluginLoader.reValidate(JaxoInfo.PLUGIN_DIR);

        for (final Iterator<JaxoPlugin> it = plugins.iterator(); it.hasNext();) {
            addPlugin(it.next());
        }
    }

    /**
     * Returns the list of available plugins as a String.
     * @return The list of available plugins.
     */
    public String availablePlugins() {
        return externalPlugins.isEmpty() ? JaxoLanguage.translate("None")
                                            : externalPlugins.toString();
    }

    /**
     * Returns the plugins that are available at runtime.
     * Elements are Strings of class names.
     *
     * @return A copy of the set of available plugins.
     */
    public Set<String> getAvailablePlugins() {
        return new HashSet<String>(externalPlugins);
    }

    /**
     * Installed export formats. This includes the built-in formats plus any
     * formats that are available at runtime via plugins. Elements are either
     * Lists (containing a subgroup of JaxoExportPlugin formats),
     * or a JaxoExportPlugin.
     *
     * @return A copy of the list of export formats.
     */
    public List<Object> getExportFormats() {
        return new ArrayList<Object>(exportFormats);
    }

    /**
     * Installed import formats. This includes the built-in formats plus any
     * formats that are available at runtime via plugins. Elements are either
     * Lists (containing a subgroup of JaxoImportPlugin formats),
     * or a JaxoImportPlugin.
     *
     * @return A copy of the list of import formats.
     */
    public List<JaxoImportPlugin> getImportFormats() {
        return new ArrayList<JaxoImportPlugin>(importFormats);
    }

    /**
     * Add a plugin to the list of plugins. *Note* that this does not check
     * whether the plugin is currently on the classpath, it is assumed that
     * the plugin has previously been loaded with the methods in
     * {@link JaxoPluginLoader}.
     *
     * @param plugin the plugin to add. If this is not and instance of either
     * {@link JaxoExportPlugin} or {@link JaxoImportPlugin}, does nothing.
     */
    public void addPlugin(final JaxoPlugin plugin) {
        if (plugin instanceof JaxoExportPlugin) {
            final JaxoExportPlugin exportPlugin = (JaxoExportPlugin) plugin;
            externalPlugins.add(exportPlugin.getClassName());
            if (exportPlugin.makeAvailableAtRuntime()
                    && !exportFormats.contains(exportPlugin)) {
                exportFormats.add(exportPlugin);
            }
        } else if (plugin instanceof JaxoImportPlugin) {
            final JaxoImportPlugin importPlugin = (JaxoImportPlugin) plugin;
            externalPlugins.add(importPlugin.getClassName());
            if (importPlugin.makeAvailableAtRuntime()
                    && !importFormats.contains(importPlugin)) {
                importFormats.add(importPlugin);
            }
        }
    }

    /**
     * Removes the given JaxoPlugin from the list of plugins.
     * If the plugin has been added previously, it stays on the classpath
     * of the running instance of JaxoDraw.
     *
     * @param plugin the plugin to remove.
     */
    @SuppressWarnings("element-type-mismatch")
    public void removePlugin(final JaxoPlugin plugin) {
        if (plugin == null) {
            return;
        }

        if (exportFormats.remove(plugin)) {
            JaxoLog.debug("removed export plugin: " + plugin.pluginId());
        }

        if (importFormats.remove(plugin)) {
            JaxoLog.debug("removed import plugin: " + plugin.pluginId());
        }

        if (externalPlugins.remove(((AbstractJaxoPlugin) plugin).getClassName())) {
            JaxoLog.debug("removed external plugin: " + plugin.pluginId());
        }
    }

    /**
     * Return an import plugin that is capable of importing a given format.
     * Note that this just returns the first plugin found for the given
     * extension, this might not be the correct one if different plugins
     * use the same extension.
     *
     * @param ext the file extension.
     *
     * @return A JaxoImportPlugin,
     *          or null if none was found for the given extension.
     */
    public JaxoImportPlugin getImportForExtension(final String ext) {
        for (final Iterator<JaxoImportPlugin> it = importFormats.iterator(); it.hasNext();) {
            final JaxoImportPlugin plugin = it.next();

            if (ext.equals(plugin.getFileExtension())) {
                return plugin;
            }
        }

        return null;
    }

    private void createPluginDir() {

        final File pluginDir = new File(JaxoInfo.PLUGIN_DIR);

        if (!pluginDir.exists()) {
            if (pluginDir.mkdirs()) {
                JaxoLog.debug("Created plugin directory: " + JaxoInfo.PLUGIN_DIR);
            } else {
                JaxoLog.warn("Failed to create plugin dir: " + JaxoInfo.PLUGIN_DIR);
            }
        }
    }
}
