/*
 *
 *  D-Bus++ - C++ bindings for D-Bus
 *
 *  Copyright (C) 2005-2007  Paolo Durante <shackan@gmail.com>
 *
 *
 *  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
 *
 */


#include "xml.h"

#include <dbus-c++/debug.h>

#include <expat.h>

std::istream& operator >> ( std::istream& in, DBus::Xml::Document& doc )
{
	std::stringbuf xmlbuf;
	in.get(xmlbuf, '\0');
	doc.from_xml(xmlbuf.str());

	return in;
}

std::ostream& operator << ( std::ostream& out, const DBus::Xml::Document& doc )
{
	return out << doc.to_xml();
}

using namespace DBus;
using namespace DBus::Xml;

Error::Error( const char* error, int line, int column )
{
	std::ostringstream estream;

	estream << "line " << line << ", column " << column << ": " << error;

	_error = estream.str();
}

Node::Node( const char* n, const char** a )
: name(n)
{
	if(a)
	for(int i = 0; a[i]; i += 2)
	{
		_attrs[a[i]] = a[i+1];

		//debug_log("xml:\t%s = %s", a[i], a[i+1]);
	}
}

Nodes Nodes::operator[]( const std::string& key )
{
	Nodes result;

	for(iterator i = begin(); i != end(); ++i)
	{
		Nodes part = (**i)[key];

		result.insert(result.end(), part.begin(), part.end());
	}
	return result;
}

Nodes Nodes::select( const std::string& attr, const std::string& value )
{
	Nodes result;

	for(iterator i = begin(); i != end(); ++i)
	{
		if((*i)->get(attr) == value)
			result.insert(result.end(), *i);
	}
	return result;
}

Nodes Node::operator[]( const std::string& key )
{
	Nodes result;

	if(key.length() == 0) return result;

	for(Children::iterator i = children.begin(); i != children.end(); ++i)
	{
		if(i->name == key)
			result.push_back(&(*i));
	}
	return result;
}

std::string Node::get( const std::string& attribute )
{
	if(_attrs.find(attribute) != _attrs.end())
		return _attrs[attribute];
	else
		return "";
}

void Node::set( const std::string& attribute, std::string value )
{
	if(value.length())
		_attrs[attribute] = value;
	else 
		_attrs.erase(value);
}

std::string Node::to_xml() const
{
	std::string xml;
	int depth = 0;

	_raw_xml(xml, depth);

	return xml;
}

void Node::_raw_xml( std::string& xml, int& depth ) const
{
	xml.append(depth*2, ' ');
	xml.append("<"+name);

	for(Attributes::const_iterator i = _attrs.begin(); i != _attrs.end(); ++i)
	{
		xml.append(" "+i->first+"=\""+i->second+"\"");
	}

	if(cdata.length() == 0 && children.size() == 0)
	{
		xml.append("/>\n");
	}
	else
	{
		xml.append(">");
		
		if(cdata.length())
		{
			xml.append(cdata);
		}

		if(children.size())
		{
			xml.append("\n");
			depth++;

			for(Children::const_iterator i = children.begin(); i != children.end(); ++i)
			{
				i->_raw_xml(xml, depth);
			}

			depth--;
			xml.append(depth*2, ' ');
		}
		xml.append("</"+name+">\n");
	}
}

Document::Document()
: root(0), _depth(0)
{
}
	
Document::Document( const std::string& xml )
: root(0), _depth(0)
{
	from_xml(xml);
}

Document::~Document()
{
	delete root;
}

struct Document::Expat
{
	static void start_doctype_decl_handler(
		void* data, const XML_Char* name, const XML_Char* sysid, const XML_Char* pubid, int has_internal_subset
	);
	static void end_doctype_decl_handler( void* data );
	static void start_element_handler( void *data, const XML_Char *name, const XML_Char **atts );
	static void character_data_handler( void *data, const XML_Char* chars, int len );
	static void end_element_handler( void *data, const XML_Char *name );
};

void Document::from_xml( const std::string& xml )
{
	_depth = 0;
	delete root;
	root = 0;

	XML_Parser parser = XML_ParserCreate("UTF-8");

	XML_SetUserData(parser, this);

	XML_SetDoctypeDeclHandler(
		parser,
		Document::Expat::start_doctype_decl_handler,
		Document::Expat::end_doctype_decl_handler
	);

	XML_SetElementHandler(
		parser,
		Document::Expat::start_element_handler,
		Document::Expat::end_element_handler
	);

	XML_SetCharacterDataHandler(
		parser,
		Document::Expat::character_data_handler
	);

	XML_Status status = XML_Parse(parser, xml.c_str(), xml.length(), true);

	if(status == XML_STATUS_ERROR)
	{
		const char* error = XML_ErrorString(XML_GetErrorCode(parser));
		int line = XML_GetCurrentLineNumber(parser);
		int column = XML_GetCurrentColumnNumber(parser);

		XML_ParserFree(parser);

		throw Error(error, line, column);
	}
	else
	{
		XML_ParserFree(parser);
	}
}

std::string Document::to_xml() const
{
	return root->to_xml();
}

void Document::Expat::start_doctype_decl_handler(
	void* data, const XML_Char* name, const XML_Char* sysid, const XML_Char* pubid, int has_internal_subset
)
{
}

void Document::Expat::end_doctype_decl_handler( void* data )
{
}

void Document::Expat::start_element_handler( void *data, const XML_Char *name, const XML_Char **atts )
{
	Document* doc = (Document*)data;

	//debug_log("xml:%d -> %s", doc->_depth, name);

	if(!doc->root)
	{
		doc->root = new Node(name, atts);
	}
	else
	{
		Node::Children* cld = &(doc->root->children);

		for(int i = 1; i < doc->_depth; ++i)
		{
			cld = &(cld->back().children);
		}
		cld->push_back(Node(name, atts));

		//std::cerr << doc->to_xml() << std::endl;
	}
	doc->_depth++;
}

void Document::Expat::character_data_handler( void *data, const XML_Char* chars, int len )
{
	Document* doc = (Document*)data;

	Node* nod = doc->root;

	for(int i = 1; i < doc->_depth; ++i)
	{
		nod = &(nod->children.back());
	}
	int x, y;

	x = 0;
	y = len-1;

	while(isspace(chars[y]) && y > 0) --y;
	while(isspace(chars[x]) && x < y) ++x;

	nod->cdata = std::string(chars, x, y+1);
}

void Document::Expat::end_element_handler( void *data, const XML_Char *name )
{
	Document* doc = (Document*)data;

	//debug_log("xml:%d <- %s", doc->_depth, name);

	doc->_depth--;
}

