/**
 *############################################################################
 * A component of the Greenstone Librarian Interface, part of the Greenstone
 * digital library suite from the New Zealand Digital Library Project at the
 * University of Waikato, New Zealand.
 *
 * Author: Michael Dewsnip, NZDL Project, University of Waikato, NZ
 *
 * Copyright (C) 2005 New Zealand Digital Library Project
 *
 * 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.
 *
 * 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.
 *
 * 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 javax.swing.*;
import javax.swing.event.*;
import javax.swing.tree.*;
import org.greenstone.gatherer.Configuration;
import org.greenstone.gatherer.Dictionary;
import org.greenstone.gatherer.metadata.MetadataElement;
import org.greenstone.gatherer.metadata.MetadataValue;
import org.greenstone.gatherer.metadata.MetadataValueTreeNode;
import org.greenstone.gatherer.util.PatternTokenizer;


/**
 * This class is a little bit complex, a little bit subtle, and a little bit odd.
 * The strange interaction model is due to the fact that it is very tightly
 * coupled to the EnrichPane.
 *
 * The interaction is complex because there are three separate controls that the
 * user may interact with, each of which can affect the other two. The three
 * controls are:
 *   - The "assigned metadata" table, at the top right of the meta edit pane.
 *   - The "metadata value" text field, where users can type in new values.
 *   - The "metadata value tree", which shows other values that have been
 *     assigned to the selected metadata element.
 *
 * The interactions are:
 *   - The "assigned metadata" table
 *     Users may select one (and only one) row in this table. Selecting a row
 *     chooses one metadata element. The text field will be set to the current
 *     value of the metadata element. This value will also be selected in the
 *     metadata value tree.
 *
 *   - The "metadata value" text field
 *     If the value the user has typed in this is a prefix of an entry in the
 *     value tree, this value will be selected in the value tree. In this case,
 *     pressing "Tab" will complete the value (ie. make the value in the text
 *     field the same as the value in the tree). This is to allow users to
 *     quickly and easily assign existing metadata values to new documents.
 *
 *   - The "metadata value tree"
 *     Selecting a value in the tree will set the text field to this value.
 */
public class MetadataValueTreePane
    extends JPanel
{
    private boolean ignore_tree_selection_event = false;
    /** The metadata value that the tree pane is built on */
    private MetadataValue metadata_value = null;

    /** Used to display either the MetadataValueTree (when metadata is selected) or a placeholder panel */
    private CardLayout card_layout = null;
    /** The name of the panel containing the metadata value tree */
    private String METADATA_VALUE_TREE_CARD = "";
    /** The name of the panel containing the "extracted metadata element selected" placeholder */
    private String EXTRACTED_METADATA_ELEMENT_SELECTED_CARD = "Extracted metadata element selected";
    /** The name of the panel containing the "inherited metadata selected" placeholder */
    private String INHERITED_METADATA_SELECTED_CARD = "Inherited metadata selected";
    /** The name of the panel containing the "not only file only metadata selected" placeholder */
    private String NOT_ONE_FILE_ONLY_METADATA_SELECTED_CARD = "Not one file only metadata selected";
    /** The name of the panel containing the "no metadata element selected" placeholder */
    private String NO_METADATA_ELEMENT_SELECTED_CARD = "No metadata element selected";

    private JLabel metadata_value_tree_label = new JLabel();
    private JTextArea extracted_metadata_element_selected_message;
    private JTree metadata_value_tree;


    public MetadataValueTreePane()
    {
	super();
        JScrollPane scrol_tmp;
        this.setComponentOrientation(Dictionary.getOrientation());
        metadata_value_tree_label.setComponentOrientation(Dictionary.getOrientation());
	// Card containing the metadata value tree
	metadata_value_tree = new JTree();
        metadata_value_tree.setComponentOrientation(Dictionary.getOrientation());
	metadata_value_tree.getSelectionModel().setSelectionMode(TreeSelectionModel.SINGLE_TREE_SELECTION);
	metadata_value_tree.setModel(null);
	metadata_value_tree.setRootVisible(false);
	metadata_value_tree.putClientProperty("JTree.lineStyle", "Angled");

	JPanel metadata_value_tree_pane = new JPanel();
        metadata_value_tree_pane.setComponentOrientation(Dictionary.getOrientation());        
	metadata_value_tree_pane.setLayout(new BorderLayout());
	metadata_value_tree_pane.add(metadata_value_tree_label, BorderLayout.NORTH);
        scrol_tmp = new JScrollPane(metadata_value_tree);
        scrol_tmp.setComponentOrientation(Dictionary.getOrientation());
	metadata_value_tree_pane.add(scrol_tmp, BorderLayout.CENTER);

	// Card containing the "extracted metadata element selected" message
	extracted_metadata_element_selected_message = new JTextArea("");
        extracted_metadata_element_selected_message.setComponentOrientation(Dictionary.getOrientation());
	extracted_metadata_element_selected_message.setEditable(false);
	extracted_metadata_element_selected_message.setLineWrap(true);
	extracted_metadata_element_selected_message.setOpaque(false);
	extracted_metadata_element_selected_message.setWrapStyleWord(true);

	JPanel extracted_metadata_element_selected_pane = new JPanel();
        extracted_metadata_element_selected_pane.setComponentOrientation(Dictionary.getOrientation());
	extracted_metadata_element_selected_pane.setBorder(BorderFactory.createEmptyBorder(25,0,0,0));
	extracted_metadata_element_selected_pane.setLayout(new BorderLayout());
	extracted_metadata_element_selected_pane.add(extracted_metadata_element_selected_message, BorderLayout.CENTER);

	// Card containing the "inherited metadata selected" message
	JTextArea inherited_metadata_selected_message = new JTextArea(Dictionary.get("EnrichPane.InheritedMetadataSelected"));
	inherited_metadata_selected_message.setEditable(false);
	inherited_metadata_selected_message.setLineWrap(true);
	inherited_metadata_selected_message.setOpaque(false);
	inherited_metadata_selected_message.setWrapStyleWord(true);
        inherited_metadata_selected_message.setComponentOrientation(Dictionary.getOrientation());
	
	JPanel inherited_metadata_selected_pane = new JPanel();
	inherited_metadata_selected_pane.setBorder(BorderFactory.createEmptyBorder(25,0,0,0));
	inherited_metadata_selected_pane.setLayout(new BorderLayout());
	inherited_metadata_selected_pane.add(inherited_metadata_selected_message, BorderLayout.CENTER);
        inherited_metadata_selected_pane.setComponentOrientation(Dictionary.getOrientation());
	// Card containing the "not one file only metadata selected" message
	JTextArea not_one_file_only_metadata_selected_message = new JTextArea(Dictionary.get("EnrichPane.NotOneFileOnlyMetadataSelected"));
	not_one_file_only_metadata_selected_message.setEditable(false);
	not_one_file_only_metadata_selected_message.setLineWrap(true);
	not_one_file_only_metadata_selected_message.setOpaque(false);
	not_one_file_only_metadata_selected_message.setWrapStyleWord(true);
        not_one_file_only_metadata_selected_message.setComponentOrientation(Dictionary.getOrientation());
        
	JPanel not_one_file_only_metadata_selected_pane = new JPanel();
	not_one_file_only_metadata_selected_pane.setBorder(BorderFactory.createEmptyBorder(25,0,0,0));
	not_one_file_only_metadata_selected_pane.setLayout(new BorderLayout());
	not_one_file_only_metadata_selected_pane.add(not_one_file_only_metadata_selected_message, BorderLayout.CENTER);
        not_one_file_only_metadata_selected_pane.setComponentOrientation(Dictionary.getOrientation());
        
	// Card containing the "no metadata element selected" message
	JLabel no_metadata_element_selected_label = new JLabel(Dictionary.get("EnrichPane.No_Metadata_Element"));
	no_metadata_element_selected_label.setHorizontalAlignment(JLabel.CENTER);
	no_metadata_element_selected_label.setOpaque(false);
	no_metadata_element_selected_label.setVerticalAlignment(JLabel.CENTER);
        no_metadata_element_selected_label.setComponentOrientation(Dictionary.getOrientation());
        
	JPanel no_metadata_element_selected_pane = new JPanel();
	no_metadata_element_selected_pane.setLayout(new BorderLayout());
	no_metadata_element_selected_pane.add(no_metadata_element_selected_label, BorderLayout.CENTER);
        no_metadata_element_selected_pane.setComponentOrientation(Dictionary.getOrientation());
        
	card_layout = new CardLayout();
        
	this.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
	this.setFont(Configuration.getFont("general.font", false));
	this.setLayout(card_layout);
	this.add(no_metadata_element_selected_pane, NO_METADATA_ELEMENT_SELECTED_CARD);
	this.add(extracted_metadata_element_selected_pane, EXTRACTED_METADATA_ELEMENT_SELECTED_CARD);
	this.add(inherited_metadata_selected_pane, INHERITED_METADATA_SELECTED_CARD);
	this.add(not_one_file_only_metadata_selected_pane, NOT_ONE_FILE_ONLY_METADATA_SELECTED_CARD);
	this.add(metadata_value_tree_pane, METADATA_VALUE_TREE_CARD);
    }


    public void addMetadataValueTreeSelectionListener(TreeSelectionListener tree_selection_listener)
    {
	metadata_value_tree.addTreeSelectionListener(tree_selection_listener);
    }


    /** Returns a TreePath for the node most closely matching the metadata value. */
    private TreePath getClosestPath(String metadata_value_string)
    {
	if (metadata_value_tree.getModel() == null) {
	    return null;
	}

	// Start at the root of the tree
	MetadataValueTreeNode tree_node = (MetadataValueTreeNode) metadata_value_tree.getModel().getRoot();

	// Separate hierarchical values
	PatternTokenizer tokenizer = new PatternTokenizer(metadata_value_string, MetadataValueTreeNode.METADATA_VALUE_TREE_NODE_HIERARCHY_TOKEN);
	while (tokenizer.hasMoreTokens()) {
	    String token = tokenizer.nextToken();

	    // All components except the last must match exactly
	    if (tokenizer.hasMoreTokens()) {
		for (int i = 0; i < tree_node.getChildCount(); i++) {
		    MetadataValueTreeNode child_node = (MetadataValueTreeNode) tree_node.getChildAt(i);
		    if (child_node.getValue().equals(token)) {
			// The matching node has been found, so move onto the next token
			tree_node = child_node;
			break;
		    }
		}
	    }

	    // The last component may match partially
	    else {
		for (int i = 0; i < tree_node.getChildCount(); i++) {
		    MetadataValueTreeNode child_node = (MetadataValueTreeNode) tree_node.getChildAt(i);
		    if (child_node.getFullValue().startsWith(metadata_value_string)) {
			// The closest node has been found, so return its path
			return new TreePath(child_node.getPath());
		    }
		}

		// Not even a partial match
		return null;
	    }
	}

	// If nothing else, return the path of the root node
	return new TreePath(tree_node.getPath());
    }


    public MetadataValueTreeNode getSelectedMetadataValueTreeNode()
    {
	if (metadata_value_tree.getSelectionCount() == 0 || ignore_tree_selection_event) {
	    return null;
	}

	return (MetadataValueTreeNode) metadata_value_tree.getSelectionPath().getLastPathComponent();
    }


    public void rebuild(MetadataValue new_metadata_value)
    {
	// If the metadata value hasn't changed there is nothing to do
	if (new_metadata_value == metadata_value) {
	    return;
	}

	MetadataElement metadata_element = ((metadata_value != null) ? metadata_value.getMetadataElement() : null);
	metadata_value = new_metadata_value;

	// Selection cleared, so display "no metadata element selected" card and we're done
	if (new_metadata_value == null) {
	    metadata_value_tree.setModel(null);
	    card_layout.show(this, NO_METADATA_ELEMENT_SELECTED_CARD);
	    return;
	}

	// If a piece of inherited metadata is selected, display "inherited metadata selected" card and we're done
	if (new_metadata_value.isInheritedMetadata()) {
	    card_layout.show(this, INHERITED_METADATA_SELECTED_CARD);
	    return;
	}

	// If the metadata applies to multiple files, display "not one file only metadata selected" card and we're done
	if (new_metadata_value.isOneFileOnlyMetadata() == false) {
	    card_layout.show(this, NOT_ONE_FILE_ONLY_METADATA_SELECTED_CARD);
	    return;
	}

	// If an extracted metadata element is selected, display "extracted metadata element selected" card
	MetadataElement new_metadata_element = new_metadata_value.getMetadataElement();
	if (new_metadata_element.isExtractedMetadataElement()) {
	    String[] args = new String[1];
	    args[0] = new_metadata_element.getDisplayName();
	    extracted_metadata_element_selected_message.setText(Dictionary.get("EnrichPane.AutoMessage", args));
	    card_layout.show(this, EXTRACTED_METADATA_ELEMENT_SELECTED_CARD);
	    return;
	}

	// Display the value tree for the selected metadata element
	String[] args = new String[1];
	args[0] = new_metadata_element.getDisplayName();
	metadata_value_tree_label.setText(Dictionary.get("EnrichPane.ExistingValues", args));
	
	metadata_value_tree.setModel(new_metadata_element.getMetadataValueTreeModel());
	card_layout.show(this, METADATA_VALUE_TREE_CARD);
	selectBestPathForMetadataValue(new_metadata_value.getFullValue());
    }


    public void selectBestPathForMetadataValue(String metadata_value_string)
    {
	TreePath best_path = getClosestPath(metadata_value_string);

	// Select the new path in the tree
	// The tree selection event this causes must be ignored, since it alters the metadata text field value
	ignore_tree_selection_event = true;
	metadata_value_tree.setSelectionPath(best_path);
	ignore_tree_selection_event = false;

	// If a folder has been specified, make sure it is expanded
	if (metadata_value_string.endsWith(MetadataValueTreeNode.METADATA_VALUE_TREE_NODE_HIERARCHY_TOKEN) && !metadata_value_tree.isExpanded(best_path)) {
	    metadata_value_tree.expandPath(best_path);
	}

	// Make sure the tree path is expanded
	metadata_value_tree.makeVisible(best_path);

	// Make sure the tree path is visible on screen
	final Rectangle bounds = metadata_value_tree.getPathBounds(best_path);
	if (bounds != null) {
	    bounds.x = 0;  // Want the tree to be always flushed left

	    // Scrolling the path to be visible must be done on the GUI thread
	    Runnable task = new Runnable() {
		    public void run() {
			metadata_value_tree.scrollRectToVisible(bounds);
		    }
		};
	    SwingUtilities.invokeLater(task);
	}
    }
}
