/**
 *#########################################################################
 *
 * A component of the Greenstone Librarian Interface (GLI) application, 
 * part of the Greenstone digital library software suite from the New 
 * Zealand Digital Library Project at the University of Waikato, 
 * New Zealand.
 *
 * Author: John Thompson 
 *         Greenstone Project, New Zealand Digital Library
 *         University of Waikato
 *         http://www.nzdl.org
 *
 * Copyright (C) 2004 New Zealand Digital Library, University of Waikato
 *
 * 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.io.*;
import java.util.*;
import org.greenstone.gatherer.Configuration;
import org.greenstone.gatherer.DebugStream;
import org.greenstone.gatherer.Gatherer;
import org.greenstone.gatherer.collection.Collection;
import org.greenstone.gatherer.collection.CollectionManager;
import org.greenstone.gatherer.metadata.MetadataElement;
import org.greenstone.gatherer.metadata.MetadataTools;
import org.greenstone.gatherer.util.StaticStrings;
import org.greenstone.gatherer.util.Utility;
import org.greenstone.gatherer.util.XMLTools;
import org.w3c.dom.*;

/** This class contains all the details about a single argument that can be passed to this plugin, including option lists if the parameters are restricted.
 * @author John Thompson, Greenstone Project, New Zealand Digital Library, University of Waikato
 * @version 2.41 final
 */
public class Argument
    implements Comparable, Serializable {
    /** An element of the argument type enumeration specifying a combobox control. */
    static final public byte ENUM = 0;
    /** An element of the argument type enumeration specifying a checkbox control. */
    static final public byte FLAG = 1;
    /** An element of the argument type enumeration specifying a tree control. */
    static final public byte HIERARCHY = 2;
    /** An element of the argument type enumeration specifying a spinner control. */
    static final public byte INTEGER = 3;
    /** An element of the argument type enumeration specifying a language combobox control. */
    static final public byte LANGUAGE = 4;
    /** An element of the argument type enumeration specifying a list control. */
    static final public byte METADATA = 5;
    /** An element of the argument type enumeration specifying a metadata combobox control. */
    static final public byte METADATUM = 6;
    /** An element of the argument type enumeration specifying a text field. */
    static final public byte STRING = 7;
    /** An element of the argument type enumeration specifying a regular expression text field. */
    static final public byte REGEXP = 8;
    /** An element of the argument type enumeration specifying a metadata set combobox control. */
    static final public byte METADATA_SET_NAMESPACE = 9;

 ///////////kk added the number was 9, I changed it to 10//////////////
  /** An element of the argument type enumeration specifying a text field. */
    static final public byte URL = 10;
    /////////////////////////////////////////////////////////////////

    /** true if this argument should actually be hidden within the GLI. This is important for arguments such as import dir or other location critical arguments. */
    private boolean hidden_gli = false;
    /** <i>true</i> if this argument is required for the applicable script to work properly, <i>false</i> otherwise. */
    private boolean required = false;
    /** The type of this argument. Used to be an int, but bytes are cheaper. */
    private byte type = STRING;
    /** The maximum value an integer based control can have. */
    private int maximum = Integer.MAX_VALUE;
    /** The minimum value an integer based control can have. */
    private int minimum = Integer.MIN_VALUE;
    /** Every argument has a detail mode level at which it becomes available to the user to edit. 
     * @see org.greenstone.gatherer.Configuration
     */
    private int mode_level = Configuration.LIBRARIAN_MODE;
    /** The DOM element this argument is built around, if any. */
    private Element element;
    /** If the argument is of type ENUM then this map holds all the various options. Each entry is an &lt;option value&gt; -&gt; &lt;description&gt; mapping. */
    private ArrayList option_list = null;
    /** A default value for parameter-type arguments. May be a Perl pattern. */
    private String default_value = null;
    /** The text description of this argument parsed from the pluginfo output. */
    private String description = null;
    /** The argument flag as it appears in the command. Also used as the unique identifier of an argument. */
    private String name = null;
    /** The plugin that owns this argument, for the purposes of visualising inheritance. */
    private String owner = null;

    private String display_name = null;

    /** Default Constructor. */
    public Argument() {
    }

    /** Another constructor but this one is a little more interesting as it takes a DOM element.
     * @param element the Element this argument is based around
     */
    public Argument(Element element) {
	this.element = element;
    }

    /** Method to add an element to the option_list.
     * @param name the name value of the option as a String
     * @param desc the description of this options as a String
     */
    public void addOption(String name, String desc) {
	if(type == ENUM && name != null) {
	    if(desc == null) {
		desc = "";
	    }
	    if(option_list == null) {
		option_list = new ArrayList();
	    }
	    option_list.add(new ArgumentOption(name, desc));
	}
    }

    /** Method to compare two arguments for ordering.
     * @param object the argument we are comparing to, as an Object
     * @return an int specifying the argument order, using values as set out in String
     * @see org.greenstone.gatherer.cdm.Argument
     */
    public int compareTo(Object object) {
	if(object instanceof Argument) {
	    return getName().compareTo(((Argument)object).getName());
	}
	else {
	    return toString().compareTo(object.toString());
	}
    }

    /** Create a copy of this argument.
     * @return a newly created Argument with the same details as this one
     */
    public Argument copy() {
	Argument copy = new Argument();
	copy.setDefaultValue(default_value);
	copy.setDescription(description);
	copy.setOptions(option_list);
	copy.setOwner(owner);
	copy.setName(name);
	copy.setRequired(required);
	copy.setType(type);
	copy.setMinimum(minimum);
	copy.setMaximum(maximum);
	copy.setModeLevel(mode_level);
	copy.setHiddenGLI(hidden_gli);
	return copy;
    }

    /** Method to determine if two arguments are equal.
     * @param object the argument to test against, as an Object
     * @return true if the arguments names match, false otherwise
     */
    public boolean equals(Object object) {
	return (compareTo(object) == 0);
    }

    /** Method to retrieve the value of default_value.
     * @return a String containing the default value
     */
    public String getDefaultValue() {
	return default_value;
    }

    /** Method to retrieve this arguments description.
     * @return a String containing the description
     */
    public String getDescription() {
	return description;
    }

    public Element getElement() {
	return element;
    }
    /** Retrieve the upper bound of a range based argument.
     * @return the maximum as an int
     */
    public int getMaximum() {
	return maximum;
    }

    /** Retrieve the lower bound of a range based argument.
     * @return the minimum as an int
     */
    public int getMinimum() {
	return minimum;
    }

    /** Retrieves the mode level at which this argument should become available. Any higher levels should also see this argument.
     * @return the mode level as an int
     */
    public int getModeLevel() {
	return mode_level;
    }

    /** Method to retrieve the value of name.
     * @return a String containing the argument name
     * @see org.greenstone.gatherer.util.StaticStrings#NAME_ATTRIBUTE
     */
    public String getName() {
	if(name == null && element != null) {
	    name = element.getAttribute(StaticStrings.NAME_ATTRIBUTE);
	}
	return name;
    }

    public String getDisplayName() {
	return display_name;
    }


    /** Method to retrieve the option list for this argument.
     * @return a HashMap containing &lt;option value&gt; -&gt; &lt;description&gt; entries
     */
    public ArrayList getOptions() {
	return option_list;
    }

    /** Retrieve the name of the owner of this argument.
     * @return the owners name as a String
     */
    public String getOwner() {
	return owner;
    }
    
    /** Method to determine the type of this argument.
     * @return a byte specifying the type
     */
    public byte getType() {
	return type;
    }

    /** Method to retrieve the value of value.
     * @return the value of value as a String
     * @see org.greenstone.gatherer.Gatherer#c_man
     * @see org.greenstone.gatherer.collection.CollectionManager#getCollection
     */
    public String getValue()
    {
	// Only assigned arguments have values.
	if (element == null) {
	    return null;
	}

	return XMLTools.getValue(element);
    }


    /** Method to determine if this argument has been assigned.
     * @return true if it has, false otherwise
     * @see org.greenstone.gatherer.util.StaticStrings#ASSIGNED_ATTRIBUTE
     * @see org.greenstone.gatherer.util.StaticStrings#TRUE_STR
     */
    public boolean isAssigned() {
	return (element != null && element.getAttribute(StaticStrings.ASSIGNED_ATTRIBUTE).equals(StaticStrings.TRUE_STR));
    }

    /** Determine if this is a custom argument ie one that has been parsed from the config file but doesn't have a matching entry in the argument library.
     * @return true if this argument is a custom, false otherwise
     * @see org.greenstone.gatherer.util.StaticStrings#CUSTOM_ATTRIBUTE
     * @see org.greenstone.gatherer.util.StaticStrings#TRUE_STR
     */
    public boolean isCustomArgument() {
	return (element != null && element.getAttribute(StaticStrings.CUSTOM_ATTRIBUTE).equals(StaticStrings.TRUE_STR));
    }

    /** Determine if this argument is hidden in GLI
     * @return true if the argument is hidden, false otherwise
     */
    public boolean isHiddenGLI() {
	return hidden_gli;
    }

    /** Method to determine of this argument is required for the associated script to work.
     * @return true if this argument is required, false otherwise
     */
    public boolean isRequired() {
	return required;
    }

    /** Method to allow for the activation of arguments that might never have their setValue() method called.
     * @param assigned the desired state as a boolean
     * @see org.greenstone.gatherer.util.StaticStrings#ASSIGNED_ATTRIBUTE
     * @see org.greenstone.gatherer.util.StaticStrings#FALSE_STR
     * @see org.greenstone.gatherer.util.StaticStrings#TRUE_STR
     */
    public void setAssigned(boolean assigned) {
	if(element != null) {
	    element.setAttribute(StaticStrings.ASSIGNED_ATTRIBUTE, (assigned ? StaticStrings.TRUE_STR : StaticStrings.FALSE_STR));
	}
    }

    /** Sets the value of default_value.
     * @param default_value The new value for default_value as a <strong>String</strong>.
     */
    public void setDefaultValue(String default_value) {
	this.default_value = default_value;
    }

    /** Set the value of desc.
     * @param description the new value of desc as a String
     */
    public void setDescription(String description) {
	this.description = description;
    }

    /** Set the element this argument should be based upon.
     * @param element the Element
     */
    public void setElement(Element element) {
	this.element = element;
    }

    /** Mark this argument as being hidden in GLI. */
    public void setHiddenGLI(boolean hidden) {
	this.hidden_gli = hidden;
    }

    /** Set the upper bound for a range type argument.
     * @param maximum the maximum as an int
     */
    public void setMaximum(int maximum) {
	this.maximum = maximum;
    }

    /** Set the lower bound for a range type argument.
     * @param minimum the minimum as an int
     */
    public void setMinimum(int minimum) {
	this.minimum = minimum;
    }

    /** Set the detail mode level where this argument will become available.
     * @param mode_level the mode level as an int
     */
    public void setModeLevel(int mode_level) {
	this.mode_level = mode_level;
    }

    /** Set the value of name.
     * @param name the new value of name as a String
     */
    public void setName(String name) {
	this.name = name;
    }

    /** Sets the value of the options list.
     * @param list the new options list as a HashMap
     */
    public void setOptions(ArrayList list) {
	this.option_list = list;
    }

    /** Set the owner of this argument.
     * @param owner the name of the owner of this argument as a String
     */
    public void setOwner(String owner) {
	this.owner = owner;
    }

    /** Set the value of required.
     * @param required the new value of required as a boolean
     */
    public void setRequired(boolean required) {
	this.required = required;
    }

    /** Set the value of type.
     * @param type the new value of type as an byte
     */
    public void setType(byte type) {
	this.type = type;
    }

    /** Set the value of type, by matching a type to the given string.
     * @param new_type a String which contains the name of a certain argument type
     * @see org.greenstone.gatherer.util.StaticStrings#ENUM_STR
     * @see org.greenstone.gatherer.util.StaticStrings#FLAG_STR
     * @see org.greenstone.gatherer.util.StaticStrings#HIERARCHY_STR
     * @see org.greenstone.gatherer.util.StaticStrings#INT_STR
     * @see org.greenstone.gatherer.util.StaticStrings#LANGUAGE_STR
     * @see org.greenstone.gatherer.util.StaticStrings#METADATA_TYPE_STR
     * @see org.greenstone.gatherer.util.StaticStrings#METADATUM_TYPE_STR
     * @see org.greenstone.gatherer.util.StaticStrings#REGEXP_STR
     */
    public void setType(String new_type) {
	if(new_type.equalsIgnoreCase(StaticStrings.ENUM_STR)) {
	    this.type = ENUM;
	    option_list = new ArrayList();
	}
	else if(new_type.equalsIgnoreCase(StaticStrings.FLAG_STR)) {
	    this.type = FLAG;
	}
	else if(new_type.equalsIgnoreCase(StaticStrings.HIERARCHY_STR)) {
	    this.type = HIERARCHY;
	}
	else if(new_type.equalsIgnoreCase(StaticStrings.INT_STR)) {
	    this.type = INTEGER;
	}
	else if(new_type.equalsIgnoreCase(StaticStrings.LANGUAGE_STR)) {
	    this.type = LANGUAGE;
	}
	else if(new_type.equalsIgnoreCase(StaticStrings.METADATA_TYPE_STR)) {
	    this.type = METADATA;
	}
	else if(new_type.equalsIgnoreCase(StaticStrings.METADATUM_TYPE_STR)) {
	    this.type = METADATUM;
	}
	else if(new_type.equalsIgnoreCase(StaticStrings.REGEXP_STR)) {
	    this.type = REGEXP;
	}
	else {
	    this.type = STRING;
	}
    }

    /** Method to set the value of this argument.
     * @param value the new value for the argument
     * @see org.greenstone.gatherer.Gatherer#println
     */
    public void setValue(String value) {
	if(element != null) {
	    XMLTools.setValue(element, value);
	}
	else {
	    DebugStream.println("Argument.setValue(" + value + ") called on a base Argument.");
	}
    }

    /** Set the values vector to the given values. Currently I just assign the new values, whereas I may later want to implement a deep clone.
     * @param values an ArrayList of values
     * @see org.greenstone.gatherer.Gatherer#println
     */
    public void setValues(ArrayList values) {
	if(element != null) {
	    StringBuffer value = new StringBuffer();
	    int value_length = values.size();
	    for(int i = 0; i < value_length; i++) {
		value.append(values.get(i));
		value.append(StaticStrings.COMMA_CHARACTER);
	    }
	    value.deleteCharAt(value.length() - 1); // Remove last ','
	    XMLTools.setValue(element, value.toString());
	}
	else {
	    DebugStream.println("Argument.setValues([" + values.size() + " items]) called on a base Argument.");
	}
    }

    /** Method for translating the data of this class into a string.
     * @return a String containing a fragment of the total arguments string
     * @see org.greenstone.gatherer.Gatherer#c_man
     * @see org.greenstone.gatherer.collection.CollectionManager#getCollection
     * @see org.greenstone.gatherer.util.StaticStrings#COMMA_CHARACTER
     * @see org.greenstone.gatherer.util.StaticStrings#NAME_ATTRIBUTE
     * @see org.greenstone.gatherer.util.StaticStrings#SPACE_CHARACTER
     * @see org.greenstone.gatherer.util.StaticStrings#SPEECH_CHARACTER
     */
    public String toString()
    {
	StringBuffer text = new StringBuffer("-");

	if (element == null) {
	    return text.toString();
	}

	if (name == null) {
	    name = element.getAttribute(StaticStrings.NAME_ATTRIBUTE);
	}
	text.append(name);

	String value = XMLTools.getValue(element);
	if (value.length() == 0) {
	    return text.toString();
	}

	text.append(StaticStrings.SPACE_CHARACTER);

// 	// Handle metadata elements specially
// 	if (type == METADATA || type == METADATUM) {
// 	    // Tokenize the string
// 	    StringTokenizer tokenizer = new StringTokenizer(value, ",");
// 	    while (tokenizer.hasMoreTokens()) {
// 		String token = tokenizer.nextToken();

// 		MetadataElement metadata_element = MetadataTools.getMetadataElementWithDisplayName(token);
// 		if (metadata_element != null) {
// 		    text.append(metadata_element.getFullName());
// 		}
// 		else {
// 		    text.append(token);
// 		}

// 		if (tokenizer.hasMoreTokens()) {
// 		    text.append(StaticStrings.COMMA_CHARACTER);
// 		}
// 	    }
// 	    return text.toString();
// 	}

	// If the value contains a space, add speech marks
	//   (Except for metadata elements, which won't have spaces when written out to collect.cfg)
	if (value.indexOf(StaticStrings.SPACE_CHARACTER) != -1 && !(type == METADATUM || type == METADATA)) {
	    value = StaticStrings.SPEECH_CHARACTER + value + StaticStrings.SPEECH_CHARACTER;
	}

	text.append(value);
	return text.toString();
    } 

    /** parse the <Option> XML from eg import.pl -xml or pluginfo.pl -xml */
    public void parseXML(Element option) {

	for(Node node = option.getFirstChild(); node != null; node = node.getNextSibling()) {
	    String node_name = node.getNodeName();
	    if(node_name.equals("Name")) {
		setName(XMLTools.getValue(node));
	    }
	    else if(node_name.equals("Desc")) {
		setDescription(XMLTools.getValue(node));
	    }
	    else if(node_name.equals("Type")) {
		setType(XMLTools.getValue(node));
	    }
	    else if(node_name.equals("Default")) {
		setDefaultValue(XMLTools.getValue(node));
	    }
	    else if(node_name.equals("Required")) {
		String v = XMLTools.getValue(node);
		if(v != null && v.equals("yes")) {
		    setRequired(true);
		}
	    }
	    else if(node_name.equals("List")) {
		// Two final loops are required to parse lists.
		for(Node value = node.getFirstChild(); value != null; value = value.getNextSibling()) {
		    if(value.getNodeName().equals("Value")) {
			String key = null;
			String desc = "";
			for(Node subvalue = value.getFirstChild(); subvalue != null; subvalue = subvalue.getNextSibling()) {
			    node_name = subvalue.getNodeName();
			    if(node_name.equals("Name")) {
				key = XMLTools.getValue(subvalue);
			    }
			    else if(node_name.equals("Desc")) {
				desc = XMLTools.getValue(subvalue);
			    }
			}
			if(key != null) {
			    addOption(key, desc);
			}
		    }
		}
	    }
	    else if(node_name.equals("Range")) {
		String range_raw = XMLTools.getValue(node);
		int index = -1;
		if((index = range_raw.indexOf(StaticStrings.COMMA_CHARACTER)) != -1) {
		    if(index > 0) {
			try {
			    String first_number = range_raw.substring(0, index);
			    setMinimum(Integer.parseInt(first_number));
			    first_number = null;
			}
			catch(Exception exception) {
			}
		    }
		    
		    if(index + 1 < range_raw.length()) {
			try {
			    String second_number = range_raw.substring(index + 1);
			    setMaximum(Integer.parseInt(second_number));
			    second_number = null;    
			}
			catch(Exception exception) {
			}
		    }
		}
		// Else it wasn't a valid range anyway, so ignore it
	    }
	    else if(node_name.equals("HiddenGLI")) {
		setHiddenGLI(true);
	    }
	    else if(node_name.equals("ModeGLI")) {
		String mode_level_str = XMLTools.getValue(node);
		try {
		    int mode_level = Integer.parseInt(mode_level_str);
		    setModeLevel(mode_level);
		}
		catch(Exception exception) {
		    DebugStream.println("Exception in Argument.parseXML() - Unexpected but non-fatal");
		    DebugStream.printStackTrace(exception);
		}
	    }
	    
	} // for each option
	
    } // parseXML

    public class ArgumentOption 
	implements Comparable {
	public String name;
	public String description;
	private String text; // cached version

	public ArgumentOption(String name, String desc) {
	    this.name = name;
	    this.description = desc;
	}
	
	public int compareTo(Object obj) {
	    return toString().compareTo(obj.toString());
	}

	public boolean equals(Object obj) {	    
	    return (obj != null && compareTo(obj) == 0);
	}
	
	public String toString() {
	    return name + " "+ StaticStrings.MINUS_CHARACTER + " "+ description;
	}
	
	public String getToolTip() {
	    return Utility.formatHTMLWidth(name+": "+description, 80);
	}
    }
}
