/*********************************************************************
 * This file is part of the cpplibs suite.
 *
 * Copyright (C) 2001 Topi Mäenpää
 * All rights reserved.
 *
 * This program is free software. You can redistribute and/or modify
 * it under the terms of the free software licence found in the
 * accompanying file "COPYING". The licence terms must always be
 * redistributed with this source file. The above copyright notice
 * must be reproduced in all modified and unmodified copies of this
 * source file.
 *
 * $Revision: 1.11 $
 *********************************************************************/

#include "XMLDocument.h"
#include "../String.h"

using namespace std;
using namespace util::io;

namespace util { namespace xml {

	Node::~Node()
	{
		for (int i=_plstChildren.getLength();i--;)
			delete _plstChildren[i];
	}

	Node& Node::operator= (const Node& other)
	{
		for (int i=_plstChildren.getLength();i--;)
			delete _plstChildren[i];
		_pParent = NULL;
		_nodeType = other._nodeType;
		return *this;
	}

	List<Node*> Node::getChildNodes(NodeType type, std::string name) const
	{
		int len = _plstChildren.getLength();
		List<Node*> result(len);
		for (int i=0;i<len;i++)
			{
				if (_plstChildren[i]->getNodeType() == type &&
						(!name.size() || name == _plstChildren[i]->getName()))
					result += _plstChildren[i];
			}
		return result;
	}

	const Node* Node::getChildNode(string nodeName) const
	{
		//cerr << "Getting child node from a " << getName() << " with '" << nodeName << "'" << endl;
		List<string> parts(2);
		String::tokenize(nodeName,".",parts,2);

		//Check if the first part of the query string matches my name.
		if (getName() != parts[0])
			return NULL;

		//If it does, and there is no child request, return myself.
		if (parts.getLength() == 1)
			return this;

		//cerr << "Found " << getName() << ", looking for child from " << parts[1] << endl;
		
		//Split the child request into parts.
		string tmp(parts[1]);
		parts.clear();
		String::tokenize(tmp,".",parts,2);

		//cerr << "Child request tokenized: " << parts[0] << ", " << parts[1] << endl;

		string typeName = parts[0];
		if (!typeName.size()) return NULL;

		//Split bracket notation.
		List<string> typeNameParts(3);
		String::tokenize(typeName,"[]",typeNameParts);
		int len = typeNameParts.getLength();
		string childName;
		int nodeIndex = 0;
		if (len == 1) //no brackets -> take the first match
			{
				nodeIndex = 0;
				//cerr << "No brackets." << endl;
			}
		else if (len == 2) //element[name] or element[index] -> take the indexth match or the first name match
			{
				if (!typeNameParts[1].size())
					return NULL;
				if (isdigit(typeNameParts[1][0]))
					nodeIndex = String::parseInt(typeNameParts[1]);
				else
					childName = typeNameParts[1];
				typeName = typeNameParts[0];
				//cerr << "One pair of brackets." << endl;
			}
		else if (len == 3) //element[name][index] -> take the indexth name match
			{
				if (!typeNameParts[1].size() || !typeNameParts[1].size() ||
						isdigit(typeNameParts[1][0]) || !isdigit(typeNameParts[2][0]))
					return NULL;
				typeName = typeNameParts[0];
				childName = typeNameParts[1];
				nodeIndex = String::parseInt(typeNameParts[2]);
				//cerr << "Two pairs of brackets." << endl;
			}
		else //We don't know about this.
			return NULL;

		//cerr << "Restrictions: childName = " << childName << ", index = " << nodeIndex << endl;

		if (isInternalName(typeName))
			{
				//cerr << "An internal name was requested." << endl;
				int matchIndex = 0;
				
				for (int i=0;i<_plstChildren.getLength();i++)
					{
						//If the type name, child name and index match.
						if (typeName == _plstChildren[i]->getTypeName() &&
								(!childName.size() || childName == _plstChildren[i]->getName()) &&
								(matchIndex++ == nodeIndex))
							{
								return _plstChildren[i]->getChildNode(parts.getLength() > 1 ?
																											(_plstChildren[i]->getName() + '.' + parts[1]) :
																											_plstChildren[i]->getName());
							}
					}
			}
		else
			{
				//cerr << "An external name was requested." << endl;
				if (childName.size()) //we don't allow tagname[tagname]
					return NULL;
				
				int matchIndex = 0;
				
				for (int i=0;i<_plstChildren.getLength();i++)
					{
						//cerr << "Searching " << _plstChildren[i]->getName() << "..." << endl;
						if (typeName == _plstChildren[i]->getName() &&
								matchIndex++ == nodeIndex)
							return _plstChildren[i]->getChildNode(parts.getLength() > 1 ?
																										(_plstChildren[i]->getName() + '.' + parts[1]) :
																										_plstChildren[i]->getName());
					}
			}
		return NULL;
	}

	bool Node::isInternalName(string name) const
	{
		return name == "text" || name == "element" || name == "attribute" ||
			name == "comment" || name == "document" || name == "instruction" ||
			name == "declaration" || name == "cdata";
	}
	
	string Node::getTypeName() const
	{
		switch (_nodeType)
			{
			case DOCUMENT_NODE: return "document";
			case ELEMENT_NODE: return "element";
			case ATTRIBUTE_NODE: return "attribute";
			case PROCESSING_INSTRUCTION_NODE: return "instruction";
			case DECLARATION_NODE: return "declaration";
			case TEXT_NODE: return "text";
			case COMMENT_NODE: return "comment";
			case CDATA_SECTION_NODE: return "cdata";
			}
		return "unknown";
	}

	Element& Element::operator= (const Element& other)
	{
		tagName = tagName;
		tagType = other.tagType;
		return *this;
	}

	void Element::appendChild(Node* child)
	{
		Node::appendChild(child);
		Attr* attribute = dynamic_cast<Attr*>(child);
		if (attribute)
			{
				//cerr << "Appending " << attribute->name << " => " << attribute->value << " to " << getName() << endl;
				_tblAttributes.put(attribute->name,attribute);
			}
	}
	
	void Element::removeChild(Node* child)
	{
		Node::removeChild(child);
		Attr* attribute = dynamic_cast<Attr*>(child);
		if (attribute)
			_tblAttributes.remove(attribute->name);
	}

	string Element::getAttribute(string name)
	{
		Attr** result = _tblAttributes[name];
		return result ? (*result)->value : "";
	}

	Attr* Element::getAttributeNode(string name)
	{
		Attr** result = _tblAttributes[name];
		return result ? *result : NULL;
	}
	

	void Element::printOut(ostream& out) const throw (IOException&)
	{
		switch (tagType)
			{
			case TAG_OPENING:
				out << "<" << tagName;
				printTagAttributes(out);
				out << ">";
				for (int i=0;i<_plstChildren.getLength();i++)
					{
						Attr* attribute = dynamic_cast<Attr*>(_plstChildren[i]);
						if (!attribute)
							((Node*)_plstChildren[i])->printOut(out);
					}
				out << "</" << tagName << ">";
				break;
			case TAG_EMPTY:
				out << "<" << tagName;
				printTagAttributes(out);
				out << "/>";
				break;
			default:
				break;
			}
	}

	void Element::printTagAttributes(ostream& out) const throw (IOException&)
	{
		for (int i=0;i<_plstChildren.getLength();i++)
			{
				Attr* attribute = dynamic_cast<Attr*>(_plstChildren[i]);
				if (attribute)
					out << " " << attribute->name << "=\"" << String::addXMLEntities(attribute->value) << "\"";
			}
	}

	void Document::printOut(ostream& out) const throw (IOException&)
	{
		out << "<?xml version=\"1.0\"?>" << endl;
		if (documentElement)
			documentElement->printOut(out);
	}

	void Text::printOut(ostream& out) const throw (IOException&)
	{
		out << String::addXMLEntities(contents);
	}

}}
