/*
 * Conditions Of Use
 *
 * This software was developed by employees of the National Institute of
 * Standards and Technology (NIST), an agency of the Federal Government.
 * Pursuant to title 15 Untied States Code Section 105, works of NIST
 * employees are not subject to copyright protection in the United States
 * and are considered to be in the public domain.  As a result, a formal
 * license is not needed to use the software.
 *
 * This software is provided by NIST as a service and is expressly
 * provided "AS IS."  NIST MAKES NO WARRANTY OF ANY KIND, EXPRESS, IMPLIED
 * OR STATUTORY, INCLUDING, WITHOUT LIMITATION, THE IMPLIED WARRANTY OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, NON-INFRINGEMENT
 * AND DATA ACCURACY.  NIST does not warrant or make any representations
 * regarding the use of the software or the results thereof, including but
 * not limited to the correctness, accuracy, reliability or usefulness of
 * the software.
 *
 * Permission to use this software is contingent upon your acceptance
 * of the terms of this agreement
 *
 * .
 *
 */
/*******************************************************************************
 * Product of NIST/ITL Advanced Networking Technologies Division (ANTD).        *
 *******************************************************************************/
package gov.nist.core;

import java.util.Map.Entry;

/*
 * Bug reports and fixes: Kirby Kiem, Jeroen van Bemmel.
 */

/**
 * Generic structure for storing name-value pairs.
 *
 * @version 1.2
 *
 * @author M. Ranganathan <br/>
 *
 *
 *
 */
public class NameValue extends GenericObject implements Entry<String,String> {

    private static final long serialVersionUID = -1857729012596437950L;

    protected boolean isQuotedString;

    protected final boolean isFlagParameter;

    private String separator;

    private String quotes;

    private String name;

    private Object value;

    public NameValue() {
        name = null;
        value = "";
        separator = Separators.EQUALS;
        this.quotes = "";
        this.isFlagParameter = false;
    }

    /**
     * New constructor, taking a boolean which is set if the NV pair is a flag
     *
     * @param n
     * @param v
     * @param isFlag
     */
    public NameValue(String n, Object v, boolean isFlag) {

        // assert (v != null ); // I dont think this assertion is correct mranga

        name = n;
        value = v;
        separator = Separators.EQUALS;
        quotes = "";
        this.isFlagParameter = isFlag;
    }

    /**
     * Original constructor, sets isFlagParameter to 'false'
     *
     * @param n
     * @param v
     */
    public NameValue(String n, Object v) {
        this(n, v, false);
    }

    /**
     * Set the separator for the encoding method below.
     */
    public void setSeparator(String sep) {
        separator = sep;
    }

    /**
     * A flag that indicates that doublequotes should be put around the value
     * when encoded (for example name=value when value is doublequoted).
     */
    public void setQuotedValue() {
        isQuotedString = true;
        this.quotes = Separators.DOUBLE_QUOTE;
    }

    /**
     * Return true if the value is quoted in doublequotes.
     */
    public boolean isValueQuoted() {
        return isQuotedString;
    }

    public String getName() {
        return name;
    }

    public Object getValueAsObject() {
        return isFlagParameter ? "" : value; // never return null for flag
                                                // params
    }

    /**
     * Set the name member
     */
    public void setName(String n) {
        name = n;
    }

    /**
     * Set the value member
     */
    public void setValueAsObject(Object v) {
        value = v;
    }

    /**
     * Get the encoded representation of this namevalue object. Added
     * doublequote for encoding doublequoted values.
     *
     * Bug: RFC3261 stipulates that an opaque parameter in authenticate header
     * has to be:
     * opaque              =  "opaque" EQUAL quoted-string
     * so returning just the name is not acceptable. (e.g. LinkSys phones
     * are picky about this)
     *
     * @since 1.0
     * @return an encoded name value (eg. name=value) string.
     */
    public String encode() {
        return encode(new StringBuffer()).toString();
    }

    public StringBuffer encode(StringBuffer buffer) {
        if (name != null && value != null && !isFlagParameter) {
            if (GenericObject.isMySubclass(value.getClass())) {
                GenericObject gv = (GenericObject) value;
                buffer.append(name).append(separator).append(quotes);
                gv.encode(buffer);
                buffer.append(quotes);
                return buffer;
            } else if (GenericObjectList.isMySubclass(value.getClass())) {
                GenericObjectList gvlist = (GenericObjectList) value;
                buffer.append(name).append(separator).append(gvlist.encode());
                return buffer;
            } else if ( value.toString().length() == 0) {
                // opaque="" bug fix - pmusgrave
                /*if (name.toString().equals(gov.nist.javax.sip.header.ParameterNames.OPAQUE))
                    return name + separator + quotes + quotes;
                else
                    return name;*/
                if ( this.isQuotedString ) {
                    buffer.append(name).append(separator).append(quotes).append(quotes);
                    return buffer;
                } else {
                    buffer.append(name).append(separator); // JvB: fix, case: "sip:host?subject="
                    return buffer;
                }
            } else {
                buffer.append(name).append(separator).append(quotes).append(value.toString()).append(quotes);
                return buffer;
            }
        } else if (name == null && value != null) {
            if (GenericObject.isMySubclass(value.getClass())) {
                GenericObject gv = (GenericObject) value;
                gv.encode(buffer);
                return buffer;
            } else if (GenericObjectList.isMySubclass(value.getClass())) {
                GenericObjectList gvlist = (GenericObjectList) value;
                buffer.append(gvlist.encode());
                return buffer;
            } else {
                buffer.append(quotes).append(value.toString()).append(quotes);
                return buffer;
            }
        } else if (name != null && (value == null || isFlagParameter)) {
            buffer.append(name);
            return buffer;
        } else {
            return buffer;
        }
    }

    public Object clone() {
        NameValue retval = (NameValue) super.clone();
        if (value != null)
            retval.value = makeClone(value);
        return retval;
    }

    /**
     * Equality comparison predicate.
     */
    public boolean equals(Object other) {
        if (other == null ) return false;
        if (!other.getClass().equals(this.getClass()))
            return false;
        NameValue that = (NameValue) other;
        if (this == that)
            return true;
        if (this.name == null && that.name != null || this.name != null
                && that.name == null)
            return false;
        if (this.name != null && that.name != null
                && this.name.compareToIgnoreCase(that.name) != 0)
            return false;
        if (this.value != null && that.value == null || this.value == null
                && that.value != null)
            return false;
        if (this.value == that.value)
            return true;
        if (value instanceof String) {
            // Quoted string comparisions are case sensitive.
            if (isQuotedString)
                return this.value.equals(that.value);
            String val = (String) this.value;
            String val1 = (String) that.value;
            return val.compareToIgnoreCase(val1) == 0;
        } else
            return this.value.equals(that.value);
    }

    /*
     * (non-Javadoc)
     * @see java.util.Map$Entry#getKey()
     */
    public String getKey() {

        return this.name;
    }

    /*
     * (non-Javadoc)
     * @see java.util.Map$Entry#getValue()
     */
    public String getValue() {

        return  value == null ? null : this.value.toString();
    }

    /*
     * (non-Javadoc)
     * @see java.util.Map$Entry#setValue(java.lang.Object)
     */
    public String setValue(String value) {
        String retval = this.value == null ? null : value;
        this.value = value;
        return retval;

    }
    
    @Override
    public int hashCode() {
        return this.encode().toLowerCase().hashCode();
    }

}