
package org.biomoby.shared.data;

import org.biomoby.shared.*;
import org.biomoby.shared.parser.MobyTags;
import org.w3c.dom.*;
import java.math.*;
import java.util.GregorianCalendar;  // with apologies to the Julians among us...

/**
 * A class that holds and displays secondary input data to a service.
 */

public class MobyDataSecondaryInstance extends MobySecondaryData implements MobyDataInstance, Comparable{
    protected String dataValue = null;
    protected int xmlMode = MobyDataInstance.CENTRAL_XML_MODE;
    public static final String ELEMENT_NAME = "Parameter";
    public static final String VALUE_NAME = "Value";

    public MobyDataSecondaryInstance(MobySecondaryData type, String value){
	this(type);
	dataValue = value;
    }

    /**
     * Basically clones the input parameter, and sets the value to the default value.
     */
    public MobyDataSecondaryInstance(MobySecondaryData type){
	super(type.getName());
	setId(type.getId());
	try{
	    setDataType(type.getDataType());
	}catch(Exception e){
	    e.printStackTrace();
	}
	setAllowedValues(type.getAllowedValues());
	setDefaultValue(type.getDefaultValue());
	setValue(type.getDefaultValue());
	setMinValue(type.getMinValue());
	setMaxValue(type.getMaxValue());
	setDescription(type.getDescription());
    }

    /**
     * Create an instance from XML input.  Unfortunately, under the current scheme
     * we have no way of knowing what the type of the parameter is (int, string, etc.).
     * By default we will treat it as a string, as this is the most lenient.
     */
    public MobyDataSecondaryInstance(Element objectTag) throws MobyException{
	// What is the parameter name?
	super(MobyDataObject.getName(objectTag));

	if(getName() == null){
	    throw new MobyException("Anonymous secondary parameters are not allowed (need articleName), input was :\n" + 
				    objectTag);
	}
	if(getName().length() == 0){
	    throw new MobyException("Secondary parameters with blank articleNames are not allowed, input was: " + objectTag);
	}
	try{
	    setDataType(MobySecondaryData.STRING_TYPE);
	} catch(Exception e){
	    e.printStackTrace();
	}

	if(objectTag == null){
	    return;
	}

	String objectClass = objectTag.getLocalName();
	if(!MobyTags.PARAMETER.equals(objectClass)){
	    throw new MobyException("While creating a secondary parameter instance from XML: " +
				    "the passed in element (" +objectClass + ") was not a " + MobyTags.PARAMETER);
	}
	NodeList values = MobyPrefixResolver.getChildElements(objectTag, MobyTags.VALUE);
	if(values.getLength() == 0){
	    throw new MobyException("While creating a secondary parameter instance from XML: " +
				    "No " + VALUE_NAME + " element in the namespace " + 
				    MobyPrefixResolver.MOBY_XML_NAMESPACE + " was found");
	}
	if(values.getLength() > 1){
	    throw new MobyException("While creating a secondary parameter instance from XML: " +
				    "More than one " + VALUE_NAME + " element ("+
				    values.getLength() +") in the namespace " + 
				    MobyPrefixResolver.MOBY_XML_NAMESPACE + " was found");
	}
	setValue(MobyDataObject.getTextContents((Element) values.item(0)));
    }

    public boolean asBoolean(){
	return dataValue.equalsIgnoreCase("true") || dataValue.equals("1") || dataValue.equalsIgnoreCase("T");
    }

    public BigDecimal asFloat(){
	try{
	    return new BigDecimal(dataValue);
	} catch(Exception e){
	    return null;
	}
    }

    public BigInteger asInteger(){
	try{
	    return new BigInteger(dataValue);
	} catch(Exception e){
	    return null;
	}
    }

    public String asString(){
	return dataValue;
    }

    public GregorianCalendar asDateTime(){
	try{
	    return MobyDataDateTime.parseISO8601(dataValue);
	} catch(Exception e){
	    return null;
	}   
    }

    /**
     * For now, all secondary parameters are treated as strings.
     */
    public Object getObject(){
	return getValue();
    }

    public void setValue(String value) throws IllegalArgumentException{
	dataValue = value;
    }

    public String getValue(){
	return dataValue;
    }

    public void setXmlMode(int mode) throws IllegalArgumentException{
        if(mode != MobyDataInstance.CENTRAL_XML_MODE && mode != MobyDataInstance.SERVICE_XML_MODE){
	    throw new IllegalArgumentException("Value passed to setXmlMode was neither " +
					       "MobyDataInstance.CENTRAL_XML_MODE nor MobyDataInstance.SERVICE_XML_MODE");
	}
	xmlMode = mode;
    }

    /**
     * Report whether toXML will produce Central template or service call instance XML.
     *
     * @return one of CENTRAL_XML_MODE or SERVICE_XML_MODE
     */
    public int getXmlMode(){
	return xmlMode;
    }

    /**
     * This method sanitizes parameter data value strings of XML escape characters such as the ampersand (&amp;) and the 
     * less-than sign (&lt;).  WARNING: this method will not escape ampersand in the string "&amp;amp;", 
     * or '&amp;#x26;' style character references.  We will assume that is this case you've probably 
     * already written the string as XML. Also note that null strings are treated as empty strings.
     *
     * WARNING: As of yet, we do not deal with the false escaping of strings containg already-escaped 
     * CDATA sections!
     */
    public String toXML(){
	// Sanitize the data before adding to the XML
	String tmpValue = dataValue;
	if(tmpValue != null){
	    tmpValue = tmpValue.replaceAll("&(?!(amp|#x\\d+);)", "&amp;");
	    tmpValue = tmpValue.replaceAll("<", "&lt;");
	}
	// A null value and an empty string are considered equivalent here
	else{
	    tmpValue = "";
	}

	if(xmlMode == MobyDataInstance.SERVICE_XML_MODE){
	    return "  <" + MobyTags.PARAMETER +
	    " articleName='" + getName() +
	    "'><" + MobyTags.VALUE + ">" + tmpValue + "</" + MobyTags.VALUE + "></" + MobyTags.PARAMETER + ">\n";
	}
	else{
	    return super.toXML();
	}
    }

    /**
     * This method lexically compares in the order of value, ID, name if passed in object is a MobyDataObject.
     * Otherwise it returns -1 (null fields get pushed to the end of the list).
     *
     * Child classes are encouraged to override this method for numerical comparison, etc. where
     * appropriate, falling back to this method if the result is 0.
     */
    public int compareTo(Object o){
	if(o == null || !(o instanceof MobyDataSecondaryInstance)){
	    return -1;
	}
	MobyDataSecondaryInstance mdsi = (MobyDataSecondaryInstance) o;
	if(getName() == null && mdsi.getName() != null){
	    return 1;
	}
	else if(getName() != null && getName().compareTo(mdsi.getName()) != 0){
	    return getName().compareTo(mdsi.getName());
	}
	else{
	    return 0;
	}
    }
}
