/**
 *#########################################################################
 *
 * A component of the Gatherer application, part of the Greenstone digital
 * library suite from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * <BR><BR>
 *
 * Author: John Thompson, Greenstone Digital Library, University of Waikato
 *
 * <BR><BR>
 *
 * Copyright (C) 1999 New Zealand Digital Library Project
 *
 * <BR><BR>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * <BR><BR>
 *
 * This program 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 for more details.
 *
 * <BR><BR>
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 *########################################################################
 */
package org.greenstone.gatherer.gui.metaaudit;

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import javax.swing.*;
import org.greenstone.gatherer.Configuration;
import org.greenstone.gatherer.Dictionary;
import org.greenstone.gatherer.Gatherer;
import org.greenstone.gatherer.gui.GLIButton;
import org.greenstone.gatherer.gui.SimpleMenuBar;
import org.greenstone.gatherer.gui.ModalDialog;

/** The autofilter concept comes from Microsoft Excel spreadsheets that use autofilters to filter to the sheet. When you click on the heading of a column, a new prompt allows you to specify what filter should be acting apon the selected column. Any new filter is conjoined with any previous filters to provide a sheet containing only rows that match all current filters. Each column must also provide an indicator for determining if a filter is set (in this case a special icon) and and a method for removing a filter (use the clear filter button within the autofilter dialog). Having recently discovered that most JVMs aren't very good at recoving memory used by dialog and frame windows, special care must be made to deallocate all references properly, as a user may open several dozen autofilter prompts over the lifetime of a session.
 * @author John Thompson, Greenstone Digital Library, University of Waikato
 * @version 2.3
 */
public final class AutofilterDialog
    extends ModalDialog {
    /** The filter being edited. */
    private Autofilter filter;
    /** A reference to ourselves so our inner classes can reference us. */
    private AutofilterDialog self;

    private boolean cancelled;
    /** The value returned from the filter dialog prompt. Used to determine if a prompt was set or unset, and what subsequent action to take. */
    private byte return_value = 0;
    /** The button used to cancel the prompt. */
    private JButton cancel_button = null;
    private JButton remove_button = null;
    private JButton set_button = null;
    /** Used to specify that the given filters should be applied conjunctly. */
    private JRadioButton and_radiobutton = null;
    /** The check box used to specify whether the first filter is case sensitive. */
    private JCheckBox first_case = null;
    /** Used to disable the second filter. */
    private JRadioButton none_radiobutton = null;
    /** Used to specify that the given filters should be applied disjunctly. */
    private JRadioButton or_radiobutton = null;
    /** The check box used to specify whether the second filter is case sensitive. */
    private JCheckBox second_case = null;
    /** Used to specify the order of the resulting set: Ascending or Descending. */
    private JRadioButton ascending_radiobutton = null;
    private JRadioButton descending_radiobutton = null;
    /** The method used to match the first filter: Contains, Doesn't contain etc. */
    private JComboBox first_method = null;
    /** The value to be matched for the first filter. */
    private JComboBox first_value = null;
    /** The method used to match the first filter. Options as for the first method. */
    private JComboBox second_method = null;
    /** The value to be matched for the second filter. */
    private JComboBox second_value = null;
    /** Used for the most basic filter where an Equals, Case sensitive method is automatically used. */
    private JComboBox value = null;
    /** The label which displays the name of the currently selected column (the column that will be associated with the autofilter this dialog produces). */
    private JLabel name;
    /** The autofilter prompt contains two different panes, one for basic functionality and one for advanced. This containiner component is used to allow access to each via a 'tabbed' interface. */
    private JTabbedPane control = null;
    /** A reference back to the MetaAudit dialog that spawned this prompt. Used to make sure that any open dialog window is always in front of the audit pane. */
    private MetaAuditFrame dialog;
    /** The default size for the autofilter control. */
    static final private Dimension SIZE = new Dimension(640, 245);

    /** Constructor.
     * @param dialog A reference to the <strong>MetaAuditFrame</strong> that spawned this dialog.
     * @see org.greenstone.gatherer.gui.metaaudit.Autofilter
     * @see org.greenstone.gatherer.gui.metaaudit.AutofilterDialog.ButtonListener
     * @see org.greenstone.gatherer.gui.metaaudit.AutofilterDialog.CheckListener
     */
    public AutofilterDialog(MetaAuditFrame dialog) {
	super(Gatherer.g_man);
	this.dialog = dialog;
	this.self = this;
        this.setComponentOrientation(Dictionary.getOrientation());
	setModal(true);
	setJMenuBar(new SimpleMenuBar("reviewingmetadata")); 
	setSize(SIZE);
	setTitle(Dictionary.get("Autofilter.Title"));
	
	// Creation
	JPanel content_pane = (JPanel) getContentPane();
        content_pane.setComponentOrientation(Dictionary.getOrientation());
	JPanel name_pane = new JPanel();
        name_pane.setComponentOrientation(Dictionary.getOrientation());
	JLabel name_label = new JLabel(Dictionary.get("Autofilter.Name"));
        name_label.setComponentOrientation(Dictionary.getOrientation());
	JTextField name_template = new JTextField();
        name_template.setComponentOrientation(Dictionary.getOrientation());
	name = new JLabel();
        name.setComponentOrientation(Dictionary.getOrientation());
	name.setBorder(name_template.getBorder());
	control = new JTabbedPane();
        control.setComponentOrientation(Dictionary.getOrientation());
	JPanel value_pane = new JPanel();
        value_pane.setComponentOrientation(Dictionary.getOrientation());
	JPanel inner_value_pane = new JPanel();
        inner_value_pane.setComponentOrientation(Dictionary.getOrientation());
	JLabel value_label = new JLabel(Dictionary.get("Autofilter.eqeq"));
        value_label.setComponentOrientation(Dictionary.getOrientation());
	value = new JComboBox();
        value.setComponentOrientation(Dictionary.getOrientation());
	value.setOpaque(false);
	value.setEditable(false);
	JPanel custom_pane = new JPanel();
        custom_pane.setComponentOrientation(Dictionary.getOrientation());

	JPanel first_pane = new JPanel();
        first_pane.setComponentOrientation(Dictionary.getOrientation());
	first_method = new JComboBox();
        first_method.setComponentOrientation(Dictionary.getOrientation());
	first_method.setOpaque(false);
	first_value = new JComboBox();
        first_value.setComponentOrientation(Dictionary.getOrientation());
	first_value.setOpaque(false);
	first_value.setEditable(true);
	first_value.addItem("");
	first_value.setSelectedItem("");
	first_case = new JCheckBox(Dictionary.get("Autofilter.Case_Sensitive"));
        first_case.setComponentOrientation(Dictionary.getOrientation());
	JPanel operator_pane = new JPanel();
        operator_pane.setComponentOrientation(Dictionary.getOrientation());
	JLabel operator_label = new JLabel(Dictionary.get("Autofilter.Operator"));
        operator_label.setComponentOrientation(Dictionary.getOrientation());
	ButtonGroup operator_group = new ButtonGroup();        
	and_radiobutton = new JRadioButton(Dictionary.get("Autofilter.AND"));
        and_radiobutton.setComponentOrientation(Dictionary.getOrientation());
	and_radiobutton.setOpaque(false);
	none_radiobutton = new JRadioButton(Dictionary.get("Autofilter.None"));
        none_radiobutton.setComponentOrientation(Dictionary.getOrientation());
	none_radiobutton.setOpaque(false);
	none_radiobutton.setSelected(true);
	or_radiobutton = new JRadioButton(Dictionary.get("Autofilter.OR"));
        or_radiobutton.setComponentOrientation(Dictionary.getOrientation());
	or_radiobutton.setOpaque(false);
	operator_group.add(none_radiobutton);
	operator_group.add(and_radiobutton);
	operator_group.add(or_radiobutton);

	JPanel second_pane = new JPanel();
        second_pane.setComponentOrientation(Dictionary.getOrientation());
	second_method = new JComboBox();
        second_method.setComponentOrientation(Dictionary.getOrientation());
	second_method.setOpaque(false);
	second_method.setEnabled(false);
	second_value = new JComboBox();
        second_value.setComponentOrientation(Dictionary.getOrientation());
	second_value.setOpaque(false);
	second_value.setEditable(true);
	second_value.setEnabled(false);
	second_value.addItem("");
	second_value.setSelectedItem("");
	second_case = new JCheckBox(Dictionary.get("Autofilter.Case_Sensitive"));
        second_case.setComponentOrientation(Dictionary.getOrientation());
	JPanel lower_pane = new JPanel();
        lower_pane.setComponentOrientation(Dictionary.getOrientation());
	JPanel order_pane = new JPanel();
        order_pane.setComponentOrientation(Dictionary.getOrientation());
	order_pane.setToolTipText(Dictionary.get("Autofilter.Order_Tooltip"));
	JLabel order_label = new JLabel(Dictionary.get("Autofilter.Order"));
	order_label.setComponentOrientation(Dictionary.getOrientation());
	ButtonGroup order_group = new ButtonGroup();
	ascending_radiobutton = new JRadioButton(Dictionary.get("Autofilter.Ascending"));
        ascending_radiobutton.setComponentOrientation(Dictionary.getOrientation());
	ascending_radiobutton.setOpaque(false);
	ascending_radiobutton.setSelected(true);
	descending_radiobutton = new JRadioButton(Dictionary.get("Autofilter.Descending"));
        descending_radiobutton.setComponentOrientation(Dictionary.getOrientation());
	descending_radiobutton.setOpaque(false);
	order_group.add(ascending_radiobutton);
	order_group.add(descending_radiobutton);

	// Assign values to method comboboxes.
	for(int i = 0; i < Autofilter.METHOD_LIST.length; i++) {
	    first_method.addItem(Dictionary.get(Autofilter.METHOD_LIST[i]));
	    second_method.addItem(Dictionary.get(Autofilter.METHOD_LIST[i]));
	}
	JPanel button_pane = new JPanel();
        button_pane.setComponentOrientation(Dictionary.getOrientation());
	cancel_button = new GLIButton(Dictionary.get("General.Cancel"), Dictionary.get("General.Pure_Cancel_Tooltip"));
	remove_button = new GLIButton(Dictionary.get("Autofilter.Remove"), Dictionary.get("Autofilter.Remove_Tooltip"));
	set_button = new GLIButton(Dictionary.get("Autofilter.Set"), Dictionary.get("Autofilter.Set_Tooltip"));
	
	// Connection
	and_radiobutton.addActionListener(new CheckListener(true));
	none_radiobutton.addActionListener(new CheckListener(false));
	or_radiobutton.addActionListener(new CheckListener(true));
	cancel_button.addActionListener(new ButtonListener()); // It returns any currently set filter
	remove_button.addActionListener(new ButtonListener(false));
	set_button.addActionListener(new ButtonListener(true));

	// Layout		  
	name_label.setBorder(BorderFactory.createEmptyBorder(0,0,0,5));

	name_pane.setBorder(BorderFactory.createEmptyBorder(0,0,5,0));
	name_pane.setLayout(new BorderLayout());
	name_pane.add(name_label, BorderLayout.LINE_START);
	name_pane.add(name, BorderLayout.CENTER);

	value_label.setBorder(BorderFactory.createEmptyBorder(0,0,0,5));

	inner_value_pane.setLayout(new BorderLayout());
	inner_value_pane.add(value_label, BorderLayout.LINE_START);
	inner_value_pane.add(value, BorderLayout.CENTER);

	value_pane.setBorder(BorderFactory.createEmptyBorder(5,10,5,10));
	value_pane.setLayout(new BorderLayout());
	value_pane.add(inner_value_pane, BorderLayout.NORTH);

	first_pane.setLayout(new BorderLayout());
	first_pane.add(first_method, BorderLayout.LINE_START);
	first_pane.add(first_value, BorderLayout.CENTER);
	first_pane.add(first_case, BorderLayout.LINE_END);

	operator_pane.setLayout(new GridLayout(1,4));
	operator_pane.add(operator_label);
	operator_pane.add(none_radiobutton);
	operator_pane.add(and_radiobutton);
	operator_pane.add(or_radiobutton);

	second_pane.setLayout(new BorderLayout());
	second_pane.add(second_method, BorderLayout.LINE_START);
	second_pane.add(second_value, BorderLayout.CENTER);
	second_pane.add(second_case, BorderLayout.LINE_END);

	order_pane.setBorder(BorderFactory.createEmptyBorder(5,0,0,0));
	order_pane.setLayout(new GridLayout(1,2));
	order_pane.add(order_label);
	order_pane.add(ascending_radiobutton);
	order_pane.add(descending_radiobutton);

	custom_pane.setBorder(BorderFactory.createEmptyBorder(2,2,2,2));
	custom_pane.setLayout(new GridLayout(3,1));
	custom_pane.add(first_pane);
	custom_pane.add(operator_pane);
	custom_pane.add(second_pane);

	control.add(Dictionary.get("Autofilter.Filter_By_Value"), value_pane);
	control.add(Dictionary.get("Autofilter.Custom_Filter"), custom_pane);

	button_pane.setBorder(BorderFactory.createEmptyBorder(5,0,0,0));
	button_pane.setLayout(new GridLayout(1, 3));
	button_pane.add(set_button);
	button_pane.add(remove_button);
	button_pane.add(cancel_button);

	lower_pane.setLayout(new BorderLayout());
	lower_pane.add(order_pane, BorderLayout.CENTER);
	lower_pane.add(button_pane, BorderLayout.SOUTH);

	content_pane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
	content_pane.setLayout(new BorderLayout());
	content_pane.add(name_pane, BorderLayout.NORTH);
	content_pane.add(control, BorderLayout.CENTER);
	content_pane.add(lower_pane, BorderLayout.SOUTH);

	Dimension screen_size = Configuration.screen_size;
	setLocation((screen_size.width - SIZE.width) / 2, (screen_size.height - SIZE.height) / 2);
	screen_size = null;
    }

    public boolean cancelled() {
	return cancelled;
    }

    /** Destructor. */
    public void destroy() {
	dispose();
	first_case = null;
	second_case = null;
	first_method = null;
	first_value = null;
	second_method = null;
	second_value = null;
	value = null;
	control = null;
	dialog = null;
	self = null;
	name = null;
    }
    /** Display the modal dialog box, allowing the user to define the filter. When the user presses one of the buttons, dispose and return to the caller providing an indication of which button was pressed.
     * @return An <i>int</i> which indicates which button was pressed to dismiss the dialog.
     */
    public Autofilter display(Autofilter filter, ArrayList raw_values, String column_name) {
	this.cancelled = false;
	this.filter = filter;
	name.setText(column_name);
	// Prune values so it only contains unique entries, then order.
	TreeSet values = new TreeSet(raw_values);
	value.setModel(new DefaultComboBoxModel(values.toArray()));
	String star = "*";
	value.insertItemAt(star, 0);
	value.setSelectedItem(star);
	first_value.setModel(new DefaultComboBoxModel(values.toArray()));
	second_value.setModel(new DefaultComboBoxModel(values.toArray()));
	// Restore previous values.
	if(filter != null && filter.value_one != null) {
	    value.setSelectedItem(filter.value_one);
	    first_method.setSelectedIndex(filter.method_one);
	    first_value.setSelectedItem(filter.value_one);
	    first_case.setSelected(filter.casesense_one);
	    if (filter.value_two == null) {
		none_radiobutton.setSelected(true);
	    }
	    else {
		if (filter.operation) {
		    and_radiobutton.setSelected(true);
		}
		else {
		    or_radiobutton.setSelected(true);
		}
		second_method.setSelectedIndex(filter.method_two);
		second_value.setSelectedItem(filter.value_two);
		second_case.setSelected(filter.casesense_two);
	    }
	    if (filter.sort) {
		ascending_radiobutton.setSelected(true);
		// order.setSelectedIndex(0);
	    }
	    else {
		descending_radiobutton.setSelected(true);
		// order.setSelectedIndex(1);
	    }
	}
	// Display	  
	setVisible(true);
	dialog.toFront();
	return this.filter;
    }

    /** Listens for actions on the button it is attached to, and when notified sets the return_value and disposes of the dialog. */
    private final class ButtonListener
	implements ActionListener {
	private boolean cancel_button;
	private boolean return_filter;
	
	/** Default constructor. Returns any filter that was already set */
	public ButtonListener() {
	    this.cancel_button = true;
	}

	/** Does an action on this button cause a filter to be returned. */
	/** Constructor takes an associated return value as an argument.
	 * @param return_filter <i>true</i> if we update then return the filter, <i>false</i> to clear existing filter.
	 */
	public ButtonListener(boolean return_filter) {
	    this.cancel_button = false;
	    this.return_filter = return_filter;
	}
	/** When any registered component is actioned apon, set the value and hide the dialog. We hide rather than dispose, because hide assures the data values will be retained. 
	 * @param event An <strong>ActionEvent</strong> containing information about the action that caused this method call.
	 * @see org.greenstone.gatherer.gui.metaaudit.Autofilter
	 */
	public void actionPerformed(ActionEvent event) {
	    if(cancel_button) {
		cancelled = true;
	    }
	    else if(return_filter) {
		if(control.getSelectedIndex() == 0) {
		    filter.setFilter(1, 0, (String)value.getSelectedItem(), true);
		    filter.setFilter(2, 0, null, true);
		}
		else {
		    filter.setFilter(1, first_method.getSelectedIndex(), (String)first_value.getSelectedItem(), first_case.isSelected());
		    if(!none_radiobutton.isSelected()) {
			if (and_radiobutton.isSelected()) {
			    filter.setOperation(Autofilter.AND);
			}
			else {
			    filter.setOperation(Autofilter.OR);
			}
			filter.setFilter(2, second_method.getSelectedIndex(), (String)second_value.getSelectedItem(), second_case.isSelected());
		    }
		}
		if (ascending_radiobutton.isSelected()) {
		    filter.setSort(Autofilter.ASCENDING);
		}
		else {
		    filter.setSort(Autofilter.DESCENDING);
		}
	    }
	    else {
		filter = null;
	    }
	    setVisible(false);
	}
    }
    /** Listens for actions on the check box it is attached to, and when notified sets the state of the second method and value to the specified state. */
    private final class CheckListener
	implements ActionListener {
	/** The default desire state any check button this listens to. */
	private boolean desired_state = false;
	/** The constructor takes an associated desired state.
	 * @param desired_state The state that should be set when this is actioned, as a <i>boolean</i>.
			*/
	public CheckListener(boolean desired_state) {
	    this.desired_state = desired_state;
	}
	/** Whenever our registered components are actioned apon, set the state of the second method and value to the desired state. */
	public void actionPerformed(ActionEvent event) {
	    second_method.setEnabled(desired_state);
	    second_value.setEnabled(desired_state);
	}
    }
}
