/**
 *############################################################################
 * 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
 * Based on code by John Thompson
 *
 * 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 java.awt.event.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.text.*;
import javax.swing.tree.*;
import org.greenstone.gatherer.Configuration;
import org.greenstone.gatherer.DebugStream;
import org.greenstone.gatherer.Dictionary;
import org.greenstone.gatherer.Gatherer;
import org.greenstone.gatherer.collection.CollectionTree;
import org.greenstone.gatherer.collection.CollectionTreeNode;
import org.greenstone.gatherer.gui.tree.DragTree;
import org.greenstone.gatherer.metadata.MetadataElement;
import org.greenstone.gatherer.metadata.MetadataValue;
import org.greenstone.gatherer.metadata.MetadataValueTableEntry;
import org.greenstone.gatherer.metadata.MetadataValueTreeNode;
import org.greenstone.gatherer.metadata.MetadataXMLFileManager;


/** Provides a view of controls for the editing of metadata.
 */
public class EnrichPane
    extends JPanel
    implements TreeSelectionListener
{
    static private Dimension MINIMUM_SIZE = new Dimension(100, 100);
    static private Dimension COLLECTION_TREE_SIZE = new Dimension(250, 500);

    /** The collection tree. */
    private CollectionTree collection_tree = null;
    /** The currently reported selection. */
    private CollectionTreeNode[] file_nodes = null;
    /** Used to dynamically filter the collection tree. */
    private Filter collection_filter = null;
    /** The label at the top of the collection tree, which shows the collection name. */
    private JLabel collection_label;
    /** The panel that contains the collection tree. */
    private JPanel collection_pane = null;
    /** The scrollable area into which the collection tree is placed. */
    private JScrollPane collection_scroll = null;
    /** The splitpane dividing the collection tree and the metadata editing controls. */
    private JSplitPane external_split;
    /** The metadata value table shows the metadata values that are currently assigned to a file. */
    private MetadataValueTablePane metadata_value_table_pane = null;
    /** The metadata value tree shows the metadata values that are currently assigned to a metadata element. */
    private MetadataValueTreePane metadata_value_tree_pane = null;


    public EnrichPane()
    {
	// Create the metadata value tree pane
	metadata_value_tree_pane = new MetadataValueTreePane();
	metadata_value_tree_pane.addMetadataValueTreeSelectionListener(new MetadataValueTreeSelectionListener());

	// Create metadata value table pane
	metadata_value_table_pane = new MetadataValueTablePane();
	metadata_value_table_pane.addMetadataValueTableListSelectionListener(new MetadataValueTableListSelectionListener());
	metadata_value_table_pane.addMetadataValueTableMouseListener(new MetadataValueTableMouseListener());
	metadata_value_table_pane.addMetadataValueTextFieldDocumentListener(new MetadataValueTextFieldDocumentListener());
	metadata_value_table_pane.addMetadataValueTextFieldKeyListener(new MetadataValueTextFieldKeyListener());
    }


    /** Some actions can only occur after this panel has been displayed on-screen, so this method is provided to do exactly that. Such actions include the proportioning of the split panes and the setting of table column widths.
     */
    public void afterDisplay()
    {
	external_split.setDividerLocation(0.3);
    }


    /** Used to create, connect and layout the components to be shown on this control panel.
     * @see org.greenstone.gatherer.Gatherer
     * @see org.greenstone.gatherer.file.FileOpenActionListener
     * @see org.greenstone.gatherer.gui.Filter
     */
    public void display()
    {
	JPanel left_hand_pane = new JPanel(new BorderLayout());
	GLIButton metadata_set_button = new GLIButton(Dictionary.get("EnrichPane.ManageMetadataSets"), Dictionary.get("EnrichPane.ManageMetadataSets_Tooltip"));
	metadata_set_button.addActionListener(new ActionListener() {
		public void actionPerformed(ActionEvent event) {
		    MetadataSetDialog msd = new MetadataSetDialog();
		    if (msd.setsChanged()) {
			valueChanged(null);
		    } 
		}
	    });
	
	// Collection Tree
	collection_pane = new JPanel(new BorderLayout());
	collection_pane.setMinimumSize(MINIMUM_SIZE);
	collection_pane.setPreferredSize(COLLECTION_TREE_SIZE);

	collection_label = new JLabel(Dictionary.get("Collection.No_Collection"));
	collection_label.setOpaque(true);

	collection_tree = Gatherer.c_man.getCollectionTree();
	collection_tree.getSelectionModel().setSelectionMode(TreeSelectionModel.DISCONTIGUOUS_TREE_SELECTION);
	collection_tree.addTreeSelectionListener(this);
	collection_filter = collection_tree.getFilter();

	external_split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT);

	// Layout
	collection_pane.setBorder(BorderFactory.createCompoundBorder(BorderFactory.createEmptyBorder(3,3,3,3), BorderFactory.createLoweredBevelBorder()));
	collection_pane.setMinimumSize(MINIMUM_SIZE);
	collection_pane.setPreferredSize(new Dimension(Gatherer.g_man.getSize().width / 3, Gatherer.g_man.getSize().height));

	// Collection Pane
	collection_pane.add(collection_label, BorderLayout.NORTH);

	JSplitPane metadata_editing_pane = new JSplitPane(JSplitPane.VERTICAL_SPLIT);
	metadata_editing_pane.setBorder(BorderFactory.createEmptyBorder(0,5,5,5));
	metadata_editing_pane.setDividerSize(8);

	metadata_editing_pane.add(metadata_value_table_pane, JSplitPane.TOP);
	metadata_editing_pane.add(metadata_value_tree_pane, JSplitPane.BOTTOM);
	metadata_editing_pane.setDividerLocation(250);

	left_hand_pane.add(collection_pane, BorderLayout.CENTER);
	left_hand_pane.add(metadata_set_button, BorderLayout.SOUTH);
	external_split.add(left_hand_pane, JSplitPane.LEFT);
	external_split.add(metadata_editing_pane, JSplitPane.RIGHT);


	
	this.setLayout(new BorderLayout());
	this.add(external_split, BorderLayout.CENTER);
    }


    /** Called to inform this pane that it has just gained focus as an effect of the user clicking on its tab
     */
    public void gainFocus()
    {
	// Add the collection tree and filter back into this pane
	collection_scroll = new JScrollPane(collection_tree);
	collection_pane.add(collection_scroll, BorderLayout.CENTER);
	collection_pane.add(collection_filter, BorderLayout.SOUTH);

	// Select the first node in the tree if nothing is already selected
	if (collection_tree.getSelectionPaths() == null && collection_tree.getRowCount() > 0) {
 	    collection_tree.setImmediate(true);
 	    collection_tree.setSelectionRow(0);
 	    collection_tree.setImmediate(false);
	    return;
	}

	// Force all of the controls to be updated
	valueChanged(null);
    }


    /** Called to inform this pane that it has just lost focus as an effect of the user clicking on another tab
     */
    public void loseFocus()
    {
	// Very important: make sure metadata value is saved before leaving the pane
	metadata_value_table_pane.stopEditingAndRebuild(file_nodes);

	// Upload the modified metadata.xml files to the server now, if we're using one
	MetadataXMLFileManager.uploadModifiedMetadataXMLFiles();

	// Remove the collection tree and filter from this pane so it can be added to the Gather pane
	collection_pane.remove(collection_scroll);
	collection_pane.remove(collection_filter);
    }


    /** Called whenever the detail mode changes to ensure the filters are at an appropriate level (ie only editable by those that understand regular expression matching)
     * @param mode the mode level as an int
     */
    public void modeChanged(int mode)
    {
	collection_filter.setEditable(mode > Configuration.LIBRARIAN_MODE);
    }


    /** Refresh this pane, depending on what has just happened (refresh_reason). */
    public void refresh(int refresh_reason, boolean collection_loaded)
    {
	if (collection_loaded) {
	    // Update collection label
	    collection_label.setText(Dictionary.get("Collection.Collection"));
	    collection_label.setBackground(Configuration.getColor("coloring.collection_heading_background", false));
	    collection_label.setForeground(Configuration.getColor("coloring.collection_heading_foreground", false));

	    // Update collection tree
	    if (refresh_reason == Gatherer.COLLECTION_OPENED) {
		collection_tree.setModel(Gatherer.c_man.getCollectionTreeModel());
	    }

	    // Update collection filter
	    collection_filter.setBackground(Configuration.getColor("coloring.collection_heading_background", false));
	}
	else {
	    // Update collection label
	    collection_label.setText(Dictionary.get("Collection.No_Collection"));
	    collection_label.setBackground(Color.lightGray);
	    collection_label.setForeground(Color.black);

	    // Update collection tree
	    collection_tree.setModel(new DefaultTreeModel(new DefaultMutableTreeNode("Error")));

	    // Update collection filter
	    collection_filter.setBackground(Color.lightGray);
	}

	// Enable or disable the controls
	collection_tree.setEnabled(collection_loaded);
	collection_filter.setEnabled(collection_loaded);

	// Force the metadata table to be rebuilt (for switching extracted metadata on or off)
	if (refresh_reason == Gatherer.PREFERENCES_CHANGED) {
	    metadata_value_table_pane.stopEditingAndRebuild(file_nodes);
	}
    }


    /** Called whenever the collection tree selection changes. This causes the metadata value table to be rebuilt. */
    public void valueChanged(TreeSelectionEvent event)
    {
	// Nothing selected in the collection tree
	if (collection_tree.getSelectionCount() == 0) {
	    file_nodes = null;
	}

	// Some files selected in the collection tree
	else {
	    TreePath paths[] = collection_tree.getSelectionPaths();
	    file_nodes = new CollectionTreeNode[paths.length];
	    for (int i = 0; i < paths.length; i++) {
		file_nodes[i] = (CollectionTreeNode) paths[i].getLastPathComponent();
	    }
	}

	// Update the metadata value table (and consequently, the metadata value tree)
	metadata_value_table_pane.stopEditingAndRebuild(file_nodes);
    }


    private class MetadataValueTableListSelectionListener
	implements ListSelectionListener
    {
	public void valueChanged(ListSelectionEvent list_selection_event)
	{
	    // We only want to handle one event per selection, so wait for the value to stabilise
	    if (list_selection_event.getValueIsAdjusting()) {
		return;
	    }

	    // Update the metadata value tree for the current table selection
	    metadata_value_tree_pane.rebuild(metadata_value_table_pane.getSelectedMetadataValueTableEntry());
	}
    }


    private class MetadataValueTableMouseListener
	extends MouseAdapter
    {
	public void mouseClicked(MouseEvent mouse_event)
	{
	    // We're only interested in clicks on the inherited column
	    if (metadata_value_table_pane.isMouseEventForInheritedMetadataValueTableColumn(mouse_event) == false) {
		return;
	    }

	    // If the selected metadata is inherited, switch to the folder it came from
	    MetadataValueTableEntry selected_metadata_value_table_entry = metadata_value_table_pane.getSelectedMetadataValueTableEntry();
	    if (selected_metadata_value_table_entry.isInheritedMetadata()) {
		collection_tree.setSelection(selected_metadata_value_table_entry.getFolderMetadataInheritedFrom());
	    }
	}
    }


    private class MetadataValueTextFieldDocumentListener
	implements DocumentListener
    {
	/** Gives notification that an attribute or set of attributes changed */
	public void changedUpdate(DocumentEvent document_event) {
	    validate(document_event);
	}

	/** Gives notification that there was an insert into the document */
	public void insertUpdate(DocumentEvent document_event) {
	    validate(document_event);
	}

	/** Gives notification that a portion of the document has been removed */
	public void removeUpdate(DocumentEvent document_event) {
	    validate(document_event);
	}


	/** Ensures that the value tree is updated in response to changes in the value text field */
	private void validate(DocumentEvent document_event)
	{
	    try {
		Document document = document_event.getDocument();
		String metadata_value_string = document.getText(0, document.getLength());
		metadata_value_tree_pane.selectBestPathForMetadataValue(metadata_value_string);
	    }
	    catch (Exception exception) {
		DebugStream.printStackTrace(exception);
	    }
	}
    }


    private class MetadataValueTextFieldKeyListener
	extends KeyAdapter
    {
	/** Gives notification of key events on the text field */
	public void keyPressed(KeyEvent key_event)
	{
	    // Tab: Auto-complete what is selected in the metadata value tree
	    if (key_event.getKeyCode() == KeyEvent.VK_TAB) {
		MetadataValueTreeNode selected_metadata_value_tree_node = metadata_value_tree_pane.getSelectedMetadataValueTreeNode();
		if (selected_metadata_value_tree_node != null) {
		    metadata_value_table_pane.setMetadataValueTextFieldValue(selected_metadata_value_tree_node.getFullValue());
		}

		// We do not want this event to be processed by the table also
		key_event.consume();
	    }

	    // Enter: save the current value then add a blank row for the selected metadata element
	    if (key_event.getKeyCode() == KeyEvent.VK_ENTER) {
		metadata_value_table_pane.stopEditingAndAddBlankRowForSelectedMetadataElement();
	    }
	}
    }


    private class MetadataValueTreeSelectionListener
	implements TreeSelectionListener
    {
	public void valueChanged(TreeSelectionEvent tree_selection_event)
	{
	    // When a node is selected in the tree, fill the metadata value text field with the selected node's value
	    MetadataValueTreeNode selected_metadata_value_tree_node = metadata_value_tree_pane.getSelectedMetadataValueTreeNode();
	    if (selected_metadata_value_tree_node != null) {
		metadata_value_table_pane.setMetadataValueTextFieldValue(selected_metadata_value_tree_node.getFullValue());
	    }
	}
    }
}
