/*
 * (C) Copyright 2002, Schlund+Partner AG
 *
 * 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
*/

// Local configuration
#include "config.h"

// Implementation
#include "XMLTree.hpp"

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

// STDC
#include <cstring>

// C libraries
#include <libxslt/xsltInternals.h>
#include <libxml/parser.h>

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

// Local
#include "../../src/ui-gxmlcpp/Util.hpp"
#include "XMLNodeSet.hpp"
#include "XMLDump.hpp"

namespace UI {
namespace GXML {

XMLTree::XMLTree(xmlDocPtr const doc)
	:_doc()
	,_context(doc)
{
	_doc.set(doc);
}

XMLTree::XMLTree(char const * xmlBuffer, int size)
	:_doc(xmlBuffer, size)
	,_context(_doc)
{}

XMLTree::XMLTree(std::string const & xmlString)
	:_doc(xmlString.c_str())
	,_context(_doc)
{}

XMLTree::XMLTree(std::ifstream & xmlStream)
	:_doc(UI::Util::istream2String(xmlStream).c_str())
	,_context(_doc)
{}

XMLTree::XMLTree(std::istream & xmlStream)
	:_doc(UI::Util::istream2String(xmlStream).c_str())
	,_context(_doc)
{}

XMLTree::~XMLTree()
{}

UI::Util::auto_ptr<XMLNodeSet> XMLTree::getNodeSet(std::string const & xpath) const
{
	return UI::Util::auto_ptr<XMLNodeSet>(new XMLNodeSet(xpath, this));
}

//
// libxml2 Pointers; these are a Mockery to Abstraction
//
xmlDocPtr XMLTree::getDocPtr() const
{
	return _doc.get();
}

xmlXPathContextPtr XMLTree::getXPathContextPtr() const
{
	return _context.get();
}

XPathContext XMLTree::getXPathContext() const
{
	return XPathContext(_doc);
}

//
// Get Values from XPath Expressions
//

// This is the master "getValue" function
// §STS§ "xmlNodeGetContent" might do the same ?
xmlChar * XMLTree::getXmlCharValue(xmlChar const * xpath) const
{
	xmlChar * result(0);

	XPathContext context(_doc);
	xmlNodePtr node(nodeFromPath(context.get(), xpath, false));

	if (node && node->children)
	{
		result = node->children->content;
	}
	return result;
}

xmlChar * XMLTree::getXmlCharValue(std::string const & xpath) const
{
	return getXmlCharValue((xmlChar *) xpath.c_str());
}

char * XMLTree::getAddrValue(std::string const & xpath) const
{
	return (char *) getXmlCharValue(xpath);
}

std::string XMLTree::getValue(std::string const & xpath) const
{
	char * result = getAddrValue(xpath);
	if (result)
	{
		return result;
	}
	else
	{
		return "";
	}
}

char * XMLTree::getAddrName(std::string const & xpath) const
{
	char * result(0);

	XPathContext context(_doc);
	xmlNodePtr node(nodeFromPath(context.get(), xpath, false));
	if (node)
	{
		result = (char *) node->name;
	}
	return result;
}

std::string XMLTree::getName(std::string const & xpath) const
{
	char * result(getAddrName(xpath));
	if (result)
	{
		return result;
	}
	else
	{
		return "";
	}
}

void XMLTree::setValue(std::string const & xpath, char * value)
{
	XPathContext context(_doc);
	xmlNodePtr node(nodeFromPath(context.get(), xpath, true));
	xmlNodeSetContent(node, (xmlChar *) value);
}

void XMLTree::setValue(std::string const & xpath, std::string const & value)
{
	setValue(xpath, (char *) value.c_str());
}

std::string XMLTree::getSiblingXML(std::string const & xpath) const
{
	std::string result;

	if (xpath == "/")
	{
		// Note: xmlNodeDump segfaults with xpath="/" see XMLDump.h
		result = getXML();
	}
	else
	{
		UI::Util::auto_ptr<XMLNodeSet> ns = this->getNodeSet(xpath);
		for (XMLNodeSet::Iterator i = (ns.get())->begin() ; i != (ns.get())->end(); ++i)
		{
			result.append((*i).getNodeDump());
		}
	}
	return result;
}


std::string XMLTree::getXML(std::string const & xpath) const
{
	return XMLDump(this, xpath).get();
}

int XMLTree::getCount(xmlChar const * xpath) const
{
	return getNewInterfaceConst()->getNodeSet((char *)xpath).size();
}

int XMLTree::getCount(char const * xpath) const
{
	return getCount((xmlChar *) xpath);
}

int XMLTree::getCount(std::string const & xpath) const
{
	return getCount(xpath.c_str());
}


void XMLTree::delTag(std::string const & xpath)
{
	XPathContext context(_doc);
	xmlNodePtr node(nodeFromPath(context.get(), xpath, true));
	xmlUnlinkNode(node);
	xmlFreeNode(node);
}

void XMLTree::addTag(std::string const & xpath, std::string const & name, std::string const & content)
{
	XPathContext context(_doc);
	xmlNodePtr node(nodeFromPath(context.get(), xpath, true));
	xmlNodePtr newNode(xmlNewChild(node, 0, (xmlChar *) name.c_str(), (xmlChar *) content.c_str()));
	if (!newNode)
	{
		UI_THROW_CODE(NodeCreation_, "addTag: Can't create new node");
	}
}

void XMLTree::addSiblingTag(std::string const & xpath, std::string const & name, std::string const & content)
{
	XPathContext context(_doc);
	xmlNodePtr node(nodeFromPath(context.get(), xpath, true));
	xmlNodePtr inNode(xmlNewNode(0, (xmlChar *)name.c_str()));
	if (inNode)
	{
		xmlNodeAddContent(inNode, (xmlChar *)content.c_str());
	}
	else
	{
		UI_THROW_CODE(NodeCreation_, "addSiblingTag: Can't create new node");
	}

	xmlNodePtr newNode = xmlAddNextSibling(node, inNode);
	if (!newNode)
	{
		UI_THROW_CODE(NodeCreation_, "addSiblingTag: Can't create new node");
	}
}

void XMLTree::addTree(std::string const & xpath, XMLTree const * tree)
{
	assert(tree);

	XPathContext context(_doc);
	xmlNodePtr node(nodeFromPath(context.get(), xpath, true));

	UI::Util::auto_ptr<XMLNodeSet> ns(tree->getNodeSet("/*"));
	for (XMLNodeSet::Iterator i(ns.get()->begin()); i != ns.get()->end(); ++i)
	{
		xmlNodePtr newNode(xmlCopyNode((*i).getNodePtr(), 1));

		if (!newNode)
		{
			UI_THROW_CODE(NodeCreation_, "addTree: Can't create new node");
		}
		if (xmlAddChild(node, newNode) == 0)
		{
			xmlFreeNode(newNode);
			UI_THROW_CODE(NodeAdding_, "addTree: Can't add node");
		}
	}
}

void XMLTree::addXML(std::string const & xpath, std::string const & xml)
{
	UI::Util::auto_ptr<XMLTree> xmlTree(new XMLTree(xml));
	return addTree(xpath, xmlTree.get());
}

////////////////////////////////////////////////////////////////////////
// Utility functions
//
xmlNodePtr XMLTree::nodeFromPath(xmlXPathContextPtr const context, xmlChar const * xpath, bool doThrow)
{
	assert(context);

	xmlNodePtr result(0);

	try
	{
		XPathObject obj(context, (char *) xpath);
		if (obj->nodesetval && (obj->nodesetval->nodeNr > 0))
		{
			result = obj->nodesetval->nodeTab[0];
		}
		else
		{
			UI_THROW_CODE(NoNodeFromPath_, std::string("No node for xpath: ") + (char *) xpath);
		}
	}
	catch (...)
	{
		if (doThrow)
		{
			throw;
		}
	}
	return result;
}

xmlNodePtr XMLTree::nodeFromPath(xmlXPathContextPtr const context, std::string const & xpath, bool doThrow)
{
	return nodeFromPath(context, (xmlChar *) xpath.c_str(), doThrow);
}

UI::Util::auto_ptr<XMLTree> XMLTree::getTree(std::string const & xpath)
{
	return UI::Util::auto_ptr<XMLTree>(new XMLTree(getXML(xpath)));
}

}}
