/*

 ============================================================================
                   The Apache Software License, Version 1.1
 ============================================================================

 Copyright (C) 1999-2003 The Apache Software Foundation. All rights reserved.

 Redistribution and use in source and binary forms, with or without modifica-
 tion, are permitted provided that the following conditions are met:

 1. Redistributions of  source code must  retain the above copyright  notice,
    this list of conditions and the following disclaimer.

 2. Redistributions in binary form must reproduce the above copyright notice,
    this list of conditions and the following disclaimer in the documentation
    and/or other materials provided with the distribution.

 3. The end-user documentation included with the redistribution, if any, must
    include  the following  acknowledgment:  "This product includes  software
    developed  by the  Apache Software Foundation  (http://www.apache.org/)."
    Alternately, this  acknowledgment may  appear in the software itself,  if
    and wherever such third-party acknowledgments normally appear.

 4. The names "Batik" and  "Apache Software Foundation" must  not  be
    used to  endorse or promote  products derived from  this software without
    prior written permission. For written permission, please contact
    apache@apache.org.

 5. Products  derived from this software may not  be called "Apache", nor may
    "Apache" appear  in their name,  without prior written permission  of the
    Apache Software Foundation.

 THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES,
 INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
 FITNESS  FOR A PARTICULAR  PURPOSE ARE  DISCLAIMED.  IN NO  EVENT SHALL  THE
 APACHE SOFTWARE  FOUNDATION  OR ITS CONTRIBUTORS  BE LIABLE FOR  ANY DIRECT,
 INDIRECT, INCIDENTAL, SPECIAL,  EXEMPLARY, OR CONSEQUENTIAL  DAMAGES (INCLU-
 DING, BUT NOT LIMITED TO, PROCUREMENT  OF SUBSTITUTE GOODS OR SERVICES; LOSS
 OF USE, DATA, OR  PROFITS; OR BUSINESS  INTERRUPTION)  HOWEVER CAUSED AND ON
 ANY  THEORY OF LIABILITY,  WHETHER  IN CONTRACT,  STRICT LIABILITY,  OR TORT
 (INCLUDING  NEGLIGENCE OR  OTHERWISE) ARISING IN  ANY WAY OUT OF THE  USE OF
 THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

 This software  consists of voluntary contributions made  by many individuals
 on  behalf of the Apache Software  Foundation. For more  information on the
 Apache Software Foundation, please see <http://www.apache.org/>.

*/

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.7 2003/08/08 11:39:27 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();
        }
    }
}
