/**
 *  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.awt.Component;
import java.awt.Container;
import java.awt.EventQueue;
import java.awt.Frame;
import java.awt.Window;

import java.util.Iterator;
import java.util.Locale;
import java.util.Map;
import java.util.WeakHashMap;

import javax.swing.JComponent;
import javax.swing.JFileChooser;
import javax.swing.LookAndFeel;
import javax.swing.SwingUtilities;
import javax.swing.UIManager;
import javax.swing.UIManager.LookAndFeelInfo;
import javax.swing.UnsupportedLookAndFeelException;


/** Responsible for switching the Look and Feel of JaxoDraw and the
 * Locale of GUI elements (which are closely related).
 * @since 2.0
 */
public final class JaxoLooknFeel {
    private static Map<Component, Boolean> components = new WeakHashMap<Component, Boolean>();
    private static Locale locale = JComponent.getDefaultLocale();

    /** Empty private constructor to prevent the class from being
     * explicitly instantiated.
     */
    private JaxoLooknFeel() {
        // empty on purpose
    }

    /**
     * Current locale. By default, JComponent.getDefaultLocale().
     * @return The current locale.
     */
    public static Locale getLocale() {
        return locale;
    }

    /**
     * Apply the given locale.
     * @param value The locale to apply.
     */
    public static void applyLocale(final Locale value) {
        if (!value.equals(locale)) {
            locale = value;

            JComponent.setDefaultLocale(locale);

            EventQueue.invokeLater(new Runnable() {
                    public void run() {
                        try {
                            // reset look and feel to get a fresh property lookup
                            setLookAndFeel(UIManager.getLookAndFeel().getClass()
                                                    .getName());
                            updateLocale();
                        } catch (Exception x) {
                            JaxoLog.debug(x);
                        }
                    }
                });
        }
    }

    /**
     * Set LookAndFeel to given value value,
     * and update existing windows and registered components.
     * If the given value fails, use the system LookAndFeel.
     *
     * @param lookAndFeelClassName The class name of the LAF to set.
     * @return True if the given L&F was set successfully.
     */
    public static boolean applyLookAndFeel(final String lookAndFeelClassName) {
        boolean success = true;

        try {
            setLookAndFeel(lookAndFeelClassName);

            EventQueue.invokeLater(new Runnable() {
                    public void run() {
                        updateLookAndFeel();
                    }
                });
        } catch (Exception exc) {
            success = false;
            JaxoLog.debug(exc);

            try {
                setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
            } catch (Exception x) {
                JaxoLog.debug(x);
            }
        }
        return success;
    }

    /**
     * Return a LookAndFeelInfo object for a given class name.
     *
     * @param className the name of the class.
     * @return a LookAndFeelInfo object.
     */
    public static LookAndFeelInfo info(final String className) {
        final LookAndFeelInfo[] ll = UIManager.getInstalledLookAndFeels();

        for (int i = 0; i < ll.length; i++) {
            if (className.equals(ll[i].getClassName())) {
                return ll[i];
            }
        }

        // If not known, use the 'className' as 'name'.
        return new LookAndFeelInfo(className, className);
    }

    /**
     * Safe wrapper around {@link UIManager#setLookAndFeel(String)}:
     * Set the LookAndFeel, temporarily adjusting the locale to
     * fix resource bundle lookup (default locale problems), and to
     * make JColorChooser work.
     * @param value The LAF to set.
     * @throws java.lang.ClassNotFoundException ClassNotFoundException
     * @throws java.lang.InstantiationException InstantiationException
     * @throws java.lang.IllegalAccessException IllegalAccessException
     * @throws javax.swing.UnsupportedLookAndFeelException UnsupportedLookAndFeelException
     * @see javax.swing.UIManager#setLookAndFeel(String)
     */
    public static void setLookAndFeel(final String value)
        throws ClassNotFoundException, InstantiationException,
            IllegalAccessException, UnsupportedLookAndFeelException {
        final Locale old = Locale.getDefault();
        Locale.setDefault(locale);

        try {
            UIManager.setLookAndFeel(value);
            new JFileChooser(); // this initializes the resource bundles
        } finally {
            Locale.setDefault(old);
        }
    }

    /**
     * Set the LookAndFeel, temporarily adjusting the locale to
     * fix resource bundle lookup (default locale problems), and to
     * make JColorChooser work.
     * @param value The LAF to set.
     * @throws javax.swing.UnsupportedLookAndFeelException UnsupportedLookAndFeelException
     * @see javax.swing.UIManager#setLookAndFeel(LookAndFeel)
     */
    public static void setLookAndFeel(final LookAndFeel value)
        throws UnsupportedLookAndFeelException {
        final Locale old = Locale.getDefault();
        Locale.setDefault(locale);

        try {
            UIManager.setLookAndFeel(value);
            new JFileChooser(); // this initializes the resource bundles
        } finally {
            Locale.setDefault(old);
        }
    }

    /** Update LookAndFeel of all windows and all registered components. */
    public static void updateLookAndFeel() {
        final Frame[] f = Frame.getFrames();

        for (int i = 0; i < f.length; i++) {
            updateWindow(f[i]);
        }

        for (final Iterator<Component> i = components.keySet().iterator(); i.hasNext();) {
            final Component c = i.next();

            SwingUtilities.updateComponentTreeUI(c);
        }
    }

    private static void updateWindow(final Window w) {
        SwingUtilities.updateComponentTreeUI(w);

        final Window[] c = w.getOwnedWindows();

        for (int i = 0; i < c.length; i++) {
            updateWindow(c[i]);
        }
    }

    /**
     * Register a component whose look and feel/locale is to be updated
     * automatically. This is useful only for components that are
     * not permanently part of a Window (e.g., popup menus).
     * @param c The component to register.
     */
    public static void registerComponent(final JComponent c) {
        components.put(c, Boolean.TRUE);
    }

    /**
     * Unregister a component whose look and feel/locale is to be updated
     * automatically.
     * @param c The component to unregister.
     */
    public static void unregisterComponent(final JComponent c) {
        components.remove(c);
    }

    // Update locale of all windows and registered components.
    private static void updateLocale() {
        final Frame[] f = Frame.getFrames();

        for (int i = 0; i < f.length; i++) {
            updateWindowLocale(f[i]);
        }

        for (final Iterator<Component> i = components.keySet().iterator(); i.hasNext();) {
            final Component c = i.next();

            applyLocale(c);
        }
    }

    private static void updateWindowLocale(final Window w) {
        applyLocale(w);

        final Window[] c = w.getOwnedWindows();

        for (int i = 0; i < c.length; i++) {
            updateWindowLocale(c[i]);
        }
    }

    private static void applyLocale(final Component c) {
        c.setLocale(locale);

        // most components that use the Locale at all need this
        if (c instanceof JComponent) {
            ((JComponent) c).updateUI();
        }

        if (c instanceof Container) {
            final Container p = (Container) c;
            for (int i = p.getComponentCount() - 1; i >= 0; --i) {
                applyLocale(p.getComponent(i));
            }
        }
    }
}
