/**
 *############################################################################
 * 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 java.awt.event.*;
import java.io.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import org.greenstone.gatherer.Configuration;
import org.greenstone.gatherer.Dictionary;
import org.greenstone.gatherer.collection.CollectionTreeNode;
import org.greenstone.gatherer.metadata.MetadataChangedListener;
import org.greenstone.gatherer.metadata.MetadataElement;
import org.greenstone.gatherer.metadata.MetadataTools;
import org.greenstone.gatherer.metadata.MetadataValue;
import org.greenstone.gatherer.metadata.MetadataValueTableEntry;
import org.greenstone.gatherer.metadata.MetadataValueTableModel;
import org.greenstone.gatherer.metadata.MetadataXMLFileManager;
import org.greenstone.gatherer.util.JarTools;
import org.greenstone.gatherer.util.Utility;


public class MetadataValueTablePane
    extends JPanel
{
    private MetadataValueTable metadata_value_table = null;
    /** Used to display either the MetadataValueTable (when files are selected) or a placeholder panel */
    private CardLayout card_layout = null;
    /** The name of the panel containing the metadata value table */
    private String METADATA_VALUE_TABLE_CARD = "";
    /** The name of the panel containing the "no metadata available" placeholder */
    private String NO_METADATA_AVAILABLE_CARD = "No metadata available";
    /** The name of the panel containing the "no files selected" placeholder */
    private String NO_FILES_SELECTED_CARD = "No files selected";


    public MetadataValueTablePane()
    {
	super();

	metadata_value_table = new MetadataValueTable();

	JScrollPane metadata_value_table_scroll = new JScrollPane(metadata_value_table);
	metadata_value_table_scroll.getViewport().setBackground(Configuration.getColor("coloring.collection_tree_background", false));
	metadata_value_table_scroll.setOpaque(true);

	JPanel metadata_value_table_pane = new JPanel();
	metadata_value_table_pane.setLayout(new BorderLayout());
	metadata_value_table_pane.add(metadata_value_table_scroll, BorderLayout.CENTER);

	JLabel no_metadata_available_label = new JLabel(Dictionary.get("EnrichPane.No_Metadata"));
	no_metadata_available_label.setHorizontalAlignment(JLabel.CENTER);
	no_metadata_available_label.setOpaque(false);
	no_metadata_available_label.setVerticalAlignment(JLabel.CENTER);
	
	JPanel no_metadata_available_pane = new JPanel();
	no_metadata_available_pane.setLayout(new BorderLayout());
	no_metadata_available_pane.add(no_metadata_available_label, BorderLayout.CENTER);

	JLabel no_files_selected_label = new JLabel(Dictionary.get("EnrichPane.No_File"));
	no_files_selected_label.setHorizontalAlignment(JLabel.CENTER);
	no_files_selected_label.setOpaque(false);
	no_files_selected_label.setVerticalAlignment(JLabel.CENTER);
	
	JPanel no_files_selected_pane = new JPanel();
	no_files_selected_pane.setLayout(new BorderLayout());
	no_files_selected_pane.add(no_files_selected_label, BorderLayout.CENTER);

	card_layout = new CardLayout();

	this.setBorder(BorderFactory.createEmptyBorder(5,0,5,0));
	this.setFont(Configuration.getFont("general.font", false));
	this.setLayout(card_layout);
	this.add(metadata_value_table_pane, METADATA_VALUE_TABLE_CARD);
	this.add(no_metadata_available_pane, NO_METADATA_AVAILABLE_CARD);
	this.add(no_files_selected_pane, NO_FILES_SELECTED_CARD);
    }


    public void addMetadataValueTableListSelectionListener(ListSelectionListener list_selection_listener)
    {
	metadata_value_table.getSelectionModel().addListSelectionListener(list_selection_listener);
    }


    public void addMetadataValueTableMouseListener(MouseListener mouse_listener)
    {
	metadata_value_table.addMouseListener(mouse_listener);
    }


    public void addMetadataValueTextFieldDocumentListener(DocumentListener document_listener)
    {
	metadata_value_table.getMetadataValueTextField().getDocument().addDocumentListener(document_listener);
    }


    public void addMetadataValueTextFieldKeyListener(KeyListener key_listener)
    {
	metadata_value_table.getMetadataValueTextField().addKeyListener(key_listener);
    }


    public MetadataValueTableEntry getSelectedMetadataValueTableEntry()
    {
	return metadata_value_table.getSelectedMetadataValueTableEntry();
    }


    public boolean isMouseEventForInheritedMetadataValueTableColumn(MouseEvent mouse_event)
    {
	return metadata_value_table.isMouseEventForInheritedMetadataValueTableColumn(mouse_event);
    }


    public void setMetadataValueTextFieldValue(String metadata_value_string)
    {
	metadata_value_table.setMetadataValueTextFieldValue(metadata_value_string);
    }


    public void stopEditingAndAddBlankRowForSelectedMetadataElement()
    {
	metadata_value_table.stopEditing();
	metadata_value_table.addBlankRowForSelectedMetadataElement();
    }


    public void stopEditingAndRebuild(CollectionTreeNode[] file_nodes)
    {
	metadata_value_table.stopEditing();
	metadata_value_table.rebuild(file_nodes);

	// If no files are selected display the "no file selected" card
	if (file_nodes == null) {
	    card_layout.show(this, NO_FILES_SELECTED_CARD);
	}
	// If the metadata value table is empty display the "no metadata available" card
	else if (metadata_value_table.getRowCount() == 0) {
	    card_layout.show(this, NO_METADATA_AVAILABLE_CARD);
	}
	// Otherwise display the card with the metadata value table
	else {
	    card_layout.show(this, METADATA_VALUE_TABLE_CARD);
	}
    }


    private class MetadataValueTable
	extends JTable
	implements MetadataChangedListener
    {
	private int MINIMUM_TABLE_HEADER_SIZE = 15;

	private MetadataValueTableModel metadata_value_table_model = null;
	private JTextField metadata_value_text_field = new JTextField();


	public MetadataValueTable()
	{
	    // Create the model for the table
	    metadata_value_table_model = new MetadataValueTableModel();
	    setModel(metadata_value_table_model);

	    // We allow only one row in the table to be selected at a time
	    setSelectionMode(ListSelectionModel.SINGLE_SELECTION);

	    // We use our own editor for the value column so we have easy access to the underlying text field
	    setDefaultEditor(String.class, new DefaultCellEditor(metadata_value_text_field));

	    // We need to listen for double clicks on the text field to open the editor dialog
	    metadata_value_text_field.addMouseListener(new MetadataValueTextFieldMouseListener());
	    metadata_value_text_field.setBorder(null);

	    // We need to listen for key presses so we can catch Enter presses
	    addKeyListener(new MetadataValueTableKeyListener());

	    // We need to listen for metadata changes so we can rebuild the table
	    MetadataXMLFileManager.addMetadataChangedListener(this);

	    // We also have to ensure that the table column header hasn't gone on a severe Jenny Craig binge and has somehow lost 7/8th of its component size
	    JTableHeader table_header = getTableHeader();
	    Dimension table_header_preferred_size = table_header.getPreferredSize();
	    if (table_header_preferred_size.height < MINIMUM_TABLE_HEADER_SIZE) {
		table_header_preferred_size.setSize(table_header_preferred_size.width, MINIMUM_TABLE_HEADER_SIZE);
		table_header.setPreferredSize(table_header_preferred_size);
	    }

	    // Set the size of the table columns: 2/7 for element, 5/7 for value (actual numbers don't matter) 
	    TableColumnModel column_model = getColumnModel();
	    TableColumn inherited_column = column_model.getColumn(0);
	    inherited_column.setMinWidth(25);
	    inherited_column.setPreferredWidth(25);
	    inherited_column.setMaxWidth(25);
	    TableColumn element_column = column_model.getColumn(1);
	    element_column.setPreferredWidth(200);
	    TableColumn value_column = column_model.getColumn(2);
	    value_column.setPreferredWidth(500);

	    // Use our own renderer for the table cells
	    MetadataValueTableCellRenderer metadata_value_table_cell_renderer = new MetadataValueTableCellRenderer();
	    inherited_column.setCellRenderer(metadata_value_table_cell_renderer);
	    element_column.setCellRenderer(metadata_value_table_cell_renderer);
	    value_column.setCellRenderer(metadata_value_table_cell_renderer);
	}


	private void addBlankRowForSelectedMetadataElement()
	{
	    // Add a blank entry for the selected metadata element, then switch to it
	    MetadataElement selected_metadata_element = getSelectedMetadataValueTableEntry().getMetadataElement();
	    int blank_row = metadata_value_table_model.addBlankRowForMetadataElement(selected_metadata_element);
	    changeSelection(blank_row, 2, false, false);
	}


	private JTextField getMetadataValueTextField()
	{
	    return metadata_value_text_field;
	}


	private MetadataValueTableEntry getSelectedMetadataValueTableEntry()
	{
	    return metadata_value_table_model.getMetadataValueTableEntry(getSelectedRow());
	}


	private boolean isMouseEventForInheritedMetadataValueTableColumn(MouseEvent mouse_event)
	{
	    return (columnAtPoint(mouse_event.getPoint()) == 0);
	}


	public void metadataChanged(CollectionTreeNode[] file_nodes)
	{
	    rebuild(file_nodes);
	}


	private void rebuild(CollectionTreeNode[] file_nodes)
	{
	    // Note the metadata value table entry currently selected
	    MetadataValueTableEntry selected_metadata_value_table_entry = getSelectedMetadataValueTableEntry();

	    // We don't want a lot of ListSelectionEvents while the table is rebuilding
	    clearSelection();

	    // Rebuild the metadata value table model
	    metadata_value_table_model.rebuild(file_nodes);

	    // Restore the metadata value table entry selection
	    if (selected_metadata_value_table_entry != null) {
		int row_to_select = metadata_value_table_model.findMetadataValueTableEntryToSelect(selected_metadata_value_table_entry);
		changeSelection(row_to_select, 2, false, false);
	    }
	}


	private void setMetadataValueTextFieldValue(String metadata_value_string)
	{
	    metadata_value_text_field.setText(metadata_value_string);
	    metadata_value_text_field.requestFocus();
	}


	private void stopEditing()
	{
	    // Save the current value in the text field, then remove the editor so it doesn't get saved again
	    TableCellEditor table_cell_editor = getCellEditor();
	    if (table_cell_editor != null) {
		Object new_metadata_value = table_cell_editor.getCellEditorValue();
		setValueAt(new_metadata_value, editingRow, editingColumn);
		removeEditor();
	    }
	}


	private class MetadataValueTableCellRenderer
	    extends DefaultTableCellRenderer
	{
	    /** Returns the default table cell renderer.
	     * @param table The <strong>JTable</strong>.
	     * @param value The value to assign to the cell at [row, column] as an <strong>Object</strong>.
	     * @param isSelected <i>true</i> if cell is selected.
	     * @param hasFocus <i>true</i> if cell has focus.
	     * @param row The row of the cell to render as an <i>int</i>.
	     * @param column The column of the cell to render as an <i>int</i>.
	     * @return The default table cell renderer <strong>Component</strong>.
	     */
	    public Component getTableCellRendererComponent(JTable table, Object value, boolean isSelected, boolean hasFocus, int row, int column)
	    {
		JComponent component = (JLabel) super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, column);

		int real_column = table.convertColumnIndexToModel(column);
		// First column: inherited metadata icon
		if (real_column == 0 && value != null) {
		    component = new JLabel(JarTools.getImage("upfolder.gif"));
		    component.setToolTipText(Dictionary.get("EnrichPane.InheritedMetadata_Tooltip"));
		}

		// Make sure the focus always stay in the value cell of the selected row
		if (real_column == 2 && isSelected) {
		    table.editCellAt(row, column);
		    if (table.isEditing()) {
			table.getEditorComponent().requestFocus();
		    }
		}

		// Set up the component
		component.setOpaque(true);

		// Foreground
		if (metadata_value_table_model.isCommon(row)) {
		    component.setForeground(Color.black);
		}
		else {
		    component.setForeground(Color.gray);
		}

		// Background
		if (isSelected) {
		    component.setBackground(Configuration.getColor("coloring.workspace_heading_background", true));
		}
		else {
		    if (real_column < 2) {
			component.setBackground(Configuration.getColor("coloring.collection_heading_background", true));
		    }
		    else {
			component.setBackground(Configuration.getColor("coloring.collection_tree_background", true));
		    }
		}

		// The value column of cells never paints focus
		if (real_column == 2) {
		    component.setBorder(BorderFactory.createEmptyBorder(0,2,0,0));
		}

		// We set a tooltip over the element column containing the definition of the element
		if (value instanceof MetadataElement) {
		    String interface_language_code = Configuration.getLanguage();
		    String metadata_element_definition = MetadataTools.getMetadataElementAttribute((MetadataElement) value, "definition", interface_language_code, "en");
		    if (metadata_element_definition != null) {
			component.setToolTipText(Utility.formatHTMLWidth(metadata_element_definition, 60));
		    }
		}

		return component;
	    }
	}


	private class MetadataValueTableKeyListener
	    extends KeyAdapter
	{
	    /** Gives notification of key events on the text field */
	    public void keyPressed(KeyEvent key_event)
	    {
		// Enter: save the current value then add a blank row for the selected metadata element
		if (key_event.getKeyCode() == KeyEvent.VK_ENTER) {
		    // ...but not for extracted metadata elements of course
		    MetadataValueTableEntry metadata_value_table_entry = getSelectedMetadataValueTableEntry();
		    if (!metadata_value_table_entry.getMetadataElement().isExtractedMetadataElement()) {
			addBlankRowForSelectedMetadataElement();
		    }

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


	private class MetadataValueTextFieldMouseListener
	    extends MouseAdapter
	{
	    public void mouseClicked(MouseEvent mouse_event)
	    {
		// Double-click: pop up an editor dialog for large metadata values
		if (mouse_event.getClickCount() == 2) {
 		    EditorDialog editor_dialog = new EditorDialog();
 		    String new_metadata_value_string = editor_dialog.display(metadata_value_text_field.getText());
 		    if (new_metadata_value_string != null) {
 			setMetadataValueTextFieldValue(new_metadata_value_string);
 		    }
		}
	    }
	}
    }
}
