/**
 *############################################################################
 * 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) 2004 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.metadata;


import java.awt.*;
import java.io.*;
import java.util.*;
import javax.swing.*;
import javax.swing.table.*;
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.CollectionTreeNode;
import org.greenstone.gatherer.gui.WarningDialog;


public class MetadataValueTableModel
    extends AbstractTableModel
{
    /** It is not expected that this class will ever be serialized */
    private static final long serialVersionUID = 0L;

    /** The CollectionTreeNodes this model is built for */
    private CollectionTreeNode[] file_nodes = null;
    /** The list of MetadataValueTableEntries in the table */
    private ArrayList metadata_value_table_entries = new ArrayList();

    static final private String[] COLUMN_NAMES = { "", Dictionary.get("Metadata.Element"), Dictionary.get("Metadata.Value") };


    public int addBlankRowForMetadataElement(MetadataElement metadata_element)
    {
	MetadataValueTableModelBuilder metadata_value_table_model_builder = new MetadataValueTableModelBuilder();
	MetadataValue blank_metadata_value = new MetadataValue(metadata_element, new MetadataValueTreeNode(""));
	return metadata_value_table_model_builder.insertMetadataValue(blank_metadata_value);
    }


    public int findMetadataValueTableEntryToSelect(MetadataValueTableEntry metadata_value_table_entry)
    {
	MetadataElement metadata_element = metadata_value_table_entry.getMetadataElement();

	// Find the correct entry to select
	for (int i = 0; i < metadata_value_table_entries.size(); i++) {
	    MetadataValueTableEntry current_metadata_value_table_entry = (MetadataValueTableEntry) metadata_value_table_entries.get(i);
	    int element_comparison = MetadataSetManager.compareMetadataElements(current_metadata_value_table_entry.getMetadataElement(), metadata_element);

	    // We've found the right element, so check if the value already exists
	    if (element_comparison == 0) {
		int value_comparison = current_metadata_value_table_entry.compareTo(metadata_value_table_entry);
		if (value_comparison == 0) {
		    // Entry found!
		    return i;
		}
	    }

	    // We've just gone past the correct entry to select
	    if (element_comparison > 0) {
		return i - 1;
	    }
	}

	// Have to select the last entry
	return metadata_value_table_entries.size() - 1;
    }


    public Class getColumnClass(int col)
    {
	return getValueAt(0, col).getClass();
    }


    /** Returns the number of columns in this table. */
    public int getColumnCount()
    {
	return COLUMN_NAMES.length;
    }


    /** Retrieves the name of the specified column. */
    public String getColumnName(int col)
    {
	return COLUMN_NAMES[col];
    }


    /* Called to retrieve the MetadataValueTableEntry at a certain row. Usually caused by the user selecting a row in the table. It is synchronized so that the model doesn't up and change while we're trying to retrieve the indicated element. */
    public synchronized MetadataValueTableEntry getMetadataValueTableEntry(int row)
    {
	if (row >= 0 && row < metadata_value_table_entries.size()) {
	    return (MetadataValueTableEntry) metadata_value_table_entries.get(row);
	}

	return null;
    }


    /** Returns the number of rows in this table. */
    public int getRowCount()
    {
	return metadata_value_table_entries.size();
    }


    /** Returns the cell value at a given row and column as an Object. */
    public Object getValueAt(int row, int col)
    {
	// Check values are reasonable
	if (row < 0 || row >= metadata_value_table_entries.size() || col < 0 || col >= COLUMN_NAMES.length) {
	    return null;
	}

	MetadataValueTableEntry metadata_value_table_entry = (MetadataValueTableEntry) metadata_value_table_entries.get(row);
	
	if(metadata_value_table_entry == null) {
		System.err.println("\n@@@@@ MetadataValueTableModel.getValueAt(): metadata_value_table_entry is unexpectedly null!\n");
		return null;
	}
	
	if (col == 0 && metadata_value_table_entry.isInheritedMetadata()) {
	    return metadata_value_table_entry.getFolderMetadataInheritedFrom();
	}

	if (col == 1) {
	    return metadata_value_table_entry.getMetadataElement();
	}

	if (col == 2)
	{
		return decodeHTMLEntities(metadata_value_table_entry.getFullValue());
	}

	return null;
	}
	
	public String decodeHTMLEntities(String text)
	{
		text = text.replace("&amp;#40;", "(");
		text = text.replace("&amp;#41;", ")");
		text = text.replace("&amp;#44;", ",");
		text = text.replace("&amp;#60;", "<");
		text = text.replace("&amp;#62;", ">");
		text = text.replace("&amp;#91;", "[");
		text = text.replace("&amp;#93;", "]");
		text = text.replace("&amp;#123;", "{");
		text = text.replace("&amp;#125;", "}");
		return text;
	}

    public boolean isCellEditable(int row, int col)
    {
	// The inherited and element columns are never editable
	if (col < 2) {
	    return false;
	}

	// Extracted and inherited metadata is not editable
	MetadataValueTableEntry metadata_value_table_entry = (MetadataValueTableEntry) metadata_value_table_entries.get(row);
	if (metadata_value_table_entry.getMetadataElement().isExtractedMetadataElement() || metadata_value_table_entry.isInheritedMetadata()) {
	    return false;
	}

	return true;
    }


    /** Determine if the given metadata is common to all selected file nodes. */
    public boolean isCommon(MetadataValueTableEntry metadata_value_table_entry)
    {
	return (file_nodes != null && metadata_value_table_entry.getOccurrences() == file_nodes.length);
    }


    /** Determine if the given metadata is common to all selected file nodes. */
    public boolean isCommon(int row)
    {
	if (row >= 0 && row < metadata_value_table_entries.size()) {
	    return isCommon((MetadataValueTableEntry) metadata_value_table_entries.get(row));
	}

	return false;
    }


    public void rebuild(CollectionTreeNode[] file_nodes)
    {
	this.file_nodes = file_nodes;
	metadata_value_table_entries.clear();

	// Collection is in a state of flux
	if (!Gatherer.c_man.ready()) {
	    return;
	}

	if (file_nodes == null || file_nodes.length == 0) {
	    return;
	}

	// Create model builder
	MetadataValueTableModelBuilder builder = new MetadataValueTableModelBuilder();
	builder.run();
    }


    public void setValueAt(Object new_metadata_value, int row, int col)
    {
	MetadataValueTableEntry metadata_value_table_entry = getMetadataValueTableEntry(row);
	if(metadata_value_table_entry == null) {
		System.err.println("\n@@@@@ MetadataValueTableModel.setValueAt(): metadata_value_table_entry is unexpectedly null!\n");
		return;
	}
	
	// If nothing has changed no action is necessary
	String old_metadata_value = metadata_value_table_entry.getFullValue();
	if (new_metadata_value.equals(old_metadata_value)) {
	    return;
	}

	// Lock the interface so nothing can be changed while the metadata edit is being processed
	Gatherer.g_man.wait(true);

	
	// Check for a restricted metadata element
	MetadataElement metadata_element = metadata_value_table_entry.getMetadataElement();
	if (!new_metadata_value.equals("") && metadata_element.isRestricted()) {
	    if (metadata_element.getMetadataValueTreeNode((String)new_metadata_value)==null) {
		WarningDialog dialog = new WarningDialog("warning.InvalidMetadata", Dictionary.get("InvalidMetadata.Title"), Dictionary.get("InvalidMetadata.Message"), null, false);
		int dialog_result = dialog.display();
		dialog.dispose();
		Gatherer.g_man.wait(false);
		return;
		
	    }
	}
	
	// Metadata value added
	if (old_metadata_value.equals("") && !new_metadata_value.equals("")) {    
	    // If we're adding metadata to folders display the warning
 	    if (!file_nodes[0].isLeaf()) {
 		WarningDialog dialog = new WarningDialog("warning.DirectoryLevelMetadata", Dictionary.get("DirectoryLevelMetadata.Title"), Dictionary.get("DirectoryLevelMetadata.Message"), null, true);
 		int dialog_result = dialog.display();
 		dialog.dispose();
 		if (dialog_result != JOptionPane.OK_OPTION) {
		    Gatherer.g_man.wait(false);
 		    return;
 		}
 	    }

	    MetadataValueTreeNode metadata_value_tree_node = metadata_element.addMetadataValue((String) new_metadata_value);
	    MetadataValue metadata_value = new MetadataValue(metadata_element, metadata_value_tree_node);
	    metadata_value.setIsAccumulatingMetadata(true);		
	    (new AppendMetadataTask(metadata_value)).run();
	}

	// Metadata value removed
	else if (!old_metadata_value.equals("") && new_metadata_value.equals("")) {
	    (new RemoveMetadataTask(metadata_value_table_entry)).run();
	}

	// Metadata value replaced
	else {
	    MetadataValueTreeNode metadata_value_tree_node = metadata_element.addMetadataValue((String) new_metadata_value);
	    MetadataValue metadata_value = new MetadataValue(metadata_element, metadata_value_tree_node);
	    metadata_value.setIsAccumulatingMetadata(!metadata_value_table_entry.isInheritedMetadata());
	    (new ReplaceMetadataTask(metadata_value_table_entry, metadata_value)).run();
	}
    }


    private class AppendMetadataTask
	// extends Thread
    {
	private MetadataValue metadata_value = null;

	private AppendMetadataTask(MetadataValue metadata_value)
	{
	    this.metadata_value = metadata_value;
	}

	public void run()
	{
	    try {
		// Edit metadata.xml files to add the metadata
		MetadataXMLFileManager.addMetadata(file_nodes, metadata_value);
	    }
	    catch (Exception exception) {
		// We need to catch any exceptions here so the interface is unlocked below
		DebugStream.printStackTrace(exception);
	    }

	    // Operation finished, so turn busy cursor off and unlock interface
	    Gatherer.g_man.wait(false);
	}
    }


    private class ReplaceMetadataTask
	// extends Thread
    {
	private MetadataValueTableEntry selected_metadata_value_table_entry = null;
	private MetadataValue metadata_value = null;

	private ReplaceMetadataTask(MetadataValueTableEntry selected_metadata_value_table_entry, MetadataValue metadata_value)
	{
	    this.selected_metadata_value_table_entry = selected_metadata_value_table_entry;
	    this.metadata_value = metadata_value;
	}

	public void run()
	{
	    try {
		// Edit metadata.xml files to replace the metadata
		MetadataXMLFileManager.replaceMetadata(file_nodes, selected_metadata_value_table_entry, metadata_value);
	    }
	    catch (Exception exception) {
		// We need to catch any exceptions here so the interface is unlocked below
		DebugStream.printStackTrace(exception);
	    }

	    // Operation finished, so turn busy cursor off and unlock interface
	    Gatherer.g_man.wait(false);
	}
    }


    private class RemoveMetadataTask
	// extends Thread
    {
	private MetadataValueTableEntry selected_metadata_value_table_entry = null;

	private RemoveMetadataTask(MetadataValueTableEntry selected_metadata_value_table_entry)
	{
	    this.selected_metadata_value_table_entry = selected_metadata_value_table_entry;
	}

	public void run()
	{
	    try {
		// Edit metadata.xml files to remove the metadata
		MetadataXMLFileManager.removeMetadata(file_nodes, selected_metadata_value_table_entry);
	    }
	    catch (Exception exception) {
		// We need to catch any exceptions here so the interface is unlocked below
		DebugStream.printStackTrace(exception);
	    }

	    // Operation finished, so turn busy cursor off and unlock interface
	    Gatherer.g_man.wait(false);
	}
    }


    private class MetadataValueTableModelBuilder
    {
	public void run()
	{
	    // System.err.println("Building MetadataValueTableModel...");

	    // Build a list of MetadataValueTableEntries that represent the metadata asssigned to the selected files
	    boolean hid_extracted_metadata = false;
	    ArrayList metadata_elements_seen = new ArrayList();

	    // Process each of the selected files in turn
	    for (int i = 0; i < file_nodes.length; i++) {
		File current_file = file_nodes[i].getFile();

		// Get the metadata assigned to this file
		ArrayList assigned_metadata = MetadataXMLFileManager.getMetadataAssignedToFile(current_file);
		for (int j = 0; j < assigned_metadata.size(); j++) {
		    MetadataValue metadata_value = (MetadataValue) assigned_metadata.get(j);
		    MetadataElement metadata_element = metadata_value.getMetadataElement();

		    // Insert this metadata value into the table, unless it already exists (in which case increment its count)
		    insertMetadataValue(metadata_value);

		    // Remember we have seen this metadata element
		    if (metadata_elements_seen.contains(metadata_element) == false) {
			metadata_elements_seen.add(metadata_element);
		    }
		}

		// Get the extracted metadata for this file, if desired
		if (Configuration.get("general.view_extracted_metadata", Configuration.COLLECTION_SPECIFIC) == true) {
		    ArrayList extracted_metadata = DocXMLFileManager.getMetadataExtractedFromFile(current_file);
		    for (int k = 0; k < extracted_metadata.size(); k++) {
			MetadataValue metadata_value = (MetadataValue) extracted_metadata.get(k);

			// Insert this metadata value into the table, unless it already exists (in which case increment its count)
			insertMetadataValue(metadata_value);
		    }
		}
	    }

	    // Make sure each non-extracted metadata element appears in the table (even if blank)
	    ArrayList every_metadata_set_element = MetadataSetManager.getEveryMetadataSetElement();
	    for (int i = 0; i < every_metadata_set_element.size(); i++) {
		MetadataElement metadata_element = (MetadataElement) every_metadata_set_element.get(i);

		// If we haven't seen this metadata element and it isn't extracted, add it now
		if (!metadata_elements_seen.contains(metadata_element) && !metadata_element.isExtractedMetadataElement()) {
		    MetadataValueTableEntry metadata_value_table_entry = new MetadataValueTableEntry(metadata_element, new MetadataValueTreeNode(""));

		    // Blank metadata is common to all selected files (otherwise it wouldn't be blank!)
		    metadata_value_table_entry.setOccurrences(file_nodes.length);

		    // Add it to the table
		    insertMetadataValueTableEntry(metadata_value_table_entry);
		}
	    }

	    // If extracted metadata was hidden, display the warning
	    if (hid_extracted_metadata) {
		showExtractedMetadataWarning();
	    }
	}


	/** Inserts the new metadata value into the table */
	private int insertMetadataValue(MetadataValue metadata_value)
	{
	    return insertMetadataValueTableEntry(new MetadataValueTableEntry(metadata_value));
	}


	/** Inserts the new metadata value table entry into the table */
	private int insertMetadataValueTableEntry(MetadataValueTableEntry metadata_value_table_entry)
	{
	    MetadataElement metadata_element = metadata_value_table_entry.getMetadataElement();

	    // Find the correct place to insert the table entry
	    for (int i = 0; i < metadata_value_table_entries.size(); i++) {
		MetadataValueTableEntry current_metadata_value_table_entry = (MetadataValueTableEntry) metadata_value_table_entries.get(i);
		int element_comparison = MetadataSetManager.compareMetadataElements(current_metadata_value_table_entry.getMetadataElement(), metadata_element);

		// We've found the right element, so check if the value already exists
		if (element_comparison == 0) {
		    int value_comparison = current_metadata_value_table_entry.compareTo(metadata_value_table_entry);
		    if (value_comparison == 0) {
			// Entry already exists, so increment count (except for blank entries)
			if (!metadata_value_table_entry.getFullValue().equals("")) {
			    current_metadata_value_table_entry.anotherOccurrence();
			}
			return i;
		    }
		}

		// Found insertion point
		if (element_comparison > 0) {
		    metadata_value_table_entries.add(i, metadata_value_table_entry);
		    fireTableRowsInserted(i, i);
		    return i;
		}
	    }

	    // Must go at the end of the table
	    metadata_value_table_entries.add(metadata_value_table_entry);
	    fireTableRowsInserted(metadata_value_table_entries.size() - 1, metadata_value_table_entries.size() - 1);
	    return metadata_value_table_entries.size() - 1;
	}


	private void showExtractedMetadataWarning()
	{
	    Runnable task = new Runnable() {
		    public void run() {
			WarningDialog dialog = new WarningDialog("warning.ExtractedMetadata", Dictionary.get("ExtractedMetadata.Title"), Dictionary.get("ExtractedMetadata.Message"), null, false);
			dialog.display();
			dialog.dispose();
			dialog = null;
		    }
		};
	    SwingUtilities.invokeLater(task);
	}
    }
}
