/*****************************************************************************
 * Copyright (C) The Apache Software Foundation. All rights reserved.        *
 * ------------------------------------------------------------------------- *
 * This software is published under the terms of the Apache Software License *
 * version 1.1, a copy of which has been included with this distribution in  *
 * the LICENSE file.                                                         *
 *****************************************************************************/

package org.apache.batik.util.gui;

import java.awt.Component;
import java.awt.FlowLayout;
import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.GridLayout;
import java.awt.Insets;
import java.awt.event.ActionEvent;
import java.net.URL;
import java.util.HashMap;
import java.util.Locale;
import java.util.Map;
import java.util.MissingResourceException;
import java.util.ResourceBundle;
import java.util.StringTokenizer;

import javax.swing.AbstractAction;
import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.DefaultListModel;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JButton;
import javax.swing.JDialog;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JList;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.ListCellRenderer;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

import org.apache.batik.util.gui.resource.ActionMap;
import org.apache.batik.util.gui.resource.ButtonFactory;
import org.apache.batik.util.gui.resource.MissingListenerException;
import org.apache.batik.util.gui.resource.ResourceManager;

/**
 * This class represents a language selection dialog.
 *
 * @author <a href="mailto:stephane@hillion.org">Stephane Hillion</a>
 * @author <a href="mailto:cjolif@ilog.fr">Christophe Jolif</a>
 * @version $Id: LanguageDialog.java,v 1.6 2003/04/11 13:59:31 vhardy Exp $
 */
public class LanguageDialog extends JDialog implements ActionMap {

    /**
     * The return value if 'OK' is chosen.
     */
    public final static int OK_OPTION = 0;

    /**
     * The return value if 'Cancel' is chosen.
     */
    public final static int CANCEL_OPTION = 1;

    /**
     * The resource file name
     */
    protected final static String RESOURCES =
        "org.apache.batik.util.gui.resources.LanguageDialogMessages";

    /**
     * The resource bundle
     */
    protected static ResourceBundle bundle;

    /**
     * The resource manager
     */
    protected static ResourceManager resources;

    static {
        bundle = ResourceBundle.getBundle(RESOURCES, Locale.getDefault());
        resources = new ResourceManager(bundle);
    }

    /**
     * The map that contains the listeners
     */
    protected Map listeners = new HashMap();

    /**
     * The user languages panel.
     */
    protected Panel panel = new Panel();

    /**
     * The last return code.
     */
    protected int returnCode;

    /**
     * Creates a new LanguageDialog object.
     */
    public LanguageDialog(JFrame f) {
        super(f);
        setModal(true);
        setTitle(resources.getString("Dialog.title"));

        listeners.put("OKButtonAction",             new OKButtonAction());
        listeners.put("CancelButtonAction",         new CancelButtonAction());

        getContentPane().add(panel);
        getContentPane().add("South", createButtonsPanel());

        pack();
    }

    /**
     * Shows the dialog.
     * @return OK_CANCEL or CANCEL_OPTION.
     */
    public int showDialog() {
        show();
        return returnCode;
    }

    /**
     * Sets the user languages.
     */
    public void setLanguages(String s) {
        panel.setLanguages(s);
    }

    /**
     * Returns the user languages.
     */
    public String getLanguages() {
        return panel.getLanguages();
    }

    // ActionMap implementation ///////////////////////////////////////

    /**
     * Returns the action associated with the given string
     * or null on error
     * @param key the key mapped with the action to get
     * @throws MissingListenerException if the action is not found
     */
    public Action getAction(String key) throws MissingListenerException {
        return (Action)listeners.get(key);
    }

    /**
     * Creates the OK/Cancel buttons panel
     */
    protected JPanel createButtonsPanel() {
        JPanel  p = new JPanel(new FlowLayout(FlowLayout.RIGHT));
        ButtonFactory bf = new ButtonFactory(bundle, this);
        p.add(bf.createJButton("OKButton"));
        p.add(bf.createJButton("CancelButton"));

        return p;
    }

    /**
     * The language selection panel.
     */
    public static class Panel extends JPanel implements ActionMap {
        /**
         * The user languages list
         */
        protected JList userList;

        /**
         * The languages list
         */
        protected JList languageList;

        /**
         * The user list model
         */
        protected DefaultListModel userListModel = new DefaultListModel();

        /**
         * The language list model
         */
        protected DefaultListModel languageListModel = new DefaultListModel();

        /**
         * The AddLanguageButton.
         */
        protected JButton addLanguageButton;

        /**
         * The RemoveLanguageButton.
         */
        protected JButton removeLanguageButton;

        /**
         * The UpLanguageButton.
         */
        protected JButton upLanguageButton;

        /**
         * The DownLanguageButton.
         */
        protected JButton downLanguageButton;

        /**
         * The ClearLanguageButton.
         */
        protected JButton clearLanguageButton;

        /**
         * The map that contains the listeners
         */
        protected Map listeners = new HashMap();

        /**
         * The cached map for country icons (takes more than 2 secs.
         * to be computed).
         */
        private static Map iconMap = null;

        /**
         * Creates a new Panel object.
         */
        public Panel() {
            super(new GridBagLayout());

            initCountryIcons();

            setBorder(BorderFactory.createTitledBorder
                      (BorderFactory.createEtchedBorder(),
                       resources.getString("Panel.title")));

            listeners.put("AddLanguageButtonAction",
                          new AddLanguageButtonAction());
            listeners.put("RemoveLanguageButtonAction",
                          new RemoveLanguageButtonAction());
            listeners.put("UpLanguageButtonAction",
                          new UpLanguageButtonAction());
            listeners.put("DownLanguageButtonAction",
                          new DownLanguageButtonAction());
            listeners.put("ClearLanguageButtonAction",
                          new ClearLanguageButtonAction());

            // Initalize the lists
            userList = new JList(userListModel);
            userList.setCellRenderer(new IconAndTextCellRenderer());

            languageList = new JList(languageListModel);
            languageList.setCellRenderer(new IconAndTextCellRenderer());

            StringTokenizer st;
            st = new StringTokenizer(resources.getString("Country.list"), " ");
            while (st.hasMoreTokens()) {
                languageListModel.addElement(st.nextToken());
            }

            // Layout out the children
            ExtendedGridBagConstraints constraints =
                new ExtendedGridBagConstraints();
            constraints.insets = new Insets(5, 5, 5, 5);

            constraints.weightx = 1.0;
            constraints.weighty = 1.0;
            constraints.fill = GridBagConstraints.BOTH;

            // The languages list
            constraints.setGridBounds(0, 0, 1, 1);
            JScrollPane sp = new JScrollPane();
            sp.setBorder(BorderFactory.createCompoundBorder
                         (BorderFactory.createTitledBorder
                          (BorderFactory.createEmptyBorder(),
                           resources.getString("Languages.title")),
                          BorderFactory.createLoweredBevelBorder()));
            sp.getViewport().add(languageList);
            this.add(sp, constraints);

            languageList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
            languageList.addListSelectionListener
                (new LanguageListSelectionListener());

            // The user languages list
            constraints.setGridBounds(2, 0, 1, 1);

            JScrollPane sp2 = new JScrollPane();
            sp2.setBorder(BorderFactory.createCompoundBorder
                          (BorderFactory.createTitledBorder
                           (BorderFactory.createEmptyBorder(),
                            resources.getString("User.title")),
                           BorderFactory.createLoweredBevelBorder()));
            sp2.getViewport().add(userList);
            this.add(sp2, constraints);

            userList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
            userList.addListSelectionListener(new UserListSelectionListener());

            // The info label
            constraints.setGridBounds(0, 1, 3, 1);
            constraints.weightx = 0;
            constraints.weighty = 0;
            this.add(new JLabel(resources.getString("InfoLabel.text")),
                     constraints);

            // The buttons
            ButtonFactory bf = new ButtonFactory(bundle, this);
            JPanel p = new JPanel(new GridLayout(5, 1, 0, 3));
            p.add(addLanguageButton = bf.createJButton("AddLanguageButton"));
            addLanguageButton.setEnabled(false);
            p.add(removeLanguageButton =
                  bf.createJButton("RemoveLanguageButton"));
            removeLanguageButton.setEnabled(false);
            p.add(upLanguageButton = bf.createJButton("UpLanguageButton"));
            upLanguageButton.setEnabled(false);
            p.add(downLanguageButton = bf.createJButton("DownLanguageButton"));
            downLanguageButton.setEnabled(false);
            p.add(clearLanguageButton =
                  bf.createJButton("ClearLanguageButton"));
            clearLanguageButton.setEnabled(false);

            JPanel t = new JPanel(new GridBagLayout());
            constraints.setGridBounds(1, 0, 1, 1);
            this.add(t, constraints);

            constraints.fill = GridBagConstraints.HORIZONTAL;
            constraints.setGridBounds(0, 0, 1, 1);
            constraints.insets = new Insets(0, 0, 0, 0);
            t.add(p, constraints);

            sp2.setPreferredSize(sp.getPreferredSize());
        }

        /**
         * Allows to pre-initialize icons used by the <code>Panel</code>
         * constructor. It is not neccessary to call it and it should
         * be called only once.
         * This method is safe to be called by another thread than the
         * event thread as it doesn't manipulate Swing <code>JComponent</code>
         * instances.
         */
        public synchronized static void initCountryIcons()
        {
            // don't need to init several times...
            if (iconMap == null) {
                iconMap = new HashMap();
                StringTokenizer st;
                st = new StringTokenizer(resources.getString("Country.list"),
                                         " ");
                while (st.hasMoreTokens()) {
                    computeCountryIcon(LanguageDialog.Panel.class,
                                       st.nextToken());
                }
            }
        }

        /**
         * Returns the selected user languages.
         */
        public String getLanguages() {
            StringBuffer result = new StringBuffer();
            if (userListModel.getSize() > 0) {
                result.append(userListModel.getElementAt(0));

                for (int i = 1; i < userListModel.getSize(); i++) {
                    result.append("," + userListModel.getElementAt(i));
                }
            }
            return result.toString();
        }

        /**
         * Sets the user languages.
         */
        public void setLanguages(String str) {
            int len = userListModel.getSize();
            for (int i = 0; i < len; i++) {
                Object o = userListModel.getElementAt(0);
                userListModel.removeElementAt(0);
                int size = languageListModel.getSize();
                int n = 0;
                while (n < size) {
                    String s = (String)languageListModel.getElementAt(n);
                    if (s.compareTo(o) > 0) {
                        break;
                    }
                    n++;
                }
                languageListModel.insertElementAt(o, n);
            }

            StringTokenizer st;
            st = new StringTokenizer(str, ",");
            while (st.hasMoreTokens()) {
                String s = st.nextToken();
                userListModel.addElement(s);
                languageListModel.removeElement(s);
            }

            updateButtons();
        }

        /**
         * Updates the state of the buttons
         */
        protected void updateButtons() {
            int size = userListModel.size();
            int i    = userList.getSelectedIndex();

            boolean empty        = size == 0;
            boolean selected     = i != -1;
            boolean zeroSelected = i == 0;
            boolean lastSelected = i == size - 1;

            removeLanguageButton.setEnabled(!empty && selected);
            upLanguageButton.setEnabled(!empty && selected && !zeroSelected);
            downLanguageButton.setEnabled(!empty && selected && !lastSelected);
            clearLanguageButton.setEnabled(!empty);

            size = languageListModel.size();
            i = languageList.getSelectedIndex();

            empty    = size == 0;
            selected = i != -1;

            addLanguageButton.setEnabled(!empty && selected);
        }

        /**
         * returns the full string associated with a country code.
         */
        protected String getCountryText(String code) {
            return resources.getString(code + ".text");
        }

        /**
         * returns the icon associated with a country code.
         */
        protected Icon getCountryIcon(String code) {
            return computeCountryIcon(getClass(), code);
        }

        private static Icon computeCountryIcon(Class ref,
                                               String code) {
            ImageIcon icon = null;
            try {
                if ((icon = (ImageIcon)iconMap.get(code)) != null)
                    return icon;
                String s = resources.getString(code + ".icon");
                URL url  = ref.getResource(s);
                if (url != null) {
                    iconMap.put(code, icon = new ImageIcon(url));
                    return icon;
                }
            } catch (MissingResourceException e) {
            }
            return new ImageIcon(ref.getResource("resources/blank.gif"));
        }

        // ActionMap implementation ///////////////////////////////////////

        /**
         * Returns the action associated with the given string
         * or null on error
         * @param key the key mapped with the action to get
         * @throws MissingListenerException if the action is not found
         */
        public Action getAction(String key) throws MissingListenerException {
            return (Action)listeners.get(key);
        }

        /**
         * The action associated with the 'add' button
         */
        protected class AddLanguageButtonAction extends AbstractAction {
            public void actionPerformed(ActionEvent e) {
                int    i = languageList.getSelectedIndex();
                Object o = languageListModel.getElementAt(i);
                languageListModel.removeElementAt(i);
                userListModel.addElement(o);
                userList.setSelectedValue(o, true);
            }
        }

        /**
         * The action associated with the 'remove' button
         */
        protected class RemoveLanguageButtonAction extends AbstractAction {
            public void actionPerformed(ActionEvent e) {
                int i = userList.getSelectedIndex();
                Object o = userListModel.getElementAt(i);
                userListModel.removeElementAt(i);

                int size = languageListModel.getSize();
                int n = 0;
                while (n < size) {
                    String s = (String)languageListModel.getElementAt(n);
                    if (s.compareTo(o) > 0) {
                        break;
                    }
                    n++;
                }
                languageListModel.insertElementAt(o, n);
                languageList.setSelectedValue(o, true);
                updateButtons();
            }
        }

        /**
         * The action associated with the 'up' button
         */
        protected class UpLanguageButtonAction extends AbstractAction {
            public void actionPerformed(ActionEvent e) {
                int    i = userList.getSelectedIndex();
                Object o = userListModel.getElementAt(i);
                userListModel.removeElementAt(i);
                userListModel.insertElementAt(o, i - 1);
                userList.setSelectedIndex(i - 1);
            }
        }

        /**
         * The action associated with the 'down' button
         */
        protected class DownLanguageButtonAction extends AbstractAction {
            public void actionPerformed(ActionEvent e) {
                int    i = userList.getSelectedIndex();
                Object o = userListModel.getElementAt(i);
                userListModel.removeElementAt(i);
                userListModel.insertElementAt(o, i + 1);
                userList.setSelectedIndex(i + 1);
            }
        }

        /**
         * The action associated with the 'clear' button
         */
        protected class ClearLanguageButtonAction extends AbstractAction {
            public void actionPerformed(ActionEvent e) {
                int len = userListModel.getSize();
                for (int i = 0; i < len; i++) {
                    Object o = userListModel.getElementAt(0);
                    userListModel.removeElementAt(0);
                    int size = languageListModel.getSize();
                    int n = 0;
                    while (n < size) {
                        String s = (String)languageListModel.getElementAt(n);
                        if (s.compareTo(o) > 0) {
                            break;
                        }
                        n++;
                    }
                    languageListModel.insertElementAt(o, n);
                }
                updateButtons();
            }
        }

        /**
         * To manage selection modifications
         */
        protected class LanguageListSelectionListener
            implements ListSelectionListener {
            public void valueChanged(ListSelectionEvent e) {
                int i = languageList.getSelectedIndex();
                userList.getSelectionModel().clearSelection();
                languageList.setSelectedIndex(i);
                updateButtons();
            }
        }

        /**
         * To manage selection modifications
         */
        protected class UserListSelectionListener
            implements ListSelectionListener {
            public void valueChanged(ListSelectionEvent e) {
                int i = userList.getSelectedIndex();
                languageList.getSelectionModel().clearSelection();
                userList.setSelectedIndex(i);
                updateButtons();
            }
        }

        /**
         * To display icons and text in the lists.
         */
        protected class IconAndTextCellRenderer
            extends    JLabel
            implements ListCellRenderer {
            public IconAndTextCellRenderer() {
                this.setOpaque(true);
                this.setBorder(BorderFactory.createEmptyBorder(3, 3, 3, 3));
            }
            public Component getListCellRendererComponent(JList   list,
                                                          Object  value,
                                                          int     index,
                                                          boolean isSelected,
                                                          boolean cellHasFocus){
                String s = (String)value;
                this.setText(getCountryText(s));
                this.setIcon(getCountryIcon(s));
                this.setEnabled(list.isEnabled());
                this.setFont(list.getFont());
                if (isSelected) {
                    this.setBackground(list.getSelectionBackground());
                    this.setForeground(list.getSelectionForeground());
                } else {
                    this.setBackground(list.getBackground());
                    this.setForeground(list.getForeground());
                }
                return this;
            }
        }
    }

    /**
     * The action associated with the 'OK' button
     */
    protected class OKButtonAction extends AbstractAction {
        public void actionPerformed(ActionEvent e) {
            returnCode = OK_OPTION;
            dispose();
        }
    }

    /**
     * The action associated with the 'Cancel' button
     */
    protected class CancelButtonAction extends AbstractAction {
        public void actionPerformed(ActionEvent e) {
            returnCode = CANCEL_OPTION;
            dispose();
        }
    }
}
