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

import java.text.MessageFormat;

import java.util.HashMap;
import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.ResourceBundle;


/** A convenience class to deal with I18N issues.
 * @since 2.0
 */
public final class JaxoLanguage {
    /** English. */
    public static final String ENGLISH = "english";

    /** German. */
    public static final String GERMAN = "german";

    /** French. */
    public static final String FRENCH = "french";

    /** Italian. */
    public static final String ITALIAN = "italian";

    /** Spanish. */
    public static final String SPANISH = "spanish";

    /** The bundle name used for language lookups. */
    private static final String BUNDLE_NAME = "resources/properties/language";

    // private fields
    private static final Locale SPANISH_LOCALE = new Locale("es");
    private static ResourceBundle languageBundle;
    private static Locale locale;
    private static Map<String, MessageFormat> messageFormats;
    private static final String[] KNOWN_LANGUAGES =
    {ENGLISH, GERMAN, FRENCH, ITALIAN, SPANISH};

    private static final Map<String, PluginBundle> PLUGIN_BUNDLES;

    static {
        PLUGIN_BUNDLES = new HashMap<String, PluginBundle>(8);

        messageFormats = new HashMap<String, MessageFormat>(16);

        languageChanged(ENGLISH);
    }

    private static class PluginBundle {
        private final Class<?> klass;
        private ResourceBundle bundle;

        PluginBundle(final Class<?> c, final Locale l) {
            klass = c;
            setResourceBundleFor(l);
        }

        public final void setResourceBundleFor(final Locale value) {
            bundle = ResourceBundle.getBundle(
                bundleName(klass), locale, klass.getClassLoader());
        }

        public final String getString(final String key) {
            return bundle.getString(key);
        }

        private static String bundleName(final Class<?> c) {
            final int dot = c.getName().lastIndexOf('.');

            return c.getName().substring(0, dot + 1)
                .replace('.', '/').concat(BUNDLE_NAME);
        }
    }

    // Empty private constructor to prevent the class from being instantiated.
    private JaxoLanguage() {
    }

    /** Register a custom bundle for the class c. The name of the class,
        ie c.getName(), is registered as an identifier, which can be used
        in the methods bundleMessage() and bundleTranslate() to translate messages.
        The bundle must be named 'language' and will be looked for in
        <code>c.getName()/resources/properties</code> (using the class
        loader of 'c')  where dots in the class name are replaced by '/'.
     * @param c The class for which a bundle should be registered.
     */
    // TODO: This does not allow unloading of a plugin, since 'c' is strongly
    // referenced here
    public static void registerBundle(final Class<?> c) {
        PLUGIN_BUNDLES.put(c.getName(), new PluginBundle(c, locale));
        JaxoLog.debug("Registered language bundle for: " + c.getName());
    }

    /** Register a custom bundle for the class c,
        ('c' may also be in the core).
        Also setup the dictionary to translate with the given bundle.
     * @param c The class for which a bundle should be registered.
     * @param d The dictionary to bind to the given class.
     * @see #registerBundle(Class)
     */
    public static void registerBundle(final Class<?> c, final JaxoDictionary d) {
        final String n = c.getName();
        final int dot = n.lastIndexOf('.');
        final int plugin = n.indexOf(".plugin.");

        // this is a hack, TODO: put in real logic to find the core bundle
        if (plugin > 0 && dot != plugin + 7) {
            registerBundle(c);
            d.setValues(c.getName(), "");
        } else { // core
            d.setValues(null, n.substring(dot + 1) + ".");
        }
    }

    /** Removes the previously registered bundle for the class c.
     * The name of the class, ie c.getName(), is used as the key.
     * @param c The class for which a bundle should be removed.
     */
    public static void removeBundle(final Class<?> c) {
        final Object o = PLUGIN_BUNDLES.remove(c.getName());
        if (o != null) {
            JaxoLog.debug("Removed bundle for: " + c.getName());
        }
    }

    /**
     * Updates the current locale and associated language bundle.
     * @param language The new language.
     */
    public static void languageChanged(final String language) {

        if (language.equals(ENGLISH)) {
            locale = Locale.ENGLISH;
        } else if (language.equals(GERMAN)) {
            locale = Locale.GERMAN;
        } else if (language.equals(FRENCH)) {
            locale = Locale.FRENCH;
        } else if (language.equals(ITALIAN)) {
            locale = Locale.ITALIAN;
        } else if (language.equals(SPANISH)) {
            locale = SPANISH_LOCALE;
        } else {
            // Treat unknown language as english
            locale = Locale.ENGLISH;
        }

        for (final Iterator<PluginBundle> i = PLUGIN_BUNDLES.values().iterator(); i.hasNext();) {
            final PluginBundle b = i.next();

            b.setResourceBundleFor(locale);
        }

        languageBundle = ResourceBundle.getBundle(BUNDLE_NAME, locale);

        messageFormats.clear();
    }

    /** Locale corresponding to the current language.
     * @return The current locale.
     */
    public static Locale locale() {
        return locale;
    }

    /** Translate 'key' as a label (typically, by appending ": " to the
        default translation).
     * @param key The key to translate.
     * @return The translation.
     */
    public static String label(final String key) {
        return message("JaxoDraw.label%0", translate(key));
    }

    /** Translates the given string into the current default language,
        interpolating a single argument.
        Equivalent to {@link #message(String,Object[])} with second argument
        <code>new Object[] { argument }</code>.
     * @param key The key to translate.
     * @param argument An object.
     * @return The translated string.
     */
    public static String message(final String key, final Object argument) {
        return message(key, new Object[]{argument});
    }

    /** Translates the given string into the current default language.
     * @param key The key to translate.
     * @param bundleId An identifier for an external language bundle,
     * if null, the default JaxoDraw bundle will be used.
     * @param argument An object.
     * @return The translated string.
     * @see #message(String,Object)
     */
    public static String bundleMessage(final String key, final String bundleId, final Object argument) {
        return bundleMessage(key, bundleId, new Object[]{argument});
    }

    /** Translates the given string into the current default language,
        interpolating two arguments.
        Equivalent to {@link #message(String,Object[])} with second argument
        <code>new Object[] { argument1, argument2 }</code>.
     * @param key The key to translate.
     * @param argument1 A first object.
     * @param argument2 A second object.
     * @return The translated string.
     */
    public static String message(final String key, final Object argument1, final Object argument2) {
        return message(key, new Object[]{argument1, argument2});
    }

    /** Translates the given string into the current default language.
     * @param key The key to translate.
     * @param bundleId An identifier for an external language bundle,
     * if null, the default JaxoDraw bundle will be used.
     * @param argument1 A first object.
     * @param argument2 A second object.
     * @return The translated string.
     * @see #message(String,Object,Object)
     */
    public static String bundleMessage(final String key, final String bundleId, final Object argument1, final Object argument2) {
        return bundleMessage(key, bundleId, new Object[]{argument1, argument2});
    }

    /** Translates the given string into the current default language,
        interpolating arguments.
        <p>If <code>arguments</code> is not empty, the translation of
        <code>key</code> is used as a
        {@link java.text.MessageFormat} pattern, the <code>arguments</code>
        array is then used as the argument for
        {@link java.text.MessageFormat#format(Object)}.</p>
        <p>See the language.properties for notes on the parsing of
        MessageFormat patterns.
        <p>If however, <code>arguments</code> is empty, the key
        translation is not parsed as MessageFormat pattern (That way, the
        <code>message</code> methods can be used generically.)
        <p><em>By convention</em>, the keys for MessageFormat pattern (and only
        them) contain percentage signs, followed by a number (starting from zero,
        as MessageFormat), to denote the occurrence of arguments (e.g.
        <code>JaxoIO.read%0ReadFailed=File "{0}" could not be read.</code>).
        Then:</p>
        <ul><li>In the properties file, it is clear from the key whether the value
        is a MessageFormat pattern or a plain String.
        <li>In the java source, one can see whether the actual number of arguments
        matches the expected one (e.g.
        <code>message("JaxoIO.read%0ReadFailed", new Object[] { a, b});</code>
        can be seen to be incorrect.
        </ul>
     * @param key The key to translate.
     * @param arguments An array of objects (arguments for MessageFormat).
     * @return The translated string.
     */
    public static String message(final String key, final Object[] arguments) {
        return bundleMessage(key, null, arguments);
    }

    /** Translates the given string.
     * @param key The key to translate.
     * @param bundleId An identifier for an external language bundle,
     * if null, the default JaxoDraw bundle will be used.
     * @param arguments An array of objects (arguments for MessageFormat).
     * @return The translated string.
     * @see #message(String,Object[])
     */
    public static String bundleMessage(final String key, final String bundleId, final Object[] arguments) {
        if (arguments.length == 0) {
            if (bundleId == null) {
                return translate(key);
            } else {
                return bundleTranslate(key, bundleId);
            }
        }

        final String totalKey = bundleId + "//" + key;

        MessageFormat f = messageFormats.get(totalKey);

        if (f == null) {
            if (bundleId == null) {
                f = new MessageFormat(translate(key));
            } else {
                f = new MessageFormat(bundleTranslate(key, bundleId));
            }
            messageFormats.put(totalKey, f);
        }

        return f.format(arguments);
    }

    /** Translates the given string into the current default language.
     * @param key The key to translate.
     * @return The translated string, or an empty string if key itself is empty.
     */
    public static String translate(final String key) {
        return bundleTranslate(key, null);
    }

    /** Translates the given string into the current default language.
     * @param akey The key to translate.
     * @param abundleId An identifier for an external language bundle,
     * if null, the default JaxoDraw bundle will be used.
     * @return The translated string, or an empty string if key itself is empty.
     */
    public static String bundleTranslate(final String akey, final String abundleId) {
        String key = akey;
        String bundleId = abundleId;

        if ("".equals(key)) {
            return "";
        } else {
            if (key.charAt(0) == '/') {
                key = key.substring(1);
                bundleId = null;
            }

            if (bundleId == null) {
                return languageBundle.getString(key);
            } else {
                final PluginBundle b = PLUGIN_BUNDLES.get(bundleId);

                if (b == null) {
                    JaxoLog.warn("No language bundle found for id: " + bundleId);
                    return key;
                }

                return b.getString(key);
            }
        }
    }

    /** Returns the language string for the given mode.
     * @param mode The language mode as defined in JaxoConstants.
     * @return The corresponding language string
     * or empty if mode is not a language mode.
     */
    public static String getLanguageFor(final int mode) {
        String lang = "";
        switch (mode) {
            case JaxoConstants.ENGLISH:
                lang = ENGLISH;
                break;
            case JaxoConstants.GERMAN:
                lang = GERMAN;
                break;
            case JaxoConstants.FRENCH:
                lang = FRENCH;
                break;
            case JaxoConstants.ITALIAN:
                lang = ITALIAN;
                break;
            case JaxoConstants.SPANISH:
                lang = SPANISH;
                break;
            default:
                break;
        }
        return lang;
    }

    /** Returns the languages known by this version of JaxoDraw.
     * @return The known languages.
     */
    public static String[] knownLanguages() {
        return KNOWN_LANGUAGES.clone();
    }
}
