/**
 * @file
 * @version @$Id$
 * @author
 * ui-gxmlcpp: C++ wrapper for Gnome libxml2/libxsl.
 * Copyright (C) Schlund+Partner AG 2002-2004.
 * Licensed under the GNU Lesser General Public License (LGPL). See
 * @ref License, or the file COPYING in distribution.
 */

// Local configuration
#include "config.h"

// Implementation
#include "Tree.hpp"

// STDC++
#include <string>
#include <cassert>

// C libraries
#include <libxslt/xslt.h>

// C++ libraries
#include <ui-utilcpp/Text.hpp>

// Local
#include "Util.hpp"
#include "XPathObject.hpp"

namespace UI {
namespace GXML {

std::string const Tree::DefaultEncoding_("UTF-8");
std::string const Tree::DefaultEncodings_("UTF-8,UTF8");
std::string const Tree::DefaultDocbase_("noname.xml");

xmlDoc * Tree::create(char const * xml, int len, std::string const & base, int const options)
{
	assert(xml);

	xmlDoc * result(0);
	len = len < 0 ? strlen(xml) : len;

	// xmlDocPtr xmlReadMemory(const char * buffer, int size, const char * URL, const char * encoding, int options)
	result = xmlReadMemory(xml, len, base.c_str(), 0, XML_PARSE_NOBLANKS | options);
	if (!result)
	{
		UI_THROW_CODE(Parse_, "Can't parse XML");
	}
	return result;
}

xmlDoc * Tree::create(xmlDoc const * const doc)
{
	xmlDoc * result(xmlCopyDoc((xmlDoc *)doc, true));
	if (result == NULL)
	{
		UI_THROW_CODE(Mem_, "Error copying doc tree");
	}
	return result;
}

xmlDoc * Tree::create(std::string const & file, int const options)
{
	xmlDoc * result(xmlReadFile(file.c_str(), 0, XML_PARSE_NOBLANKS | options));
	if (result == NULL)
	{
		UI_THROW_CODE(Parse_, "Can't parse XML file: " + file);
	}
	return result;
}

Tree::Tree()
	:dontFreeDoc_(false)
	,context_(0)
{}

Tree::Tree(char const * xml, int len, std::string const & base, int const options)
	:dontFreeDoc_(false)
	,context_(0)
{
	set(create(xml, len, base, options));
}

Tree::Tree(std::string const & xml, std::string const & base, int const options)
	:dontFreeDoc_(false)
	,context_(0)
{
	set(create(xml.c_str(), xml.size(), base.c_str(), options));
}

Tree::Tree(std::istream & xml, std::string const & base, int const options)
	:dontFreeDoc_(false)
	,context_(0)
{
	set(create(UI::Util::istream2String(xml).c_str(), -1, base.c_str(), options));
}

Tree::Tree(FileConstructor const &, std::string const & file, int const options)
	:dontFreeDoc_(false)
	,context_(0)
{
	set(create(file, options));
}

Tree::Tree(Tree const & tree)
	:Util::auto_base<xmlDoc>()
	,dontFreeDoc_(false)
	,context_(0)
{
	set(create(tree.get()));
}

void Tree::dealloc()
{
	if (!dontFreeDoc_)
	{
		xmlFreeDoc(get());
	}
	delete context_;
}

Tree::~Tree()
{
	dealloc();
}

Tree & Tree::operator=(Tree const & tree)
{
	dealloc();
	set(create(tree.get()));
	return *this;
}

bool Tree::operator==(Tree const & tree)
{
	return dump() == tree.dump();
}

Tree & Tree::setContext(bool on)
{
	if (on)
	{
		if (!context_)
		{
			context_ = new XPathContext(get());
		}
	}
	else
	{
		delete context_;
		context_ = 0;
	}
	return *this;
}

XPathContext * Tree::getContext() const
{
	return context_;
}

Tree & Tree::setTopLevelNamespaces()
{
	if (!context_)
	{
		UI_THROW_CODE(Internal_, "We need a fixed context to prepare top level namespaces");
	}

	xmlDoc * doc(get());
	xmlXPathContext * context(context_->get());
	if (doc->children)
	{
		context->namespaces = ::xmlGetNsList(doc, doc->children);
		context->nsNr = 0;
		if (context->namespaces != 0)
		{
			while (context->namespaces[context->nsNr])
			{
				++context->nsNr;
			}
		}
	}
	return *this;
}

Tree & Tree::setXPathNamespace(std::string const & prefix, std::string const & uri)
{
	if (prefix.size())
	{
		if (uri.empty())
		{
			xPathNamespaces_.erase(prefix);
		}
		else
		{
			xPathNamespaces_.insert(std::make_pair(prefix, uri));
		}
	}
	return *this;
}


Tree & Tree::setDontFreeDoc(bool on)
{
	dontFreeDoc_=on;
	return *this;
}


//
// Tree::Node
//
xmlNode * const Tree::Node::NullNode_(xmlNewNode(0, (xmlChar*)""));

Tree::Node::Node(xmlNode * const node)
{
	set(node ? node : NullNode_);
}

bool Tree::Node::isNull() const
{
	return get() == NullNode_;
}

void Tree::Node::unlink()
{
	if (!isNull())
	{
		xmlUnlinkNode(get());
		xmlFreeNode(get());
		set(NullNode_);
	}
}

Tree::Node Tree::Node::getParent() const
{
	return Node(get()->parent);
}

Tree::Node Tree::Node::getChild() const
{
	return Node(get()->children);
}

Tree::Node Tree::Node::getNext() const
{
	return Node(get()->next);
}

Tree::Node Tree::Node::getPrev() const
{
	return Node(get()->prev);
}

Tree::NodeSet Tree::Node::getChilds(std::string const & name) const
{
	return NodeSet(*this, true, name);
}

xmlElementType Tree::Node::getType() const
{
	return get()->type;
}

std::string Tree::Node::getXPath() const
{
	auto_xmlfree<xmlChar> path(xmlGetNodePath(get()));
	return (char *) path.get();
}

char const * Tree::Node::getNameC() const
{
	return get()->name ? (char *) get()->name : UI::Util::EmptyString_.c_str();
}

std::string Tree::Node::getName() const
{
	return getNameC();
}

void Tree::Node::setName(std::string const & name)
{
	/** @bug Imho, this should also work for attribute nodes, but this currently segfaults. */
	switch (getType())
	{
	case XML_ELEMENT_NODE:
		xmlNodeSetName(get(), (xmlChar *) name.c_str());
		break;
	default:
		UI_THROW_CODE(Internal_, "Don't know how to set name for node type: " + UI::Util::tos(getType()));
		break;
	}
}

char const * Tree::Node::getContentC() const
{
	return get()->children && get()->children->content ? (char *) get()->children->content : UI::Util::EmptyString_.c_str();
}

std::string Tree::Node::getContent() const
{
	return getContentC();
}

void Tree::Node::setContent(std::string const & content)
{
	xmlNodeSetContent(get(), (xmlChar *) content.c_str());
}

char const * Tree::Node::hasAttribute(std::string const & name) const
{
	xmlAttrPtr attr(xmlHasProp(get(), (xmlChar *)name.c_str()));
	return attr ? (char *) attr->children->content : 0;
}

char const * Tree::Node::getAttributeC(std::string const & name) const
{
	char const * result(hasAttribute(name));
	return result ? result : UI::Util::EmptyString_.c_str();
}

std::string Tree::Node::getAttribute(std::string const & name) const
{
	return getAttributeC(name);
}

void Tree::Node::setAttribute(std::string const & name, std::string const & content)
{
	xmlAttr * attr(xmlSetProp(get(), (xmlChar *) name.c_str(), (xmlChar *) content.c_str()));
	if (!attr)
	{
		UI_THROW_CODE(Internal_, "Error setting attribute " + name + " to " + content);
	}
}

Tree::NodeSet Tree::Node::getNodeSet(std::string const & xpath) const
{
	/** @note XPathContext is created each time we call => thread-safe,
			but possibly non-performant. A solution like in Tree would be
			better, or even some other more general solution for both Tree +
			Tree::Node. */
	return NodeSet(XPathObject(XPathContext(get()->doc, get()), xpath));
}

Tree::Node Tree::Node::getNode(std::string const & xpath, bool const & doThrow) const
{
	NodeSet ns(getNodeSet(xpath));
	if (ns.empty() && doThrow)
	{
		UI_THROW_CODE(NoNode_, "No such node: " + xpath);
	}
	return ns.empty() ? Node(0) : ns[0];
}

Tree::Node Tree::Node::copy() const
{
	xmlNode * copy(xmlCopyNode(get(), 1));
	if (!copy)
	{
		UI_THROW_CODE(Internal_, "Error copying node: " + getName());
	}
	return Node(copy);
}

Tree::Node Tree::Node::addChild(std::string const & name, std::string const & content)
{
	xmlNode * newNode(xmlNewChild(get(), 0, (xmlChar *) name.c_str(), (xmlChar *) content.c_str()));
	if (!newNode)
	{
		UI_THROW_CODE(Internal_, "Error adding new child node: " + name);
	}
	return Node(newNode);
}

Tree::Node Tree::Node::addChild(Node const & n)
{
	Node newNode(n.copy());
	if (xmlAddChild(get(), newNode.get()) == 0)
	{
		xmlFreeNode(newNode.get());
		UI_THROW_CODE(Internal_, "addChild: Can't add node " + n.getName() + " to " + getName());
	}
	return newNode;
}

Tree::Node Tree::Node::addSiblingAfter(std::string const & name, std::string const & content)
{
	xmlNode * newXmlNode(xmlNewNode(0, (xmlChar *)name.c_str()));
	if (!newXmlNode)
	{
		UI_THROW_CODE(Internal_, "Error creating sibling node: " + name);
	}

	Node newNode(newXmlNode);
	newNode.setContent(content);

	if (xmlAddNextSibling(get(), newNode.get()) == 0)
	{
		xmlFreeNode(newXmlNode);
		UI_THROW_CODE(Internal_, "addSiblingAfter: Can't add node " + name + " as sibling of " + getName());
	}

	return newNode;
}

Tree::Node Tree::Node::addSiblingAfter(Node const & n)
{
	Node newNode(n.copy());
	if (xmlAddNextSibling(get(), newNode.get()) == 0)
	{
		xmlFreeNode(newNode.get());
		UI_THROW_CODE(Internal_, "addSiblingAfter: Can't add node " + n.getName() + " as sibling of " + getName());
	}
	return newNode;
}

std::string Tree::Node::dump(bool const & format, std::string const & encoding) const
{
	return NodeSet::Dump(NodeSet(*this), format, encoding).getC();
}


//
// Tree::NodeSet
//
Tree::NodeSet::NodeSet(XPathObject const & xPathObject)
{
	if (xPathObject->type == XPATH_NODESET && xPathObject->nodesetval)
	{
		for (int i(0); i < xPathObject->nodesetval->nodeNr; ++i)
		{
			push_back(Node(xPathObject->nodesetval->nodeTab[i]));
		}
	}
}

Tree::NodeSet::NodeSet(Node const & node, bool const & childs, std::string const & name)
{
	add(node, childs, name);
}

Tree::NodeSet::NodeSet(NodeSet const & ns, bool const & childs, std::string const & name)
	:std::vector<Node>()
{
	add(ns, childs, name);
}

void Tree::NodeSet::add(Node const & node, bool const & childs, std::string const & name)
{
	class Help
	{
	public:
		static void addMatching(NodeSet & o, Node const & n, std::string const & name)
		{
			if (name.empty() || name == n.getName())
			{
				o.push_back(n);
			}
		}
	};

	if (!node.isNull())
	{
		if (childs)
		{
			Node n(node.getChild());
			while (!n.isNull())
			{
				Help::addMatching(*this, n, name);
				n = n.getNext();
			}
		}
		else
		{
			Help::addMatching(*this, node, name);
		}
	}
}

void Tree::NodeSet::add(NodeSet const & ns, bool const & childs, std::string const & name)
{
	for (NodeSet::const_iterator i(ns.begin()); i != ns.end(); ++i)
	{
		add((*i), childs, name);
	}
}

void Tree::NodeSet::unlink()
{
	for (Tree::NodeSet::iterator n(this->begin()); n != this->end(); ++n)
	{
		n->unlink();
	}
}

Tree::NodeSet Tree::NodeSet::getChilds(std::string const & name) const
{
	return NodeSet(*this, true, name);
}

std::string Tree::NodeSet::getContent(std::string const & nodeSeparator) const
{
	std::string content("");
	for (Tree::NodeSet::const_iterator n(this->begin()); n != this->end(); ++n)
	{
		content += n->getContentC() + ((n+1) == this->end() ? "" : nodeSeparator);
	}
	return content;
}


//
// Tree::NodeSet::Dump
//
Tree::NodeSet::Dump::Dump()
{}

/** @brief Append a free text to the dump. */
int Tree::NodeSet::Dump::concat(std::string const & text)
{
	int writtenPre(outbuf_.getSize());
	outbuf_.concat(text);
	return outbuf_.getSize() - writtenPre;
}

int Tree::NodeSet::Dump::concat(Node const & node, bool const & format, std::string const & encoding)
{
	int writtenPre(outbuf_.getSize());
	xmlNodeDumpOutput(outbuf_.get(), node.get()->doc, node.get(), 0, format ? 1 : 0, encoding.c_str());
	return outbuf_.getSize() - writtenPre;
}

Tree::NodeSet::Dump::Dump(Node const & node,
                          bool const & format, std::string const & encoding)
{
	concat(node, format, encoding);
}

int Tree::NodeSet::Dump::concat(NodeSet const & nodeSet,
                                 bool const & format, std::string const & encoding)
{
	int written(0);
	for (Tree::NodeSet::const_iterator n(nodeSet.begin()); n != nodeSet.end(); ++n)
	{
		written += concat(*n, format, encoding);
	}
	return written;
}

Tree::NodeSet::Dump::Dump(NodeSet const & nodeSet,
                          bool const & format, std::string const & encoding)
{
	concat(nodeSet, format, encoding);
}

char const * Tree::NodeSet::Dump::getC() const
{
	return outbuf_.getBuf();
}

std::string Tree::NodeSet::Dump::get() const
{
	return getC();
}

int Tree::NodeSet::Dump::getSize() const
{
	return outbuf_.getSize();
}

std::string Tree::NodeSet::dump(bool const & format, std::string const & encoding) const
{
	return NodeSet::Dump(*this, format, encoding).getC();
}


//
// Tree::Dump
//
Tree::Dump::Dump(Tree const & tree, bool const & format, std::string const & encoding)
	:buf_(0)
	,size_(0)
{
	xmlDocDumpFormatMemoryEnc(tree.get(), &buf_, &size_, encoding.c_str(), format ? 1 : 0);
	if (!buf_)
	{
		UI_THROW_CODE(Mem_, "xmlDocDumpMemoryEnc: Allocation error?");
	}
}
Tree::Dump::~Dump()
{
	xmlFree(buf_);
}

char const * Tree::Dump::getC() const
{
	return (char *) buf_;
}

std::string Tree::Dump::get() const
{
	return getC();
}

int Tree::Dump::getSize() const
{
	return size_;
}

std::string Tree::dump(bool const & format, std::string const & encoding) const
{
	return Dump(*this, format, encoding).getC();
}

std::string Tree::formatDump(std::string const & encoding) const
{
	return UI::GXML::Tree(dump()).dump(true, encoding);
}

std::string Tree::getOrigEncoding() const
{
	return get()->encoding ? (char *) get()->encoding : UI::Util::EmptyString_;
}


XPathObject Tree::getXPathObject(std::string const & xpath) const
{
	return XPathObject(XPathContextSwitch(*this, xPathNamespaces_).get(), xpath);
}

Tree::NodeSet Tree::getNodeSet(std::string const & xpath) const
{
	return NodeSet(XPathObject(XPathContextSwitch(*this, xPathNamespaces_).get(), xpath));
}

Tree::Node Tree::getNode(std::string const & xpath, bool const & doThrow) const
{
	NodeSet ns(getNodeSet(xpath));
	if (ns.empty() && doThrow)
	{
		UI_THROW_CODE(NoNode_, "No such node: " + xpath);
	}
	return ns.empty() ? Node(0) : ns[0];
}

Tree::Node Tree::getRootNode() const
{
	return getNode("/*", true);
}


int Tree::getCount(std::string const & xpath) const
{
	return getNodeSet(xpath).size();
}

bool Tree::exists(std::string const & xpath, int const times) const
{
	return getCount(xpath) >= times;
}

std::string Tree::getContent(std::string const & xpath) const
{
	return getNodeSet(xpath).getContent();
}

void Tree::setContent(std::string const & xpath, std::string const & content)
{
	getNode(xpath, true).setContent(content);
}

void Tree::setName(std::string const & xpath, std::string const & name)
{
	getNode(xpath, true).setName(name);
}

void Tree::setAttribute(std::string const & xpath, std::string const & name, std::string const & content)
{
	getNode(xpath, true).setAttribute(name, content);
}

std::string Tree::getAttribute(std::string const & xpath, std::string const & name) const
{
	return getNode(xpath, false).getAttribute(name);
}

Tree::Node Tree::addChild(std::string const & xpath, Node const & node)
{
	return getNode(xpath, true).addChild(node);
}

Tree::Node Tree::addChild(std::string const & xpath, std::string const & name, std::string const & content)
{
	return getNode(xpath, true).addChild(name, content);
}

void Tree::unlink(std::string const & xpath)
{
	getNode(xpath, true).unlink();
}

void Tree::addTree(std::string const & xpath, Tree const & source, std::string const & sourceXPath)
{
	NodeSet ns(source.getNodeSet(sourceXPath));
	for (NodeSet::const_iterator i(ns.begin()); i != ns.end(); ++i)
	{
		addChild(xpath, *i);
	}
}

void Tree::addXML(std::string const & xpath, std::string const & source, std::string const & sourceXPath)
{
	return addTree(xpath, Tree(source), sourceXPath);
}

//
// Internal helper XPathContextSwitch
//
Tree::XPathContextSwitch::XPathContextSwitch(Tree const & tree, NamespaceMap const & ns)
	:treeContext_(tree.context_)
	,tempContext_(treeContext_ ? 0 : new XPathContext(tree))
{
	if (!ns.empty())
	{
		XPathContext * ctx(treeContext_ ? treeContext_ : tempContext_);

		for (NamespaceMap::const_iterator i(ns.begin()); i != ns.end(); ++i)
		{
			ctx->registerNamespace(i->first, i->second);
		}
	}
}

Tree::XPathContextSwitch::~XPathContextSwitch()
{
	delete tempContext_;
}

XPathContext & Tree::XPathContextSwitch::get()
{
	assert(treeContext_ || tempContext_);
	return treeContext_ ? *treeContext_ : *tempContext_;
}

}}
