/*
 * $Id$
 *
 * Copyright (c) 1996, 2011, Oracle and/or its affiliates. All rights reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Oracle designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Oracle in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
 * or visit www.oracle.com if you need additional information or have any
 * questions.
 */
package com.sun.interview.wizard;

import java.awt.BorderLayout;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.lang.reflect.Array;
import java.text.MessageFormat;
import javax.swing.BorderFactory;
import javax.swing.DefaultListCellRenderer;
import javax.swing.DefaultListModel;
import javax.swing.JButton;
import javax.swing.JList;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JToolBar;
import javax.swing.ListSelectionModel;
import javax.swing.event.ListDataListener;
import javax.swing.event.ListSelectionEvent;
import javax.swing.event.ListSelectionListener;

/**
 * A component that displays an editable list of values.
 */
public class EditableList extends JPanel
{
    /**
     * Create an EditableList.
     * @param uiKey A string used as the base of a key to look up resource values
     * for this item.
     * @param items An array of strings to display as initial values in the list.
     */
    public EditableList(String uiKey, Object[] items) {
	setLayout(new BorderLayout());
	setName(uiKey);
	setFocusable(false);

	listModel = new DefaultListModel(); // need to force the type of model
	if (items != null) {
	    for (int i = 0; i < items.length; i++) 
		listModel.addElement(items[i]);
	}
	list = new JList(listModel);
	list.setName(uiKey);
	list.setToolTipText(i18n.getString(uiKey + ".tip"));
	list.getAccessibleContext().setAccessibleName(list.getName());
	list.getAccessibleContext().setAccessibleDescription(list.getToolTipText());
	list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
	list.setCellRenderer(renderer);
	list.addListSelectionListener(listener);
	list.addMouseListener(listener);
	list.setVisibleRowCount(5);

	JScrollPane sp = new JScrollPane(list);
	sp.setName(uiKey + ".sp");
	sp.setFocusable(false);
	add(sp, BorderLayout.CENTER);

	JToolBar bar = new JToolBar(JToolBar.VERTICAL);
	bar.setName(uiKey + "bar");
	bar.setFocusable(false);
	bar.setFloatable(false);
	bar.add(addBtn  = createButton("elst.add"));
	bar.add(removeBtn = createButton("elst.remove"));
	bar.add(upBtn = createButton("elst.up"));
	bar.add(downBtn = createButton("elst.down"));
	add(bar, BorderLayout.EAST);
	updateButtons();
	setBorder(BorderFactory.createEtchedBorder());
    }
    
    public int getItemCount() {
	return listModel.getSize();
    }
    
    public Object getItem(int index) {
	return listModel.elementAt(index);
    }

    /**
     * Get the set of items in the list.
     * @return the set of items currently in the list
     */
    public Object[] getItems() {
	return listModel.toArray();
    }

    /**
     * Get the items currently in the list, in an array of a specific type.
     * @param c the component type of the array to be returned
     * @return an array containing the items currently in the list
     */
    public Object[] getItems(Class c) {
	Object[] items = (Object[]) (Array.newInstance(c, listModel.size()));
	listModel.copyInto(items);
	return items;
    }

    public int getSelectedIndex() {
	return list.getSelectedIndex();
    }

    public Object getSelectedItem() {
	return list.getSelectedValue();
    }

    public void setSelectedItem(Object item) {
	list.setSelectedValue(item, true);
    }

    public void addListDataListener(ListDataListener l) {
	listModel.addListDataListener(l);
    }

    public void removeListDataListener(ListDataListener l) {
	listModel.removeListDataListener(l);
    }

    /**
     * Specify whether or not duplicates should be allowed in the list.
     * @param b true if duplicates should be allowed, and false otherwise
     * @see #isDuplicatesAllowed
     */
    public void setDuplicatesAllowed(boolean b) {
	duplicatesAllowed = b;
    }

    /**
     * Check whether or not duplicates should be allowed in the list.
     * @return true if duplicates should be allowed, and false otherwise
     * @see #setDuplicatesAllowed
     */
    public boolean isDuplicatesAllowed() {
	return duplicatesAllowed;
    }

    protected Object getDisplayValue(Object item) {
	return item;
    }

    /**
     * Invoked to get a new item to put in the list, when the user clicks
     * the "Add" button". The default is to show an input dialog to allow
     * the user to type in a new string. Subtypes may override this method
     * to provide other ways of specifying items to be added, such as a
     * file chooser.
     * @return an object to be added to the list, or null if no object
     * to be added.
     */
    protected Object getNewItem() {
	return JOptionPane.showInputDialog(this, i18n.getString("elst.add.title"));
    }

    /**
     * Invoked to get a new item to replace an existing item in the list.
     * The default is to show an input dialog to allow
     * the user to type in a new string. Subtypes may override this method
     * to provide other ways of specifying items to be added, such as a
     * file chooser.
     * @param the item to be replaced in the list
     * @return an object to replace the old item the list, or null if no
     * replacement should occur.
     */
    protected Object getNewItem(Object oldItem) {
	return JOptionPane.showInputDialog(this, i18n.getString("elst.change.title"), oldItem);
    }

    protected void itemsChanged() {
    }

    protected void selectedItemChanged() {
    }

    protected void insertItem() {
	Object newItem = getNewItem();

	if (!duplicatesAllowed && listModel.contains(newItem)) {
	    showDuplicateError(newItem);
	    return;
	}
	    
	if (newItem != null) {
	    if (list.isSelectionEmpty()) 
		listModel.addElement(newItem);
	    else 
		listModel.add(1 + list.getSelectedIndex(), newItem);
	    list.setSelectedValue(newItem, true);
	}
    }

    protected void removeSelectedItem() {
	if (!list.isSelectionEmpty()) 
	    listModel.remove(list.getSelectedIndex());
    }

    protected void moveSelectedItemUp() {
	if (!list.isSelectionEmpty()) {
	    int i = list.getSelectedIndex();
	    if (i > 0) {
		swap(i, i - 1);
		list.setSelectedIndex(i - 1);
	    }
	}
    }

    protected void moveSelectedItemDown() {
	if (!list.isSelectionEmpty()) {
	    int i = list.getSelectedIndex();
	    if (i + 1 < listModel.size()) {
		swap(i, i + 1);
		list.setSelectedIndex(i + 1);
	    }
	}
    }

    protected void editItem(int index) {
	Object newItem = getNewItem(listModel.getElementAt(index));

	if (!duplicatesAllowed && listModel.contains(newItem)) {
	    showDuplicateError(newItem);
	    return;
	}

	if (newItem != null)
	    listModel.set(index, newItem);
    }


    protected JButton createButton(String uiKey) {
	JButton b = new JButton(i18n.getString(uiKey + ".btn"));
	b.setName(uiKey);
	b.setToolTipText(i18n.getString(uiKey + ".tip"));
	b.setMnemonic(i18n.getString(uiKey + ".mne").charAt(0));
	// set max size so button can grow within toolbar
	b.setMaximumSize(new Dimension(Integer.MAX_VALUE, Integer.MAX_VALUE));
	b.addActionListener(listener);
	return b;
    }

    private void showDuplicateError(Object item) {
	String text = MessageFormat.format(i18n.getString("elst.duplicate.text"),
					   new Object[] { getDisplayValue(item) });

	String title = i18n.getString("elst.duplicate.title");

	JOptionPane.showMessageDialog(this, 
				      text, 
				      title,
				      JOptionPane.INFORMATION_MESSAGE);
    }

    protected class Renderer
	extends DefaultListCellRenderer {
	public Component getListCellRendererComponent(JList list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
	    return super.getListCellRendererComponent(list, 
						      getDisplayValue(value), 
						      index, 
						      isSelected, 
						      cellHasFocus);
	}
    }

    protected class Listener 
	implements ActionListener, ListSelectionListener, MouseListener {
	// ActionListener, for add, remove, up, down buttons
	public void actionPerformed(ActionEvent e) {
	    Object src = e.getSource();
	    if (src == addBtn) {
		insertItem();
	    }
	    else if (src == removeBtn) {
		removeSelectedItem();
	    }
	    else if (src == upBtn) {
		moveSelectedItemUp();
	    }
	    else if (src == downBtn) {
		moveSelectedItemDown();
	    }
	    itemsChanged();
	    updateButtons();
	}

	// ListSelectionListener, to track list selection changes
	public void valueChanged(ListSelectionEvent e) {
	    selectedItemChanged();
	    updateButtons();
	}    

	// MouseListener, to react to double click in list
	public void mouseClicked(MouseEvent e) {
	    if (e.getClickCount() == 2) {
		int index = list.locationToIndex(e.getPoint());
                if(index != -1)
                    editItem(index);
	    }
	}
	 
	public void mouseEntered(MouseEvent e) { }
	public void mouseExited(MouseEvent e) { }
	public void mousePressed(MouseEvent e) { }
	public void mouseReleased(MouseEvent e) { }
    }

    protected void updateButtons() {
	if (list.isSelectionEmpty()) {
	    removeBtn.setEnabled(false);
	    upBtn.setEnabled(false);
	    downBtn.setEnabled(false);
	}
	else {
	    removeBtn.setEnabled(true);
	    int i = list.getSelectedIndex();
	    upBtn.setEnabled(i > 0);
	    downBtn.setEnabled((i + 1 < listModel.size()));
	}		
    }

	
    private void swap(int i1, int i2) {
	Object o1 = listModel.elementAt(i1); 
	Object o2 = listModel.elementAt(i2); 
	listModel.set(i1, o2);
	listModel.set(i2, o1);
    }
    
    protected Listener createListener() {
        return new Listener();
    }
    
    protected Renderer createRenderer() {
        return new Renderer();
    }

    protected boolean duplicatesAllowed;
    protected Listener listener = createListener();
    protected Renderer renderer = createRenderer();
    protected DefaultListModel listModel;
    protected JList list;
    protected JButton addBtn;
    protected JButton removeBtn;
    protected JButton upBtn;
    protected JButton downBtn;

    private static final I18NResourceBundle i18n = I18NResourceBundle.getDefaultBundle();
}
