/***********************************************************
Copyright (C) 2004 VeriSign, Inc.

This library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.

This library 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
Lesser General Public License for more details.

You should have received a copy of the GNU Lesser General Public
License along with this library; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA

http://www.verisign.com/nds/naming/namestore/techdocs.html
***********************************************************/
package com.verisign.epp.codec.gen;

// Log4j Imports
import java.io.ByteArrayInputStream;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;

//----------------------------------------------
//
// imports...
//
//----------------------------------------------
// JDK Imports
// Java XML imports
import com.verisign.epp.util.EqualityUtil;

/**
 * Identifies a client-provided element (including XML tag and value) that
 * caused a server error condition. The value can reference to the namespace URI
 * and namespace prefix for encoding the value. The <code>String</code> value
 * needs to be set using XML with the specified namespace prefix. For example,
 * the URI could be "urn:ietf:params:xml:ns:domain-1.0", the prefix could be
 * "domain", and the value could be
 * &lt;domain:name&gt;example.com&lt;/domain:name&gt;.
 *
 * @see com.verisign.epp.codec.gen.EPPResult
 */
public class EPPValue implements EPPCodecComponent {

	/**
	 * Document Builder Factory for creating a parser per value.
	 */
	private static DocumentBuilderFactory factory = null;

	static {
		// Initialize the Document Builder Factory
		factory = DocumentBuilderFactory.newInstance();
		factory.setValidating(false);
	}

	/**
	 * XML root tag name for <code>EPPValue</code>.
	 */
	final static String ELM_NAME = "value";

	/**
	 * Category for logging
	 */
	    private static Logger cat = LoggerFactory.getLogger(EPPValue.class);

	/**
	 * identifies a client-provided element (including XML tag and value) that
	 * caused a server error condition.
	 */
	private String value;

	/**
	 * XML prefix of value child element. For example, "domain". The default is
	 * null for no prefix.
	 */
	private String prefix = null;

	/**
	 * XML namespace of value child element. For example,
	 * "urn:ietf:params:xml:ns:domain-1.0". The default is
	 * "urn:ietf:params:xml:ns:epp-1.0".
	 */
	private String namespace = EPPCodec.NS;

	/**
	 * Default constructor for serialization. The <code>value</code> attribute
	 * must be set before calling <code>encode</code>.
	 */
	public EPPValue() {
		// Do nothing
	}

	/**
	 * Allocates a new <code>EPPValue</code> with only the <code>String</code>
	 * value. The prefix is set to {@code null} and the namespace is to the
	 * "urn:ietf:params:xml:ns:epp-1.0".
	 *
	 * @param aValue
	 *           XML <code>String</code> that identifies a client-provided
	 *           element (including XML tag and value) that caused a server
	 *           error.
	 */
	public EPPValue(String aValue) {
		this.value = aValue;
	}

	/**
	 * Allocates a new <code>EPPValue</code> with all attribute values.
	 *
	 * @param aValue
	 *           XML <code>String</code> that identifies a client-provided
	 *           element (including XML tag and value) that caused a server
	 *           error. For example,
	 *           &lt;domain:name&gt;example.com&lt;/domain:name&gt;.
	 * @param aPrefix
	 *           XML Namespace prefix. For example, "domain". Set to {@code null}
	 *           for no namespace prefix.
	 * @param aNamespace
	 *           XML Namespace URI. For example,
	 *           "urn:ietf:params:xml:ns:domain-1.0". If set to {@code null} the
	 *           default of "urn:ietf:params:xml:ns:epp-1.0" is used.
	 * 
	 */
	public EPPValue(String aValue, String aPrefix, String aNamespace) {
		this.value = aValue;
		this.prefix = aPrefix;
		this.setNamespace(aNamespace);
	}

	/**
	 * Gets XML <code>String</code> that identifies a client-provided element
	 * (including XML tag and value) that caused a server error.
	 *
	 * @return XML <code>String</code> value using namespace prefix.
	 */
	public String getValue() {
		return this.value;
	}

	/**
	 * Sets XML <code>String</code> that identifies a client-provided element
	 * (including XML tag and value) that caused a server error.
	 *
	 * @param aValue
	 *           XML <code>String</code> that identifies a client-provided
	 *           element (including XML tag and value) that caused a server
	 *           error. For example,
	 *           &lt;domain:name&gt;example.com&lt;/domain:name&gt;.
	 */
	public void setValue(String aValue) {
		this.value = aValue;
	}

	/**
	 * Gets the XML prefix of the client element. For example, "domain".
	 *
	 * @return XML prefix if defined; {@code null} otherwise.
	 */
	public String getPrefix() {
		return this.prefix;
	}

	/**
	 * Sets the XML prefix of the client element. For example, "domain".
	 *
	 * @param aPrefix
	 *           XML prefix to predefine in the &lt;value&gt; element. Set to
	 *           {@code null} to not predefine a namespace prefix.
	 */
	public void setPrefix(String aPrefix) {
		this.prefix = aPrefix;
	}

	/**
	 * Gets the XML namespace URI of the client element. For example,
	 * "urn:ietf:params:xml:ns:domain-1.0".
	 *
	 * @return XML namespace URI to predefine in the &lt;value&gt; element if
	 *         defined; {@code null} otherwise.
	 */
	public String getNamespace() {
		return this.namespace;
	}

	/**
	 * Sets the XML namespace URI of the client element. For example,
	 * "urn:ietf:params:xml:ns:domain-1.0".
	 *
	 * @param aNamespace
	 *           ML namespace URI to predefine in the &lt;value&gt; element. Set
	 *           to {@code null} to not predefine a namespace.
	 */
	public void setNamespace(String aNamespace) {
		this.namespace = aNamespace;
	}

	/**
	 * encode <code>EPPValue</code> into a DOM element tree.
	 *
	 * @param aDocument
	 *           DOM Document
	 *
	 * @return &lt;extValue&gt; root element tree.
	 *
	 * @exception EPPEncodeException
	 *               Error encoding the DOM element tree.
	 */
	public Element encode(Document aDocument) throws EPPEncodeException {
		Element root = aDocument.createElementNS(EPPCodec.NS, ELM_NAME);

		// Predefine the namespace of the value child element
		if (this.namespace != null) {
			String thePrefix = "xmlns";
			if (this.prefix != null) {
				thePrefix += ":" + this.prefix;
			}
			root.setAttributeNS("http://www.w3.org/2000/xmlns/", thePrefix, this.namespace);
		}

		// Value
		DocumentBuilder theParser = null;

		try {
			theParser = factory.newDocumentBuilder();
		}
		catch (Exception ex) {
			cat.error("EPPValue.encode(): error creating DocumentBuilder: " + ex);
			throw new EPPEncodeException("Error creating DocumentBuilder: " + ex);
		}

		ByteArrayInputStream theValueStream = new ByteArrayInputStream(this.value.getBytes());

		Document theValueDoc = null;

		try {
			theValueDoc = theParser.parse(theValueStream);
		}
		catch (Exception ex) {
			cat.error("EPPValue.encode(): error parsing value [" + this.value + "]: " + ex);
			throw new EPPEncodeException("Error parsing value [" + this.value + "]: " + ex);
		}

		Element theValueElm = theValueDoc.getDocumentElement();
		root.appendChild(aDocument.importNode(theValueElm, true));

		return root;
	}

	/**
	 * decode <code>EPPValue</code> from a DOM element tree. The
	 * <code>aElement</code> argument needs to be the "extValue" element.
	 *
	 * @param aElement
	 *           The "extValue" XML element.
	 *
	 * @exception EPPDecodeException
	 *               Error decoding the DOM element tree.
	 */
	public void decode(Element aElement) throws EPPDecodeException {
		// Value
		Element valueNode = (Element) EPPUtil.getFirstElementChild(aElement);

		if (valueNode == null) {
			cat.error("EPPValue.decode(): value node could not be found");
			throw new EPPDecodeException("EPPValue.decode(): value node could not be found");
		}

		NamedNodeMap theAttrs = aElement.getAttributes();
		for (int i = 0; i < theAttrs.getLength(); i++) {
			Node theAttr = theAttrs.item(i);
			System.out.println("theAttr Namespace URI = " + theAttr.getNamespaceURI());
			if (theAttr.getNamespaceURI().equals("http://www.w3.org/2000/xmlns/")) {
				this.prefix = theAttr.getLocalName();
				this.namespace = theAttr.getNodeValue();
				break;
			}
		}

		System.out.println("prefix = " + this.prefix + ", namespace = " + this.namespace);

		this.value = EPPUtil.toString(valueNode);
	}

	/**
	 * implements a deep <code>EPPValue</code> compare.
	 *
	 * @param aObject
	 *           <code>EPPValue</code> instance to compare with
	 *
	 * @return <code>true</code> if equal; <code>false</code> otherwise
	 */
	public boolean equals(Object aObject) {
		if (!(aObject instanceof EPPValue)) {
			cat.error("EPPValue.equals(): " + aObject.getClass().getName() + " not EPPValue instance");
			return false;
		}

		EPPValue theComp = (EPPValue) aObject;

		/**
		 * This equality check will not work with EPPCodecTst since it will
		 * randomize the XML prefix prior to decoding.
		 */
		// Prefix
		/*
		 * if (!EqualityUtil.equals(this.prefix, theComp.prefix)) { cat.error(
		 * "EPPValue.equals(): prefix not equal"); return false; }
		 */

		// Namespace
		if (!EqualityUtil.equals(this.namespace, theComp.namespace)) {
			cat.error("this.prefix = " + this.prefix + ", theComp.prefix = " + theComp.prefix);
			cat.error("this.namespace = " + this.namespace + ", theComp.namespace = " + theComp.namespace);
			cat.error("EPPValue.equals(): namespace not equal");
			return false;
		}

		return true;
	}

	/**
	 * Clone <code>EPPValue</code>.
	 *
	 * @return Deep copy clone of <code>EPPValue</code>
	 *
	 * @exception CloneNotSupportedException
	 *               standard Object.clone exception
	 */
	public Object clone() throws CloneNotSupportedException {
		EPPValue clone = null;

		clone = (EPPValue) super.clone();

		return clone;
	}

	/**
	 * Implementation of <code>Object.toString</code>, which will result in an
	 * indented XML <code>String</code> representation of the concrete
	 * <code>EPPCodecComponent</code>.
	 *
	 * @return Indented XML <code>String</code> if successful; <code>ERROR</code>
	 *         otherwise.
	 */
	public String toString() {
		return EPPUtil.toString(this);
	}

}
