/*
 * $Id$
 *
 * Copyright 1996-2008 Sun Microsystems, Inc.  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.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */
package com.sun.javatest.exec;

import java.awt.GridBagConstraints;
import java.awt.GridBagLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;

import javax.swing.Action;
import javax.swing.BorderFactory;
import javax.swing.ButtonGroup;
import javax.swing.JButton;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JMenu;
import javax.swing.JMenuItem;
import javax.swing.JPanel;
import javax.swing.JRadioButtonMenuItem;

import com.sun.javatest.TestFilter;
import com.sun.javatest.tool.ToolAction;
import com.sun.javatest.tool.UIFactory;
import com.sun.javatest.util.Debug;
import com.sun.javatest.util.DynamicArray;
import com.sun.javatest.util.OrderedTwoWayTable;

class FilterSelectionHandler {
     /**
      * Observe changes to the state of the view.  The changes to the view state
      * will generally be the result of user actions.
      */
    public interface Observer {
       /**
        * The state of the given filter has changed.
        */
	public void filterUpdated(TestFilter f);
 
       /**
        * The system is requesting a different filter.
        */
	public void filterSelected(TestFilter f);
 
	public void filterAdded(TestFilter f);
     
       /**
        * Removing the active filter will result in an exception.
        */
	public void filterRemoved(TestFilter f);
    }

    FilterSelectionHandler(FilterConfig fc, UIFactory uif) {
	this.filterConfig = fc;
	this.uif = uif;

	listener = new Listener();

	filterConfig.addObserver(listener);
    }

    public void addObserver(Observer o) {
	if (o == null)
	    return;

	if (obs == null)
	    obs = new Observer[0];

	obs = (Observer[])DynamicArray.append(obs, o);
    }

    public void removeObserver(Observer o) {
	obs = (Observer[])DynamicArray.remove(obs, o);
    }

    /**
     * Get the gui component that allows the user to select and configure
     * the active filter.  This should only be called once for each instance.
     * Only the last instance of the selector widget will be used.
     */
    JComponent getFilterSelector() {
	// may need to start offering the caller a way to control layout
	// fill and anchor settings...
	JPanel panel = uif.createPanel("fconfig.sp", new GridBagLayout(), false);
	GridBagConstraints gbc = new GridBagConstraints();
	gbc.weightx = 1.0;
	gbc.gridx = 0;
	gbc.gridy = 0;
	gbc.fill = GridBagConstraints.NONE;
	gbc.anchor = GridBagConstraints.NORTHWEST;

	selectBox = uif.createLiteralChoice("fconfig.box", filterConfig.getFilters());
	selectBox.addActionListener(listener);
	selectBox.setRenderer(RenderingUtilities.createFilterListRenderer());
	uif.setAccessibleName(selectBox, "fconfig.box");
	panel.add(selectBox, gbc);

	gbc.gridx = 1;

	configButton = uif.createButton("fconfig.config");
	// make button smaller, with minimal border
	configButton.setBorder(BorderFactory.createCompoundBorder(
	    BorderFactory.createEtchedBorder(),
	    BorderFactory.createEmptyBorder(0,3,0,3)));
	configButton.addActionListener(listener);
	configButton.setMaximumSize(configButton.getPreferredSize());
	uif.setAccessibleName(configButton, "fconfig.config");
	gbc.insets.left = 11;
	gbc.weightx = 0.0;
	panel.add(configButton, gbc);
	panel.setMaximumSize(panel.getPreferredSize());

	// make sure a filter is selected
	if (activeFilter == null) {
	    TestFilter[] filters = filterConfig.getFilters();
	    if (filters != null && filters.length > 0)
		setFilter(filters[0]);
	}
	else {
	    selectBox.setSelectedItem(activeFilter);
	}

	configButton.setEnabled((activeFilter instanceof ConfigurableTestFilter));

	return panel;
    }

    /**
     * Get the filtering submenu.
     */
    synchronized JMenu getFilterMenu() {
	menuGroup = new ButtonGroup();
	filterMenuTable = new OrderedTwoWayTable();
	
	Action showEditorAction = new ToolAction(uif, "fconfig.submenu.edit") {
	    public void actionPerformed(ActionEvent e) {
		filterConfig.showEditorDialog(getActiveFilter());
	    }
	};

	editMenu = uif.createMenu("fconfig.submenu");

	TestFilter[] filters = filterConfig.getFilters();
	for (int i = 0; i < filters.length; i++)
	    addToMenu(filters[i], -1);

	// make sure a filter is selected
	// addToMenu() also tries to do this
	if (activeFilter == null) {
	    if (filters != null && filters.length > 0)
		setFilter(filters[0]);
	}
	
	// radio set of filter names
	editMenu.addSeparator();
	editMenu.add(uif.createMenuItem(showEditorAction));
	updateMenu();

	return editMenu;

    }

    /**
     * Set the active filter.
     * If the name cannot be matched, the request will be ignored.
     *
     * @param name Should never be null.
     */
    synchronized void setFilter(String name) {
	setFilter(filterConfig.getFilter(name));
    }

    /**
     * Set the active filter.
     * If the filter supplied is part of the known set of filters, it
     * becomes the active filter and all observers are notified.
     *
     * @param f A null filter will be ignored.
     */
    synchronized void setFilter(TestFilter f) {
	if (f == null)
	    return;

	if (filterConfig.contains(f)) {
	    activeFilter = f;

	    if (selectBox != null)
		selectBox.setSelectedItem(activeFilter);

	    if (configButton != null)
		configButton.setEnabled((f instanceof ConfigurableTestFilter));

	    updateMenu();

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

    /**
     * Return the filter currently selected.
     *
     * @return Null if no filter is active.
     */
    synchronized TestFilter getActiveFilter() {
	return activeFilter;
    }

    /**
     * Hint to update any meta info about the filter if necessary.
     */
    void updateFilterMetaInfo(TestFilter f) {
	// update the drop down box if needed
	if (activeFilter == f && selectBox != null)
	    selectBox.repaint();

	// update menu item if needed
	// could check editMenu, filterMenuTable or menuGroup
	if (editMenu != null) {
	    int index = filterMenuTable.getKeyIndex(f);

	    if (index >= 0) {
		JMenuItem jmi = (JMenuItem)(filterMenuTable.getValueAt(index));
		jmi.setText(f.getName());
	    }
	}
    }

    /**
     * Add the filter to the list of filters in the menu.
     * No action is taken if the menu system is not initilized.  No
     * checking for duplicate insertions occurs.
     * @param location Position to place the new item at.  -1 indicates
     *        indifference.
     * @param f The test filter to add.
     */
    private synchronized void addToMenu(TestFilter f, int location) {
	if (editMenu == null)
	    return;

	// guess a mnemonic
	int mne = 0;
	for (int i = 0; i < editMenu.getItemCount(); i++) {
	    JMenuItem mi = editMenu.getItem(i);
	    if (mi != null) {
                int itemMne = mi.getMnemonic();
                if (itemMne >='0' && itemMne <= '9')
                    mne = Math.max(mne, itemMne);
            }
	}

	if (mne == 0)
	    mne = '0';
	else if (mne < '9')
	    mne = mne + 1;
	else
	    mne = 0;
	
	String text = (mne == 0 ? ' ' : (char) mne) + " " + f.getName();
	JRadioButtonMenuItem b = new JRadioButtonMenuItem(text);
	b.setName(f.getName());
	if (mne != 0)
	    b.setMnemonic(mne);
	b.getAccessibleContext().setAccessibleDescription(f.getDescription());
	b.addActionListener(listener);
	menuGroup.add(b);
	
	filterMenuTable.put(f, b);

	if (location < 0)	// no preference, insert as last filter
	    editMenu.insert(b, menuGroup.getButtonCount()-1);
	else
	    editMenu.insert(b, location);

	if (f == activeFilter)
	    b.setEnabled(true);
    }

    /**
     * This should never be used on an active filter.
     * No action is taken if the menu system is not initilized.
     */
    private synchronized void removeFromMenu(TestFilter f) {
	if (editMenu == null)
	    return;

	int where = filterMenuTable.getKeyIndex(f);
	if (where != -1) {	// found, continue
	    JRadioButtonMenuItem mi = (JRadioButtonMenuItem)(filterMenuTable.getValueAt(where));
	    editMenu.remove(mi);
	    filterMenuTable.remove(where);
	    menuGroup.remove(mi);
	}
    }

    /**
     * Currently just selects the correct active filter.
     */
    private void updateMenu() {
	if (editMenu == null)
	    return;

	// select the right item in the menu
	int where = filterMenuTable.getKeyIndex(activeFilter);
	if (where != -1) {
	    JRadioButtonMenuItem mi = (JRadioButtonMenuItem)(filterMenuTable.getValueAt(where));
	    mi.setSelected(true);
	}

    }

    private FilterConfig filterConfig;
    private UIFactory uif;

    private TestFilter activeFilter;
    private JComboBox selectBox;
    private JButton configButton;
    private JMenu editMenu;
    private ButtonGroup menuGroup;

    private Listener listener;
    private Observer[] obs = new Observer[0];
    private OrderedTwoWayTable filterMenuTable;	    // filter, menu item
    private static boolean debug = Debug.getBoolean(FilterConfig.class);

    class Listener implements ActionListener, FilterConfig.Observer {
	public void actionPerformed(ActionEvent e) {
	    Object source = e.getSource();
	    if (source == selectBox) {
		String action = e.getActionCommand();
		TestFilter vf = (TestFilter)(selectBox.getSelectedItem());

		if (vf == getActiveFilter()) {
		    if (debug)
			Debug.println("FC - keeping filter");

		    return;
		}
		else {
		    if (debug)
			Debug.println("FC - changing filter");

		    setFilter(vf);
		}
	    }
	    else if (source == configButton) {
		TestFilter vf = (TestFilter)(selectBox.getSelectedItem());
		filterConfig.showEditorDialog(vf);
	    }
	    else if (source instanceof JRadioButtonMenuItem) {
		// a filter menu item
		int which = filterMenuTable.getValueIndex(source);
		if (which != -1) {
		    TestFilter f = (TestFilter)(filterMenuTable.getKeyAt(which));
		    setFilter(f);
		}
	    }
	}

	public void filterUpdated(TestFilter f) {
	    if (obs == null)    // this really should not happen
		return;

	    if (activeFilter == f) {
		for (int i = 0; i < obs.length; i++)
		    obs[i].filterUpdated(f);
	    }
	}
 
	public void filterAdded(TestFilter f) {
	    // add to the list box
	    if (selectBox != null)
		selectBox.addItem(f);

	    // update the menu
	    addToMenu(f, -1);

	    // tell people about the addition
	    // obs should never be null
	    for (int i = 0; i < obs.length; i++)
		obs[i].filterAdded(f);
	}
     
       /**
        * Removing the active filter will result in an exception.
        */
	public void filterRemoved(TestFilter f) {
	    // remove from list box
	    if (selectBox != null)
		selectBox.removeItem(f);

	    // update the menu
	    removeFromMenu(f);

	    // tell people about the removal
	    // obs should never be null
	    for (int i = 0; i < obs.length; i++)
		obs[i].filterRemoved(f);
	}
    }
}

