/**
 *#########################################################################
 *
 * 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;

import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.util.regex.*;
import javax.swing.*;
import javax.swing.tree.*;
import org.greenstone.gatherer.Dictionary;
import org.greenstone.gatherer.Gatherer;
import org.greenstone.gatherer.file.FileSystemModel;
import org.greenstone.gatherer.gui.tree.DragTree;

/** <p>This object allows the user to set a filter on one of the workspace trees, specifying a preset type, or a regular expression that a files must match to be in the tree. Note that all directories are included. This class includes the controls for editing the filter. The trick is that several instances of the Filter class can share the same internal data (termed a 'run' of filters), so that the filter set on the GatherPane and the EnrichPane are virtually the same.</p>
 * <p>The regular expression typed uses '*' as a wildcard character (equivalent to '.*'), and does not use '.' to match any single character (use '?' instead).</p>
 * @author John Thompson, Greenstone Digital Library, University of Waikato
 * @version 2.3
 */
public class Filter
    extends JPanel {
    /** The other filters in this run of filters, used to ensure they all show the same thing. */
    private ArrayList others = null;
    /** Is this the first filter of this run of filters created (later filters will share the same information). */
    private boolean first = true;
    /** Prevent any changes we make in the class from causing events which we then process causing events... */
    private boolean ignore = false;
    /** A reference to ourselves so inner classes can refer to us. */
    private Filter this_filter = null;
    /** The check box to enable/disable filter. */
    private JCheckBox checkbox = null;
    /** The editable combobox where you either choose a predefined filter, or type a new pseudo-regular expression. */
    private GComboBox combobox = null;
    /** The label shown on the filter controls. */
    private JLabel label = null;
    /** A reference to the tree this filter is being applied to. */
    private DragTree tree = null;
    /** The default size for the label. */
    static final private Dimension SIZE = new Dimension(100,30);
    /** Preprogrammed default filters. */
    static final private String DEFAULTS[] 
	= {"^.*\\.html?$", "^.*\\.xml$", "^.*\\.txt$", 
	   "(^.*\\.jpe?g$)|(^.*\\.png$)|(^.*\\.gif$)|(^.*\\.bmp$)|(^.*\\.tiff?$)",
	   "^.*\\.pdf$", 
	   "(^.*\\.docx?$)|(^.*\\.pptx?$)|(^.*\\.xlsx?$)|(^.*\\.od(t|s|p)$)"};

    /** Constructor.
     * @param tree A reference to the <strong>JTree</strong> being affected.
     */
    public Filter(DragTree tree) {
	this(tree, null);
    }

    /** Constructor.
     * @param tree A reference to the <strong>JTree</strong> being affected.
     * @param others An <strong>ArrayList</strong> of the other Filters already in this run.
     */
    public Filter(DragTree tree, ArrayList others) {
	super();
        this.setComponentOrientation(Dictionary.getOrientation());
	if (others == null) {
	    others = new ArrayList();
	}
	this.others = others;
	this.others.add(this);
	this.this_filter = this;
	this.tree = tree;
	// Create components.
	combobox = new GComboBox(true);
	try {
	    combobox.add(new Entry());
	}
	catch (Exception error) {
	    error.printStackTrace();
	}
	for(int i = 0; i < DEFAULTS.length; i++) {
	    try {
		Entry entry = new Entry(Dictionary.get("Filter." + i), Pattern.compile(DEFAULTS[i]));
		combobox.add(entry);
	    }
	    catch (Exception error) {
		error.printStackTrace();
	    }
	}
	label = new JLabel(Dictionary.get("Filter.Filter_Tree"));
        label.setComponentOrientation(Dictionary.getOrientation());
        label.setComponentOrientation(org.greenstone.gatherer.Dictionary.getOrientation());
	combobox.setToolTipText(Dictionary.get("Collection.Filter_Tooltip"));
	
	// Add listeners.
	combobox.addActionListener(new ComboBoxListener());
	// Layout.
	label.setBorder(BorderFactory.createEmptyBorder(0,0,0,5));

	setBorder(BorderFactory.createEmptyBorder(2,2,2,2));
	setLayout(new BorderLayout());
	add(label, BorderLayout.LINE_START);
	add(combobox, BorderLayout.CENTER);
    }

    /** Used to restore the filter state to enabled, the normal state during collection editing.
     * @param state The new state for the filter. <i>true</i> for enabled, <i>false</i> otherwise.
     */
    public void setEnabled(boolean state) {
	ignore = true;
	combobox.setEnabled(state);
	ignore = false;
    }

    /** Set the combobox model for this filter.
     * @param model The new <strong>ComboBoxModel</strong> to use.
     */
    public void setComboBoxModel(ComboBoxModel model) {
	combobox.setModel(model);
    }

    /** Ensure that a certain entry is selected from the combobox.
     * @param selection The <strong>Entry</strong> that should be selected.
     */
    public void setComboBoxSelection(Entry selection) {
	ignore = true;
	combobox.setSelectedItem(selection);
	ignore = false;
    }

    /** Sets whether this combobox is editable or not
     * @param value true to make the control editable, false otherwise
     */
    public void setEditable(boolean value) {
	combobox.setEditable(value);	    
    }

    /** Set to signify that this filter is the first in a new run of filters.
     * @param first <i>true</i> if this is the first filter in a run, <i>false</i> if it will just be added to the current run.
     */
    public void setFirst(boolean first) {
	this.first = first;
    }

    /** Encode an expression in pseudo-regular expression into regular expression.
     * @param raw The pseudo-regular expression <strong>String</strong> which includes several characters which differ in meaning from regular expression queries.
     * @return A proper regular expression as a <strong>String</strong>.
     */
    private String encode(String raw) {
	StringBuffer working = new StringBuffer();
	for(int i = 0; i < raw.length(); i++) {
	    char c = raw.charAt(i);
	    switch(c) {
	    case '.':
		working.append("\\.");
		break;
	    case '*':
		working.append(".*");
		break;
	    case '?':
		working.append(".");
		break;
	    default:
		working.append(Character.toLowerCase(c));
	    }
	}
	return working.toString();
    }
    /** This method applies the given pattern to the tree registered as belonging to this filter.*/
    private void setFilter(Pattern pattern) {
	// Show busy cursor.
	Gatherer.g_man.wait(true);
	FileSystemModel model = (FileSystemModel) tree.getModel();
	// Apply filter
	if(pattern != null) {
	    model.setFilter(pattern.pattern());
	}
	else {
	    model.setFilter(null);
	}
	// Ask tree to completely refresh
	tree.refresh(null);
	// Restore cursor
	Gatherer.g_man.wait(false);
    }

    /** Listens for changes in the combobox as when one is detected attempts to compile a regular expression from whatever text was entered. If successful, or if the item chosen was a predefined filter, it then applies the filter to the target tree. */
    private class ComboBoxListener
	implements ActionListener {
	/** Called when a new item is selected from the filter combobox, we treat the new entry as a pseudo-regular expression, compile it and then apply it to the tree.
	 * @param event An <strong>ActionEvent</strong> containing more information about the change performed.
	 * @see org.greenstone.gatherer.gui.Filter.Entry
	 */
	public void actionPerformed(ActionEvent event) {
	    try {
		Object temp = combobox.getSelectedItem();
		Entry entry = null;
		if(temp instanceof String) {
		    String temp_str = (String) temp;
		    ///ystem.err.println("Filter = " + temp_str);

		    // Ignore any string which matches a predefined filter
		    if(temp_str.equals(Dictionary.get("Filter.All_Files"))) {
		    }
		    // HTM & HTML
		    else if(temp_str.equals(Dictionary.get("Filter.0"))) {
		    }
		    // XML
		    else if(temp_str.equals(Dictionary.get("Filter.1"))) {
		    }
		    // Text files
		    else if(temp_str.equals(Dictionary.get("Filter.2"))) {
		    }
		    // Images
		    else if(temp_str.equals(Dictionary.get("Filter.3"))) {
		    }
		    // PDF
		    else if(temp_str.equals(Dictionary.get("Filter.4"))) {
		    }
		    // Office Docs
		    else if(temp_str.equals(Dictionary.get("Filter.5"))) {
		    }
		    else {
			// Make sure the filter isn't already in the list
			boolean already_exists = false;
			for (int i = 0; i < combobox.getItemCount(); i++) {
			    if (temp_str.equals(combobox.getItemAt(i).toString())) {
				already_exists = true;
				entry = (Entry) combobox.getItemAt(i);
				break;
			    }
			}

			if (already_exists == false) {
			    entry = new Entry(temp_str, Pattern.compile(encode(temp_str)));
			    int position = combobox.getItemCount();
			    combobox.insertItemAt(entry, position);
			    combobox.setSelectedIndex(position);
			}
		    }
		}
		else {
		    ///ystem.err.println("Custom Filter");
		    entry = (Entry) temp;
		}
		if(entry != null) {
		    setFilter(entry.getPattern());
		}
		// Else we ignore this event as being one of the painfully erroneous events we receive because we've been silly enough to have an editable combobox.
	    }
	    catch (PatternSyntaxException error) {
		if(first) {
		    JOptionPane.showMessageDialog(Gatherer.g_man, Dictionary.get("Filter.Invalid_Pattern"), Dictionary.get("General.Error"), JOptionPane.ERROR_MESSAGE);
		}
	    }
	}
    }
    /** An object that holds a filter entry. This is string used for the filter pattern and, if not custom built, its name. */
    private class Entry
	implements Comparable {
	/** The compiled pattern created from a regular expression. */
	private Pattern pattern = null;
	/** The name of this filter entry. */
	private String name = null;
	/** Constructor. */
	public Entry() {
	}
	/** Constructor.
	 * @param name The name of this entry as a <strong>String</strong>.
	 * @param pattern The compiled regular expression as a <strong>Pattern</strong>.
	 */
	public Entry(String name, Pattern pattern) {
	    this.name = name;
	    this.pattern = pattern;
	}
	/** Compare two Entrys for ordering.
	 * @param object The other Entry to compare to, as an <strong>Object</strong>.
	 * @return An <i>int</i> indicating the respective ordering, as defined in java.lang.String#compareTo
	 */
	public int compareTo(Object object) {
	    return toString().compareTo(object.toString());
	}
	/** Retrieve the pattern associated with this entry.
	 * @return The <strong>Pattern</strong>.
	 */
	public Pattern getPattern() {
	    return pattern;
	}
	/** Translate this entry into a textual representation.
	 * @return A <strong>String</strong> containing the representation.
	 */
	public String toString() {
	    String result = null;
	    if (name != null) {
		result = name;
	    }
	    else if (pattern == null) {
		result = Dictionary.get("Filter.All_Files");
	    }
	    else {
		result = pattern.pattern();
	    }
	    return result;
	}
    }
}
