/*******************************************************************************
 *
 * Copyright (c) 2000-2003 Intel Corporation
 * All rights reserved.
 * Copyright (c) 2012 France Telecom All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * - Redistributions of source code must retain the above copyright notice,
 * this list of conditions and the following disclaimer.
 * - Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 * - Neither name of Intel Corporation nor the names of its contributors
 * may be used to endorse or promote products derived from this software
 * without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL INTEL OR
 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 ******************************************************************************/

/*!
 * \file
 */

#include "ixmlparser.h"

#include <assert.h>
#include <stdlib.h> /* for free() */
#include <string.h>

#include "posix_overwrites.h"

void ixmlElement_init(IXML_Element *element)
{
	if (element != NULL) {
		memset(element, 0, sizeof(IXML_Element));
	}
}

const DOMString ixmlElement_getTagName(IXML_Element *element)
{
	if (element != NULL) {
		return element->tagName;
	} else {
		return NULL;
	}
}

int ixmlElement_setTagName(IXML_Element *element, const char *tagName)
{
	int rc = IXML_SUCCESS;

	assert(element != NULL && tagName != NULL);

	if (element == NULL || tagName == NULL) {
		return IXML_FAILED;
	}

	if (element->tagName != NULL) {
		free(element->tagName);
	}
	element->tagName = strdup(tagName);
	if (element->tagName == NULL) {
		rc = IXML_INSUFFICIENT_MEMORY;
	}

	return rc;
}

const DOMString ixmlElement_getAttribute(
	IXML_Element *element, const DOMString name)
{
	IXML_Node *attrNode;

	if (element == NULL || name == NULL) {
		return NULL;
	}

	attrNode = element->n.firstAttr;
	while (attrNode != NULL) {
		if (strcmp(attrNode->nodeName, name) == 0) {
			return attrNode->nodeValue;
		} else {
			attrNode = attrNode->nextSibling;
		}
	}

	return NULL;
}

int ixmlElement_setAttribute(
	IXML_Element *element, const DOMString name, const DOMString value)
{
	IXML_Node *attrNode;
	IXML_Attr *newAttrNode;
	int errCode = IXML_SUCCESS;

	if (element == NULL || name == NULL || value == NULL) {
		errCode = IXML_INVALID_PARAMETER;
		goto ErrorHandler;
	}

	if (Parser_isValidXmlName(name) == 0) {
		errCode = IXML_INVALID_CHARACTER_ERR;
		goto ErrorHandler;
	}

	attrNode = element->n.firstAttr;
	while (attrNode != NULL) {
		if (strcmp(attrNode->nodeName, name) == 0) {
			/* Found it */
			break;
		} else {
			attrNode = attrNode->nextSibling;
		}
	}

	if (attrNode == NULL) {
		/* Add a new attribute */
		errCode = ixmlDocument_createAttributeEx(
			(IXML_Document *)element->n.ownerDocument,
			name,
			&newAttrNode);
		if (errCode != IXML_SUCCESS) {
			goto ErrorHandler;
		}

		attrNode = (IXML_Node *)newAttrNode;
		attrNode->nodeValue = strdup(value);
		if (attrNode->nodeValue == NULL) {
			ixmlAttr_free(newAttrNode);
			errCode = IXML_INSUFFICIENT_MEMORY;
			goto ErrorHandler;
		}

		errCode = ixmlElement_setAttributeNode(
			element, newAttrNode, NULL);
		if (errCode != IXML_SUCCESS) {
			ixmlAttr_free(newAttrNode);
			goto ErrorHandler;
		}
	} else {
		if (attrNode->nodeValue != NULL) {
			/* Attribute name has a value already */
			free(attrNode->nodeValue);
		}
		attrNode->nodeValue = strdup(value);
		if (attrNode->nodeValue == NULL) {
			errCode = IXML_INSUFFICIENT_MEMORY;
		}
	}

ErrorHandler:
	return errCode;
}

int ixmlElement_removeAttribute(IXML_Element *element, const DOMString name)
{
	IXML_Node *attrNode;

	if (element == NULL || name == NULL) {
		return IXML_INVALID_PARAMETER;
	}

	attrNode = element->n.firstAttr;
	while (attrNode != NULL) {
		if (strcmp(attrNode->nodeName, name) == 0) {
			/* Found it */
			break;
		} else {
			attrNode = attrNode->nextSibling;
		}
	}
	if (attrNode != NULL) {
		/* Has the attribute */
		if (attrNode->nodeValue != NULL) {
			free(attrNode->nodeValue);
			attrNode->nodeValue = NULL;
		}
	}

	return IXML_SUCCESS;
}

IXML_Attr *ixmlElement_getAttributeNode(
	IXML_Element *element, const DOMString name)
{
	IXML_Node *attrNode;

	if (element == NULL || name == NULL) {
		return NULL;
	}

	attrNode = element->n.firstAttr;
	while (attrNode != NULL) {
		if (strcmp(attrNode->nodeName, name) == 0) {
			/* found it */
			break;
		} else {
			attrNode = attrNode->nextSibling;
		}
	}

	return (IXML_Attr *)attrNode;
}

int ixmlElement_setAttributeNode(
	IXML_Element *element, IXML_Attr *newAttr, IXML_Attr **rtAttr)
{
	IXML_Node *attrNode = NULL;
	IXML_Node *node = NULL;
	IXML_Node *nextAttr = NULL;
	IXML_Node *prevAttr = NULL;
	IXML_Node *preSib = NULL;
	IXML_Node *nextSib = NULL;

	if (!element || !newAttr)
		return IXML_INVALID_PARAMETER;
	if (newAttr->n.ownerDocument != element->n.ownerDocument)
		return IXML_WRONG_DOCUMENT_ERR;
	if (newAttr->ownerElement)
		return IXML_INUSE_ATTRIBUTE_ERR;
	newAttr->ownerElement = element;
	node = (IXML_Node *)newAttr;
	attrNode = element->n.firstAttr;
	while (attrNode) {
		if (!strcmp(attrNode->nodeName, node->nodeName))
			/* Found it */
			break;
		else
			attrNode = attrNode->nextSibling;
	}
	if (attrNode) {
		/* Already present, will replace by newAttr */
		preSib = attrNode->prevSibling;
		nextSib = attrNode->nextSibling;
		if (preSib)
			preSib->nextSibling = node;
		if (nextSib)
			nextSib->prevSibling = node;
		if (element->n.firstAttr == attrNode)
			element->n.firstAttr = node;
		if (rtAttr)
			*rtAttr = (IXML_Attr *)attrNode;
		else
			ixmlAttr_free((IXML_Attr *)attrNode);
	} else {
		/* Add this attribute */
		if (element->n.firstAttr) {
			prevAttr = element->n.firstAttr;
			nextAttr = prevAttr->nextSibling;
			while (nextAttr) {
				prevAttr = nextAttr;
				nextAttr = prevAttr->nextSibling;
			}
			prevAttr->nextSibling = node;
			node->prevSibling = prevAttr;
		} else {
			/* This is the first attribute node */
			element->n.firstAttr = node;
			node->prevSibling = NULL;
			node->nextSibling = NULL;
		}
		if (rtAttr)
			*rtAttr = NULL;
	}

	return IXML_SUCCESS;
}

/*!
 * \brief Find a attribute node whose contents are the same as the oldAttr.
 *
 * \return If found, the attribute node is returned, otherwise \b NULL is
 * returned.
 */
static IXML_Node *ixmlElement_findAttributeNode(
	/*! [in] The element to search for the attribute. */
	IXML_Element *element,
	/*! [in] The attribute node to match. */
	IXML_Attr *oldAttr)
{
	IXML_Node *attrNode;
	IXML_Node *oldAttrNode = (IXML_Node *)oldAttr;

	assert(element != NULL && oldAttr != NULL);

	attrNode = element->n.firstAttr;
	while (attrNode != NULL) {
		/* parentNode, prevSib, nextSib and ownerDocument doesn't matter
		 */
		if (ixmlNode_compare(attrNode, oldAttrNode) == 1) {
			/* Found it */
			break;
		} else {
			attrNode = attrNode->nextSibling;
		}
	}

	return attrNode;
}

int ixmlElement_removeAttributeNode(
	IXML_Element *element, IXML_Attr *oldAttr, IXML_Attr **rtAttr)
{
	IXML_Node *attrNode;
	IXML_Node *preSib;
	IXML_Node *nextSib;

	if (element == NULL || oldAttr == NULL) {
		return IXML_INVALID_PARAMETER;
	}

	attrNode = ixmlElement_findAttributeNode(element, oldAttr);
	if (attrNode != NULL) {
		/* Has the attribute */
		preSib = attrNode->prevSibling;
		nextSib = attrNode->nextSibling;
		if (preSib != NULL) {
			preSib->nextSibling = nextSib;
		}
		if (nextSib != NULL) {
			nextSib->prevSibling = preSib;
		}
		if (element->n.firstAttr == attrNode) {
			element->n.firstAttr = nextSib;
		}
		attrNode->parentNode = NULL;
		attrNode->prevSibling = NULL;
		attrNode->nextSibling = NULL;
		*rtAttr = (IXML_Attr *)attrNode;
		return IXML_SUCCESS;
	} else {
		return IXML_NOT_FOUND_ERR;
	}
}

IXML_NodeList *ixmlElement_getElementsByTagName(
	IXML_Element *element, const DOMString tagName)
{
	IXML_NodeList *returnNodeList = NULL;

	if (element != NULL && tagName != NULL) {
		ixmlNode_getElementsByTagName(
			(IXML_Node *)element, tagName, &returnNodeList);
	}
	return returnNodeList;
}

const DOMString ixmlElement_getAttributeNS(
	/* IN */ IXML_Element *element,
	/* IN */ const DOMString namespaceURI,
	/* IN */ const DOMString localName)
{
	IXML_Node *attrNode;

	if (element == NULL || namespaceURI == NULL || localName == NULL) {
		return NULL;
	}

	attrNode = element->n.firstAttr;
	while (attrNode != NULL) {
		if (strcmp(attrNode->localName, localName) == 0 &&
			strcmp(attrNode->namespaceURI, namespaceURI) == 0) {
			/* Found it */
			return attrNode->nodeValue;
		} else {
			attrNode = attrNode->nextSibling;
		}
	}

	return NULL;
}

int ixmlElement_setAttributeNS(IXML_Element *element,
	const DOMString namespaceURI,
	const DOMString qualifiedName,
	const DOMString value)
{
	IXML_Node *attrNode = NULL;
	IXML_Node newAttrNode;
	IXML_Attr *newAttr;
	int rc;

	if (element == NULL || namespaceURI == NULL || qualifiedName == NULL ||
		value == NULL) {
		return IXML_INVALID_PARAMETER;
	}

	if (Parser_isValidXmlName(qualifiedName) == 0) {
		return IXML_INVALID_CHARACTER_ERR;
	}

	ixmlNode_init(&newAttrNode);
	newAttrNode.nodeName = strdup(qualifiedName);
	if (newAttrNode.nodeName == NULL) {
		return IXML_INSUFFICIENT_MEMORY;
	}

	rc = Parser_setNodePrefixAndLocalName(&newAttrNode);
	if (rc != IXML_SUCCESS) {
		Parser_freeNodeContent(&newAttrNode);
		return rc;
	}

	/* see DOM 2 spec page 59 */
	if ((newAttrNode.prefix != NULL && namespaceURI == NULL) ||
		(newAttrNode.prefix != NULL &&
			strcmp(newAttrNode.prefix, "xml") == 0 &&
			strcmp(namespaceURI,
				"http://www.w3.org/XML/1998/namespace") != 0) ||
		(strcmp(qualifiedName, "xmlns") == 0 &&
			strcmp(namespaceURI, "http://www.w3.org/2000/xmlns/") !=
				0)) {
		Parser_freeNodeContent(&newAttrNode);
		return IXML_NAMESPACE_ERR;
	}

	attrNode = element->n.firstAttr;
	while (attrNode != NULL) {
		if (strcmp(attrNode->localName, newAttrNode.localName) == 0 &&
			strcmp(attrNode->namespaceURI, namespaceURI) == 0) {
			/* Found it */
			break;
		} else {
			attrNode = attrNode->nextSibling;
		}
	}
	if (attrNode != NULL) {
		if (attrNode->prefix != NULL) {
			/* Remove the old prefix */
			free(attrNode->prefix);
		}
		/* replace it with the new prefix */
		if (newAttrNode.prefix != NULL) {
			attrNode->prefix = strdup(newAttrNode.prefix);
			if (attrNode->prefix == NULL) {
				Parser_freeNodeContent(&newAttrNode);
				return IXML_INSUFFICIENT_MEMORY;
			}
		} else
			attrNode->prefix = newAttrNode.prefix;

		if (attrNode->nodeValue != NULL) {
			free(attrNode->nodeValue);
		}
		attrNode->nodeValue = strdup(value);
		if (attrNode->nodeValue == NULL) {
			free(attrNode->prefix);
			Parser_freeNodeContent(&newAttrNode);
			return IXML_INSUFFICIENT_MEMORY;
		}
	} else {
		/* Add a new attribute */
		rc = ixmlDocument_createAttributeNSEx(
			(IXML_Document *)element->n.ownerDocument,
			namespaceURI,
			qualifiedName,
			&newAttr);
		if (rc != IXML_SUCCESS) {
			Parser_freeNodeContent(&newAttrNode);
			return rc;
		}
		newAttr->n.nodeValue = strdup(value);
		if (newAttr->n.nodeValue == NULL) {
			ixmlAttr_free(newAttr);
			Parser_freeNodeContent(&newAttrNode);
			return IXML_INSUFFICIENT_MEMORY;
		}
		if (ixmlElement_setAttributeNodeNS(
			    element, newAttr, &newAttr) != IXML_SUCCESS) {
			ixmlAttr_free(newAttr);
			Parser_freeNodeContent(&newAttrNode);
			return IXML_FAILED;
		}
	}
	Parser_freeNodeContent(&newAttrNode);

	return IXML_SUCCESS;
}

int ixmlElement_removeAttributeNS(IXML_Element *element,
	const DOMString namespaceURI,
	const DOMString localName)
{
	IXML_Node *attrNode = NULL;

	if (element == NULL || namespaceURI == NULL || localName == NULL) {
		return IXML_INVALID_PARAMETER;
	}

	attrNode = element->n.firstAttr;
	while (attrNode != NULL) {
		if (strcmp(attrNode->localName, localName) == 0 &&
			strcmp(attrNode->namespaceURI, namespaceURI) == 0) {
			/* Found it */
			break;
		} else {
			attrNode = attrNode->nextSibling;
		}
	}
	if (attrNode != NULL) {
		/* Has the attribute */
		if (attrNode->nodeValue != NULL) {
			free(attrNode->nodeValue);
			attrNode->nodeValue = NULL;
		}
	}

	return IXML_SUCCESS;
}

IXML_Attr *ixmlElement_getAttributeNodeNS(IXML_Element *element,
	const DOMString namespaceURI,
	const DOMString localName)
{
	IXML_Node *attrNode = NULL;

	if (element == NULL || namespaceURI == NULL || localName == NULL) {
		return NULL;
	}

	attrNode = element->n.firstAttr;
	while (attrNode != NULL) {
		if (strcmp(attrNode->localName, localName) == 0 &&
			strcmp(attrNode->namespaceURI, namespaceURI) == 0) {
			/* found it */
			break;
		} else {
			attrNode = attrNode->nextSibling;
		}
	}

	return (IXML_Attr *)attrNode;
}

int ixmlElement_setAttributeNodeNS(
	/* IN */ IXML_Element *element,
	/* IN */ IXML_Attr *newAttr,
	/* OUT */ IXML_Attr **rtAttr)
{
	IXML_Node *attrNode = NULL;
	IXML_Node *node = NULL;
	IXML_Node *prevAttr = NULL;
	IXML_Node *nextAttr = NULL;
	IXML_Node *preSib = NULL;
	IXML_Node *nextSib = NULL;

	if (element == NULL || newAttr == NULL) {
		return IXML_INVALID_PARAMETER;
	}

	if (newAttr->n.ownerDocument != element->n.ownerDocument) {
		return IXML_WRONG_DOCUMENT_ERR;
	}

	if (newAttr->ownerElement != NULL && newAttr->ownerElement != element) {
		return IXML_INUSE_ATTRIBUTE_ERR;
	}

	newAttr->ownerElement = element;
	node = (IXML_Node *)newAttr;
	attrNode = element->n.firstAttr;
	while (attrNode != NULL) {
		if (strcmp(attrNode->localName, node->localName) == 0 &&
			strcmp(attrNode->namespaceURI, node->namespaceURI) ==
				0) {
			/* Found it */
			break;
		} else {
			attrNode = attrNode->nextSibling;
		}
	}
	if (attrNode != NULL) {
		/* already present, will replace by newAttr */
		preSib = attrNode->prevSibling;
		nextSib = attrNode->nextSibling;
		if (preSib != NULL) {
			preSib->nextSibling = node;
		}
		if (nextSib != NULL) {
			nextSib->prevSibling = node;
		}
		if (element->n.firstAttr == attrNode) {
			element->n.firstAttr = node;
		}
		*rtAttr = (IXML_Attr *)attrNode;

	} else {
		/* Add this attribute  */
		if (element->n.firstAttr != NULL) {
			/* Element has attribute already */
			prevAttr = element->n.firstAttr;
			nextAttr = prevAttr->nextSibling;
			while (nextAttr != NULL) {
				prevAttr = nextAttr;
				nextAttr = prevAttr->nextSibling;
			}
			prevAttr->nextSibling = node;
		} else {
			/* This is the first attribute node */
			element->n.firstAttr = node;
			node->prevSibling = NULL;
			node->nextSibling = NULL;
		}
		if (rtAttr != NULL) {
			*rtAttr = NULL;
		}
	}

	return IXML_SUCCESS;
}

IXML_NodeList *ixmlElement_getElementsByTagNameNS(IXML_Element *element,
	const DOMString namespaceURI,
	const DOMString localName)
{
	IXML_Node *node = (IXML_Node *)element;
	IXML_NodeList *nodeList = NULL;

	if (element != NULL && namespaceURI != NULL && localName != NULL) {
		ixmlNode_getElementsByTagNameNS(
			node, namespaceURI, localName, &nodeList);
	}

	return nodeList;
}

int ixmlElement_hasAttribute(IXML_Element *element, const DOMString name)
{
	IXML_Node *attrNode = NULL;

	if (element == NULL || name == NULL) {
		return 0;
	}

	attrNode = element->n.firstAttr;
	while (attrNode != NULL) {
		if (strcmp(attrNode->nodeName, name) == 0) {
			return 1;
		} else {
			attrNode = attrNode->nextSibling;
		}
	}

	return 0;
}

int ixmlElement_hasAttributeNS(IXML_Element *element,
	const DOMString namespaceURI,
	const DOMString localName)
{
	IXML_Node *attrNode = NULL;

	if (element == NULL || namespaceURI == NULL || localName == NULL) {
		return 0;
	}

	attrNode = element->n.firstAttr;
	while (attrNode != NULL) {
		if (strcmp(attrNode->localName, localName) == 0 &&
			strcmp(attrNode->namespaceURI, namespaceURI) == 0) {
			return 1;
		} else {
			attrNode = attrNode->nextSibling;
		}
	}

	return 0;
}

void ixmlElement_free(IXML_Element *element)
{
	if (element != NULL) {
		ixmlNode_free((IXML_Node *)element);
	}
}
