/**
 *#########################################################################
 *
 * 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.
 *
 * Author: John Thompson, Greenstone Digital Library, University of Waikato
 *
 * Copyright (C) 1999 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.cdm;

import java.awt.BorderLayout;
import java.awt.Color;
import java.awt.Component;
import java.awt.Dimension;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.File;
import java.util.ArrayList;

import javax.swing.BorderFactory;
import javax.swing.BoxLayout;
import javax.swing.JCheckBox;
import javax.swing.JComboBox;
import javax.swing.JComponent;
import javax.swing.JOptionPane;
import javax.swing.JPanel;
import javax.swing.JScrollPane;

import org.greenstone.gatherer.Configuration;
import org.greenstone.gatherer.Dictionary;
import org.greenstone.gatherer.Gatherer;
import org.greenstone.gatherer.collection.CollectionManager;
import org.greenstone.gatherer.gui.DesignPaneHeader;
import org.greenstone.gatherer.gui.GComboBox;
import org.greenstone.gatherer.metadata.MetadataElement;
import org.greenstone.gatherer.metadata.MetadataSet;
import org.greenstone.gatherer.metadata.MetadataSetManager;
import org.greenstone.gatherer.util.Utility;

/**
 * This manager implements the "Depositor Metadata" section in GLI Format Panel, where 
 * users can customize which metadata elements will be used to describe an item when deposited in the Depositor.
 *      
 * @author Anna Huang, Greenstone Digital Library, University of Waikato
 * @version 1.0
 */
public class DepositorMetadataManager {
    /** Display controls of the element */
    static private Dimension LABEL_SIZE = new Dimension(150, 15);
    static private Dimension ROW_SIZE = new Dimension(400, 20);
    static private Dimension MAX_ROW_SIZE = new Dimension(1000, 30);
    
    /** Available types, corresponds to the HTML input types */ 
    static private String[] TYPEOPTIONS = new String[]{"text", "textarea"};
    /** Default elements to be used in Depositor */
    static private String DEFAULT_METADATA_ELEMENTS = "dc.Title, dc.Creator, dc.Description";
    /** Definition attribute of a metadata element */ 
    static private String DEFINITION = "definition";
    
    /** Display controls */
    private Control controls;	
    /** List of MetadataElementEntry objects, each represents a metadata element */
    private ArrayList metadata_checklist_model = null;	
    /** Collection configuration file manager */ 
    private CollectionMetaManager collmeta_manager = CollectionDesignManager.collectionmeta_manager;
    
    public DepositorMetadataManager() {
    }
    
    /** Destructor. */
    public void destroy() {
	if (controls != null) {
	    controls.destroy();
	    controls = null;
	}
	if(metadata_checklist_model != null) {
	    metadata_checklist_model.clear();
	    metadata_checklist_model = null;
	}
    }
		
    public Control getControls() {
	if (controls == null) {
	    controls = new DepositorControl();
	}
	return controls;
    }
	
    /** Called when the detail mode has changed which in turn may cause several design elements to be available/hidden
     * @param mode the new mode as an int
     */
    public void modeChanged(int mode) {
    }
	
    /**
     * The implementation of this display control is similar to the <code>ArgumentConfiguration</code>, 
     * where each component in the central panel is another JPanel component consists of a checkbox and a drop down list. 
     */
    private class DepositorControl
	extends JPanel
	implements Control {		
		
	private JPanel central_pane = null;

	public DepositorControl() {
	    super();
            this.setComponentOrientation(Dictionary.getOrientation());
	    JPanel header_panel = new DesignPaneHeader("CDM.GUI.DepositorMetadata", "depositormetadatasettings");
			
	    // load all possible metadata sets, and existing configuration
	    buildModel();
	    
	    central_pane = new JPanel();
            central_pane.setComponentOrientation(Dictionary.getOrientation());
	    central_pane.setBorder(BorderFactory.createEmptyBorder(5,5,5,5));
	    central_pane.setLayout(new BoxLayout(central_pane, BoxLayout.Y_AXIS));	
									
	    // build the central pane, ie. create gui component for each metadata element 
	    buildPane();		

	    JPanel collection_checklist_pane = new JPanel();
            collection_checklist_pane.setComponentOrientation(Dictionary.getOrientation());
	    collection_checklist_pane.setBorder(BorderFactory.createEmptyBorder(5,0,0,0));
	    collection_checklist_pane.setLayout(new BorderLayout());
	    collection_checklist_pane.add(new JScrollPane(central_pane), BorderLayout.CENTER);

	    setBorder(BorderFactory.createEmptyBorder(0,5,0,0));
	    setLayout(new BorderLayout());
	    add(header_panel, BorderLayout.NORTH);
	    add(collection_checklist_pane, BorderLayout.CENTER);
	}
		
	public void destroy() {
	}

	public void gainFocus() {
	}
		
	public void loseFocus() {			
	    StringBuffer converted_javascript = new StringBuffer();
			
	    // get currently selected metadata elements and create the javascript associative array variable
	    if (metadata_checklist_model != null) {
		int size = metadata_checklist_model.size();
		for(int i = 0; i < size; i++) {				
		    MetadataElementEntry entry = (MetadataElementEntry) metadata_checklist_model.get(i);
					
		    if (entry.isSelected()) {					
			StringBuffer element = new StringBuffer();
			element.append("{\"name\":\"").append(entry.name).append("\",");
			element.append("\"label\":\"").append(entry.label).append("\",");
			element.append("\"tooltip\":\"").append(entry.name).append(": ").append(entry.tooltip).append("\",");
			element.append("\"type\":\"").append(entry.getType()).append("\"}");
			if(converted_javascript.length() != 0) {
			    converted_javascript.append(", ");
			}
			converted_javascript.append(element);
			element = null;
		    }				
		}
				
		// if none of the elements were selected, use the default setting				
		if (converted_javascript.length() == 0) {
		    System.err.println("Error: DepositorMetadataMananger should have at least one selected metadata element.");
		}

		// save to the configuration file
		CollectionMeta depositor_metadata_meta = collmeta_manager.getMetadatum("depositormetadata", true);
		depositor_metadata_meta.setValue(converted_javascript.toString());			
	    }		
	}
		
	private void buildModel()
	{
	    metadata_checklist_model = new ArrayList();
	    String current_coll_name = CollectionManager.getLoadedCollectionName();
			
	    // get the elements that have already been used in depositor
	    // add the "depositormetadata" meta if not found in the configuration file
	    CollectionMeta depositor_metadata_meta = collmeta_manager.getMetadatum("depositormetadata", true);			
	    String selected_metadata_javascript = depositor_metadata_meta.getValue(true);
			
	    // load all MDSets in the collection, ie. .mds files in the collection's metadata directory
	    String file_name = Gatherer.getCollectDirectoryPath() + File.separator + current_coll_name + File.separator + "metadata";
	    ArrayList metadata_sets = MetadataSetManager.listMetadataSets(new File(file_name));
	    if (metadata_sets != null) {
	      // unload the ex mds
	      for (int i = 0, j = metadata_sets.size(); i < j; i++) {
		if (((MetadataSet) metadata_sets.get(i)).getNamespace().equals("ex")) {
		  metadata_sets.remove(i);
		  break;
		}
	      }
	    }
	    
	    if (metadata_sets == null) {
	      metadata_sets = new ArrayList();
	    }
	    if (metadata_sets.size() == 0) {
	      // load dublin core
		file_name = org.greenstone.gatherer.gems.MetadataSetManager.getGLIMetadataDirectoryPath();
		file_name += "dublin.mds";
		File dublin_mds_file = new File(file_name);
		if (dublin_mds_file.exists()) {
		  MetadataSetManager.loadMetadataSet(dublin_mds_file);	
		  MetadataSet dc = MetadataSetManager.getMetadataSet("dc");
		  metadata_sets.add(dc);
		}
	    }
	    // if we are still empty :-(
	    if (metadata_sets.size() == 0) {
		System.err.println("Error: DepositorMetadataMananger can't find any valid metadata set files.");
		return;
	    }			
			
	    // load all the metadata elements
	    for (int i = 0, j = metadata_sets.size(); i < j; i++) {
		MetadataSet metadata_set = (MetadataSet) metadata_sets.get(i);				
		ArrayList elements = metadata_set.getMetadataSetElements(); 
		
		for (int k = 0; elements != null && k < elements.size(); k++) {
		    MetadataElement element = (MetadataElement) elements.get(k);
		    // all names are language independent at the moment. 
		    MetadataElementEntry entry = new MetadataElementEntry (element.getFullName(), element.getName(), false);
		    entry.setToolTip(element.getAttribute(DEFINITION, "en"));
		    metadata_checklist_model.add(entry);
		}
	    }	
	    
	    if (!selected_metadata_javascript.equals("")) {
		for (int i = 0, j = metadata_checklist_model.size(); i < j; i++) {
		    MetadataElementEntry entry = (MetadataElementEntry) metadata_checklist_model.get(i);
		    
		    String temp = "\"name\":\"" + entry.name + "\"";
		    if (selected_metadata_javascript.indexOf(temp) != -1) {
			entry.setSelected(true);
			entry.type = getValueFromJSAssociatedArray(selected_metadata_javascript, entry.name, "type");						
		    }
		    temp = null;
		}	
	    } else {				
		// use default elements				
		for (int i = 0, j = metadata_checklist_model.size(); i < j; i++) {
		    MetadataElementEntry entry = (MetadataElementEntry) metadata_checklist_model.get(i);
		    if (DEFAULT_METADATA_ELEMENTS.indexOf(entry.name) != -1) {
			entry.setSelected(true);
			entry.type = TYPEOPTIONS[0];
		    }
		}
	    }					
	}	
	
	private void buildPane() {
	    // use different background color for two adjacent metadata sets
	    String current_namespace = null;
	    String current_bgcolor_name = null;
	    for (int i = 0, j = metadata_checklist_model.size(); i < j; i++) {
		MetadataElementEntry entry = (MetadataElementEntry) metadata_checklist_model.get(i);
		
		if (current_namespace == null) {
		    current_namespace = entry.namespace;
		    current_bgcolor_name = "coloring.collection_tree_background";
		} else {
		    if (!entry.namespace.equals(current_namespace)) {
			current_namespace = entry.namespace;
			current_bgcolor_name = (current_bgcolor_name == "coloring.collection_tree_background") ? "coloring.collection_heading_background" : "coloring.collection_tree_background";
		    }
		}			
		
		if (entry.getGUIControl() == null) {
		    MetadataElementControl element_control = entry.initGUIControl(current_bgcolor_name);					
		    central_pane.add(element_control, BorderLayout.NORTH);
		}				
	    }
	    
	}
		
	/**
	 * Retrieve the value for the specified element in the given javascript associative array expression
	 *  
	 * @param text
	 * @param identifier
	 * @param elem_name
	 * @return
	 */
	private String getValueFromJSAssociatedArray(String text, String identifier, String elem_name) {
	    int p = text.indexOf(elem_name, text.indexOf(identifier));
	    if (p == -1) {
		return null;
	    }
	    p = text.indexOf("\":\"", p);
	    if (p == -1) {
		return null;
	    }
	    p += 3;
			
	    return text.substring(p, text.indexOf("\"", p));			
	}
    }
	
    private class MetadataElementControl extends JPanel {
	
	private JCheckBox enabled = null;
	private JComponent value_control = null;		
		
	public MetadataElementControl (MetadataElementEntry entry, String background_color_name) {
            
            this.setComponentOrientation(Dictionary.getOrientation());
	    String tip = "<html>" + entry.tooltip + "</html>";
	    tip = Utility.formatHTMLWidth(tip, 80);
	    
	    setBackground(Configuration.getColor(background_color_name, false));
	    setBorder(BorderFactory.createEmptyBorder(0,0,0,0));
	    setLayout(new BorderLayout());
	    setPreferredSize(ROW_SIZE);
	    setMaximumSize(MAX_ROW_SIZE);
			
	    // the checkbox component
	    enabled = new JCheckBox(entry.name);
	    enabled.setComponentOrientation(Dictionary.getOrientation());
	    enabled.setSelected(entry.selected);
	    enabled.setOpaque(false);
	    enabled.setPreferredSize(LABEL_SIZE);
	    enabled.setToolTipText(entry.tooltip);
	    add(enabled, BorderLayout.LINE_START);
			
	    // the drop down list component
	    ArrayList option_list = new ArrayList();
	    for (int i = 0; i < TYPEOPTIONS.length; i++) {
		option_list.add(TYPEOPTIONS[i]);
	    }
	    value_control = new GComboBox(option_list.toArray(), false, false);
	    value_control.setComponentOrientation(Dictionary.getOrientation());
	    selectValue((JComboBox)value_control, entry.type); 
	    value_control.setOpaque(true);			
	    if (entry.selected) { 
		value_control.setBackground(Color.white);
		value_control.setEnabled(true);
	    } else {
		value_control.setBackground(Color.lightGray);
		value_control.setEnabled(false);
		value_control.setVisible(false);
	    }							
	    add(value_control, BorderLayout.CENTER);
	    
	    enabled.addActionListener(new EnabledListener(value_control));
	}
				
	private boolean selectValue(JComboBox combobox, String target) {
	    if (target == null) {
		combobox.setSelectedIndex(0);
		return false;
	    }
			
	    for (int i = 0; i < combobox.getItemCount(); i++) {				
		if (combobox.getItemAt(i).toString().equals(target)) {
		    combobox.setSelectedIndex(i);
		    return true;
		}
	    }
	    return false;
	}
		
	private class EnabledListener
	    implements ActionListener {			
	    private JComponent target = null;
	    
	    public EnabledListener(JComponent target) {
		this.target = target;
	    }
	    
	    public void actionPerformed(ActionEvent event) {
		JCheckBox source = (JCheckBox)event.getSource();
		
		// check at least one of the elements should be ticked
		checkTickedElements(source);
				
		// update the status of the drop-down list component associated with the current checkbox 
		if (this.target == null) {
		    return;
		}				
				
		if(source.isSelected()) {
		    target.setBackground(Color.white);
		    target.setEnabled(true);
		    target.setVisible(true);
		}
		else {
		    target.setBackground(Color.lightGray);
		    target.setEnabled(false);
		    target.setVisible(false);
		}				
	    }
			
	    private void checkTickedElements(JCheckBox source) {
		if (source.isSelected()) {
		    return;
		}
				
		boolean b = false;
		for (int i = 0, j = metadata_checklist_model.size(); i < j; i++) {
		    if (((MetadataElementEntry) metadata_checklist_model.get(i)).gui_object.enabled.isSelected()) {
			b = true;
			break;
		    }
		}
				
		// there must be at least one element selected in the list 
		if (b == false) {
		    Object[] options = {Dictionary.get("General.OK")};
		    JOptionPane.showOptionDialog(Gatherer.g_man, Dictionary.get("CDM.DepositorMetadataManager.Warning"), Dictionary.get("General.Warning"), JOptionPane.DEFAULT_OPTION, JOptionPane.WARNING_MESSAGE, null, options, options[0]);
		    
		    source.setSelected(true);
		}
	    }
	}
	
    }
	
    private class MetadataElementEntry implements Comparable {
	/** namespace, used to distinguish different metadata sets and use different colors when displaying */
	String namespace = null;
	/** identifier, with namespace */
	String name = null;
	/** language independent label, without namespace, used for display in the depositor web page, eg. Title */
	String label = null;	
	/** type of the element: text or textarea */
	String type = null;
	/** description of the element, used in web page */
	String tooltip = null;		
	/** whether current element is selected or not */
	boolean selected ;
	/** GUI component object associated with this element */ 
	MetadataElementControl gui_object = null;
		
	/**
	 * Constructor
	 * @param name Language independent identifier of the element with namespace, eg. dc.Title
	 * @param label Language independent label of the element, without namespace, eg. Title 
	 * @param selected Whether the current element has been selected as depositor metadata element 
	 */
	public MetadataElementEntry(String name, String label, boolean selected) {
	    this.name = name;
	    this.label = label;
	    this.selected = selected;
	    
	    if (name.indexOf(".") != -1) {
		namespace = name.substring(0, name.indexOf("."));
	    }
	}
		
	/**
	 * Initiate the <code>MetadataElementControl<code> GUI component object associated with this element, with the specified background color  
	 * @param background_color_name System configured color name for the background, eg. "coloring.collection_heading_background", or "coloring.collection_tree_background"
	 * @return the initialized component object
	 */
	public MetadataElementControl initGUIControl (String background_color_name) {
	    gui_object = new MetadataElementControl(this, background_color_name);
	    return gui_object;
	}
	
	/**
	 * Get the associated GUI component
	 * @return
	 */
	public MetadataElementControl getGUIControl() {
	    return gui_object;
	}
		
	/**
	 * Set selected
	 * @param b
	 */
	public void setSelected(boolean b) {
	    this.selected = b;
	}
		
	public boolean isSelected() {
	    return gui_object.enabled.isSelected();
	}
		
	/**
	 * Get the selected type for the element
	 * @return type
	 */
	public String getType() {
	    return ((JComboBox) gui_object.value_control).getSelectedItem().toString();
	}
		
	public void setToolTip (String tip) {
	    if (tip == null) {
		tooltip = "";
		return;
	    }
	    this.tooltip = tip;
	}

	public int compareTo(Object obj) {
	    return compareTo((MetadataElementEntry) obj);
	}
		
	public int compareTo(MetadataElementEntry obj) {
	    return this.name.compareTo(obj.name);
	}
	
    }
    
}
