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

import java.io.Closeable;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;

import java.util.Properties;

import net.sf.jaxodraw.util.JaxoDictionary;
import net.sf.jaxodraw.util.JaxoInfo;
import net.sf.jaxodraw.util.JaxoLog;


/** An abstract superclass for all plugins.
 * @since 2.0
 */
public abstract class AbstractJaxoPlugin implements JaxoPlugin {
    private Component parent;
    private String shortGraphName = "";
    private boolean silent;
    private Object failure;
    private final Properties pluginProperties = new Properties();
    private final File cachedProperties;
    private final String pluginName;

    private JaxoDictionary language;

    /** Protected constructor, initialize properties settings. */
    protected AbstractJaxoPlugin() {
        this.pluginName = "jaxodraw-" + getShortName() + "-plugin";
        this.cachedProperties =
            new File(JaxoInfo.PLUGIN_DIR, pluginName + ".properties");
    }

    /** {@inheritDoc} */
    public String pluginName() {
        return pluginName;
    }

    /**
     * Register a plugin class for language translations.
     * This method has to be called before any calls to {@link #getLang()}.
     *
     * @param c the class to register for bundle lookup.
     */
    protected void registerDictionary(final Class<?> c) {
        language = new JaxoDictionary(c);
    }

    /**
     * Parent component for dialogs.
     * @return The parent component.
     */
    public Component getParentComponent() {
        return parent;
    }

    /**
     * Sets the parent component of this dialog.
     * @param value The parent component to set.
     */
    public void setParentComponent(final Component value) {
        parent = value;
    }

    /**
     * Checks if the last export() has failed.
     * @return True if the last export has failed.
     */
    public final boolean hasFailed() {
        return failure != null;
    }

    /**
     * Reason for failure of the last export(), probably an (IO)Exception.
     * This is reset to 'null' at the beginning of export().
     * @return The cause of the failure.
     */
    public final Object getFailure() {
        return failure;
    }

    /**
     * Sets the reason for an export failure.
     * @param value The reason to set.
     */
    protected void setFailure(final Object value) {
        if (value instanceof Exception) {
            getLog().debug((Exception) value);
        }

        if (value instanceof JaxoPluginExecutionException) {
            final Object src = ((JaxoPluginExecutionException) value).getSource();
            if (src != null) {
                getLog().debug("Source: " + src.toString());
            }
        }

        failure = value;
    }

    /**
     * If silent, export() will/should not show any error messages.
     * @return True if silent mode is on.
     */
    public final boolean isSilent() {
        return silent;
    }

    /**
     * Sets the silent mode.
     * @param value True for silent mode on.
     */
    public void setSilent(final boolean value) {
        silent = value;
    }

    /**
     * Sets a short name for the current graph.
     * This is used eg as titles in preview windows, etc.
     * @param newName The name to set.
     */
    public final void setShortGraphName(final String newName) {
        this.shortGraphName = newName;
    }

    /**
     * Returns the short name of the current graph.
     * @return The short name of the current graph.
     */
    public final String getShortGraphName() {
        return shortGraphName;
    }

      //
     // Property methods
    //

    /**
     * Returns the property for the specified key or
     * <code>null</code> if the property is not found.
     *
     * @param key the property key.
     * @return the property value or null if this property does not exist.
     */
    public String getProperty(final String key) {
        return pluginProperties.getProperty(pluginId() + "." + key);
    }

    /**
     * Returns the property for the specified key or
     * the given defaultValue if the property is not found.
     *
     * @param key the property key.
     * @param defaultValue a default value.
     * @return the property value.
     */
    public String getProperty(final String key, final String defaultValue) {
        return pluginProperties.getProperty(pluginId() + "." + key, defaultValue);
    }

    /**
     * Sets a property to the given value. Internally the key will be prepended
     * with the pluginId() because plugins with the same short name can
     * share the same properties file.
     *
     * @param key the key of the property.
     * @param value the value corresponding to key.
     */
    public void setProperty(final String key, final String value) {
        final Object old = pluginProperties.setProperty(pluginId() + "." + key, value);

        if (old == null) {
            getLog().debug(pluginId() + " added new property: "
                + key + " = " + value);
        } else if (!old.toString().equals(value)) {
            final StringBuffer buffer = new StringBuffer(100);
            buffer.append(pluginId()).append(" changed existing property: ");
            buffer.append(key).append(" = ").append(value);
            buffer.append(" (was: ").append(old.toString()).append(')');
            getLog().debug(buffer.toString());
        }
    }

    /** {@inheritDoc} */
    public void loadProperties() {

        if (!cachedProperties.exists()) {
            // no properties have been stored yet, nothing to load
            return;
        }

        try {
            FileInputStream fis = null;
            try {
                fis = new java.io.FileInputStream(cachedProperties);
                pluginProperties.load(fis);
            } finally {
                if (fis != null) {
                    fis.close();
                }
            }
        } catch (IOException e) {
            getLog().debug("Error loading pugin properties!", e);
        }
    }

    /**
     * Stores the current set of properties in a persistent file, so they
     * are automatically available at every session start-up.
     */
    public void storeProperties() {
        final String header = "Properties for jaxodraw-" + getShortName() + "-plugin";
        try {
            FileOutputStream fos = null;
            try {
                fos = new java.io.FileOutputStream(cachedProperties);
                pluginProperties.store(fos, header);
            } finally {
                close(fos);
            }
        } catch (IOException e) {
            getLog().debug("Error storing pugin properties!", e);
        }
    }

    /**
     * Return the current set of properties.
     *
     * @return The current set of properties.
     */
    public Properties getProperties() {
        return pluginProperties;
    }

      //
     // Utility methods
    //

    /**
     * Returns the name of this plugin class without the package.
     * @return the name of this plugin class without the package.
     */
    public final String getClassName() {
        String className = pluginId();
        final int firstChar = className.lastIndexOf('.') + 1;
        if (firstChar > 0) {
            className = className.substring(firstChar);
        }
        return className;
    }

    /**
     * Closes the given Closeable, swallowing any IOException
     * except for logging it in debug mode.
     *
     * @param closeable The Closeable to close.
     */
    protected void close(final Closeable closeable) {
        if (closeable != null) {
            try {
                closeable.close();
            } catch (IOException ioEx) {
                getLog().debug(ioEx);
            }
        }
    }

    /**
     * Get a logger for this plugin.
     *
     * @return a logger.
     */
    protected JaxoPluginLogger getLog() {
        return PluginLogger.getInstance();
    }

    private static class PluginLogger implements JaxoPluginLogger {

        private static PluginLogger logger;

        private static PluginLogger getInstance() {
            synchronized (PluginLogger.class) {
                if (logger == null) {
                    logger = new PluginLogger();
                }

                return logger;
            }
        }

        /** {@inheritDoc} */
        public boolean isDebugEnabled() {
            return JaxoLog.isLoggable(JaxoLog.DEBUG);
        }

        /** {@inheritDoc} */
        public boolean isInfoEnabled() {
            return JaxoLog.isLoggable(JaxoLog.INFO);
        }

        /** {@inheritDoc} */
        public boolean isWarnEnabled() {
            return JaxoLog.isLoggable(JaxoLog.WARN);
        }

        /** {@inheritDoc} */
        public void debug(final String msg) {
            JaxoLog.debug(msg);
        }

        /** {@inheritDoc} */
        public void debug(final Exception e) {
            JaxoLog.debug(e);
        }

        /** {@inheritDoc} */
        public void debug(final String msg, final Exception e) {
            JaxoLog.debug(msg, e);
        }

        /** {@inheritDoc} */
        public void info(final String msg) {
            JaxoLog.info(msg);
        }

        /** {@inheritDoc} */
        public void info(final Exception e) {
            JaxoLog.info(e);
        }

        /** {@inheritDoc} */
        public void info(final String msg, final Exception e) {
            JaxoLog.info(msg, e);
        }

        /** {@inheritDoc} */
        public void warn(final String msg) {
            JaxoLog.warn(msg);
        }

        /** {@inheritDoc} */
        public void warn(final Exception e) {
            JaxoLog.warn(e);
        }

        /** {@inheritDoc} */
        public void warn(final String msg, final Exception e) {
            JaxoLog.warn(msg, e);
        }
    }

    /**
     * Get a dictionary for this plugin. If no dictionary has yet been
     * registered with {@link #registerDictionary(java.lang.Class)} then
     * an IllegalStateException is thrown.
     *
     * @return a dictionary.
     *
     * @see #registerDictionary(java.lang.Class)
     */
    protected JaxoPluginDictionary getLang() {
        if (language == null) {
            throw new IllegalStateException("No dictionary registered!");
        }

        return PluginDictionary.getInstance(language);
    }

    private static class PluginDictionary implements JaxoPluginDictionary {

        private static PluginDictionary dictionary;
        private static JaxoDictionary language;

        private static PluginDictionary getInstance(final JaxoDictionary lang) {
            synchronized (PluginDictionary.class) {
                language = lang;

                if (dictionary == null) {
                    dictionary = new PluginDictionary();
                }

                return dictionary;
            }
        }

        /** {@inheritDoc} */
        public String label(final String key) {
            return language.label(key);
        }

        /** {@inheritDoc} */
        public String translate(final String key) {
            return language.value(key);
        }

        /** {@inheritDoc} */
        public String message(final String key, final Object argument) {
            return language.message(key, argument);
        }

        /** {@inheritDoc} */
        public String message(final String key, final Object argument1, final Object argument2) {
            return language.message(key, argument1, argument2);
        }

        /** {@inheritDoc} */
        public String message(final String key, final Object[] arguments) {
            return language.message(key, arguments);
        }
    }

      //
     // Abstract methods
    //


    /** The name of the plugin (export or import) format.
     * @return An (internationalized) name of the plugin format.
     */
    public abstract String getFormatName();

    /** Extension to be used for plugin files.
     * @return The file extension.
     */
    public abstract String getFileExtension();

    /** Description to be used for plugin file extensions.
     * @return An (internationalized) description of the file extension.
     */
    public abstract String getFileExtensionDescription();
}
