/*
 * (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"

// STDC++
#include <sstream>
#include <fstream>

// C++ Libraries
#define UI_GXMLCPP_IGNORE_DEPRECATED
#include <ui-gxmlcpp/XMLTree.hpp>
#include <ui-gxmlcpp/XMLDump.hpp>
#include <ui-gxmlcpp/XSLTrans.hpp>
#include <ui-gxmlcpp/XMLNode.hpp>
#undef UI_GXMLCPP_IGNORE_DEPRECATED

#define BOOST_AUTO_TEST_MAIN
#define BOOST_TEST_DYN_LINK
#include <boost/test/auto_unit_test.hpp>
#include <boost/test/floating_point_comparison.hpp>

namespace UI {
namespace GXML {

BOOST_AUTO_TEST_CASE(compat10_XMLTree_old)
{
	// ^^ Note: dump will always use " (not ') to quote, so we must do this here too for string compare
	const std::string xmlString(
		"<?xml version=\"1.0\"?>\n"
		"<req>\n"
		" <uid>5</uid>\n"
		" <home>/home/absurd</home>\n"
		" <key>AAAABBBBCCCCDDDD</key>\n"
		"\n"
		" <a>\n"
		"   <b>\n"
		"     <c>Wert1</c>\n"
		"   </b>\n"
		"   <b>\n"
		"     <c> Wert 2</c>\n"
		"  </b>\n"
		" </a>\n"
		"\n"
		" <X>5</X>\n"
		"\n"
		" <int>-42</int>\n"
		" <int>17</int>\n"
		" <bool>0</bool>\n"
		" <string> abc def </string>\n"
		"\n"
		" <X>13</X>\n"
		"\n"
		"<tags>\n"
		" <subtag>EINS</subtag>\n"
		"</tags>\n"
		"\n"
		" <tags>\n"
		"  <subtag>ZWEI</subtag>\n"
		" </tags>\n"
		"\n"
		" <X>17</X>\n"
		"\n"
		"</req>\n");

	// ^^ Note: dump will always add a \n to the last line (in a full ("/") dump, that is), so our string should have this too for string compare
	// Note2: It's a bit dumb to literally compare dumps here, as the default way of producing dumps might change... §STS§

	// Partial Tree && Dump
	XMLTree xmlTree1(xmlString.c_str(), xmlString.size());
	std::string dump1 = XMLDump(&xmlTree1, "/req/tags").get();
	BOOST_CHECK(dump1 == "<tags><subtag>EINS</subtag></tags>");

	// Full Tree && Dump
	XMLTree xmlTree(xmlString.c_str(), xmlString.size());
	std::string dump = XMLDump(&xmlTree, "/").get();
	// §STS§ This does not work anymore with default encoding added in XMLDump
	//BOOST_CHECK(dump == xmlString);

	// XMLTree methods
	BOOST_CHECK(xmlTree.getValue("/req/string") == " abc def ");

	BOOST_CHECK(xmlTree.getCount("/req/int") == 2);
	BOOST_CHECK(xmlTree.getValue("/req/int") == "-42");
	BOOST_CHECK(xmlTree.getValue("/req/int[position()=2]") == "17");

	BOOST_CHECK(xmlTree.getCount("/req/tags/subtag") == 2);
	BOOST_CHECK(xmlTree.getValue("/req/tags/subtag") == "EINS");

	BOOST_CHECK(xmlTree.getCount("/req/nonexisting") == 0);
	BOOST_CHECK(xmlTree.getCount("/nonexisting/tag") == 0);
	BOOST_CHECK(xmlTree.getValue("/nonexisting/tag") == "");

	xmlTree.delTag("/req/int");
	BOOST_CHECK(xmlTree.getCount("/req/int") == 1);

	xmlTree.addTag("/req", "newtag", "contents of new tag");
	BOOST_CHECK(xmlTree.getValue("/req/newtag") == "contents of new tag");
}

BOOST_AUTO_TEST_CASE(compat10_XSLTrans)
{
	std::string xmlString(
		"<?xml version=\"1.0\"?>\n"
		"<!DOCTYPE guestbook>\n"
		"\n"
		"<guestbook>\n"
		"	<record>\n"
		"		<name>kurt</name>\n"
		"		<date>2.3.01</date>\n"
		"		<text>\n"
		"		find ich total klasse, koennte aber noch besser sein\n"
		"		</text>\n"
		"	</record>\n"
		"	<record>\n"
		"		<name>kurt</name>\n"
		"		<date>2.3.01</date>\n"
		"		<text>\n"
		"		find ich total klasse, koennte aber noch besser sein\n"
		"		</text>\n"
		"	</record>\n"
		"</guestbook>\n");

	std::string xslString(
		"<?xml version=\"1.0\"?>\n"
		"<xsl:stylesheet version=\"1.0\" xmlns:xsl=\"http://www.w3.org/1999/XSL/Transform\">\n"
		"<!-- Basic stylesheet for GUESTBOOK -->\n"
		"<xsl:output indent=\"yes\" />\n"
		"<xsl:template match=\"guestbook\">\n"
		"	<html>\n"
		"		<head>\n"
		"			<title>Gaestebuch</title>\n"
		"		</head>	\n"
		"		<body bgcolor=\"FFFFFF\">\n"
		"			<h1>Mein Gaestebuch</h1>\n"
		"			<center>\n"
		"			<table cellspacing=\"2\" cellpadding=\"3\" border=\"0\">\n"
		"			\n"
		"			<xsl:apply-templates/>\n"
		"			\n"
		"			</table>\n"
		"			</center>\n"
		"		</body>\n"
		"	</html>\n"
		"</xsl:template>\n"
		"\n"
		"<xsl:template match=\"record\">\n"
		"	\n"
		"	<tr> \n"
		"	<td bgcolor=\"336699\"><font face=\"Arial,helvitica\" size=\"-1\" color=\"FFFFFF\">Name</font></td>\n"
		"	<td bgcolor=\"336699\"><font face=\"Arial,helvitica\" size=\"-1\" color=\"FFFFFF\"><xsl:value-of select=\"name\"/></font></td>\n"
		"	</tr>\n"
		"	<tr>\n"
		"	<td bgcolor=\"336699\"><font face=\"Arial,helvitica\" size=\"-1\" color=\"FFFFFF\">Datum</font></td>\n"
		"	<td bgcolor=\"336699\"><font face=\"Arial,helvitica\" size=\"-1\" color=\"FFFFFF\"><xsl:value-of select=\"date\"/></font></td>\n"
		"	</tr>\n"
		"	<tr>\n"
		"	<td bgcolor=\"336699\"><font face=\"Arial,helvitica\" size=\"-1\" color=\"FFFFFF\">Text</font></td>\n"
		"	<td bgcolor=\"336699\"><font face=\"Arial,helvitica\" size=\"-1\" color=\"FFFFFF\"><xsl:value-of select=\"text\"/></font></td>\n"
		"	</tr>\n"
		"	<tr >\n"
		"	<td colspan=\"2\"><hr height=\"1\"/></td>\n"
		"	</tr>\n"
		"	\n"
		"</xsl:template>\n"
		"</xsl:stylesheet>\n");

	XMLTree xmlTree(xmlString);
	XSLTrans xslTrans(xslString);

	std::auto_ptr<XMLTree> resultTree = xslTrans.transToXMLTree(xmlTree.getDocPtr());

	BOOST_CHECK(resultTree.get()->getValue("/html/head/title") == "Gaestebuch");
	BOOST_CHECK(resultTree.get()->getValue("/html/body/h1") == "Mein Gaestebuch");
	BOOST_CHECK(resultTree.get()->getCount("/html/body/center/table") == 1);
}


//////////////////////////////////////////////////////////////////////
// TestXMLTree
//
BOOST_AUTO_TEST_CASE(compat10_XMLTree)
{
	// test XMLTree's following functions:
	// getString
	// getBool (XSLT-functions: boolean, false, true, not)
	// getFloat
	// getNodeSet
	// getXML
	// getTree
	std::string xml(
		"<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
		"<test>"
		 "<string>StringValue</string>"
		 "<string>"
		 "		Also a string value"
		 "</string>"
		 "<boolean>1</boolean>"
		 "<boolean>0</boolean>"
		 "<boolean>-1</boolean>"
		 "<float>3.5</float>"
		 "<float>1.6</float>"
		 "<float>-123.2</float>"
		"</test>");
	XMLTree tree(xml);

	{
		// test getString
		// string( xpath ) returns content of first xpath-node
		// string( invalid_xpath ) returns ""
		BOOST_CHECK( tree.getString( "string( /test/string )" ) == "StringValue" );
		BOOST_CHECK( tree.getString( "string( /test/string[position() = 2] )" ) == "\t\tAlso a string value" );
		BOOST_CHECK( tree.getString( "string( /test/something )" ) == "" );
		try
		{
			// xpath won't return a string
			tree.getString( "/test/string" );
			BOOST_ASSERT( ! "Exception expected!!!" );
		}
		catch (XPathObject::Exception const & e )
		{
			BOOST_CHECK(e.getCode() == XPathObject::NotAString_);
		}
		try
		{
			// xpath won't return a string
			tree.getString( "/test/something" );
			BOOST_CHECK( ! "Exception expected!!!" );
		}
		catch (XPathObject::Exception const & e )
		{
			BOOST_CHECK(e.getCode() == XPathObject::NotAString_);
		}
	}

	{
		// test getBool
		// 0 = false else true, "" = false else true
		BOOST_CHECK( tree.getBool( "boolean( number( /test/boolean ) )" ) );
		BOOST_CHECK( ! tree.getBool( "boolean( number( /test/boolean[position() = 2] ) )" ) );
		BOOST_CHECK( tree.getBool( "boolean( number( /test/boolean[position() = 3] ) )" ) );
		BOOST_CHECK( tree.getBool( "true()" ) );
		BOOST_CHECK( ! tree.getBool( "false()" ) );
		BOOST_CHECK( tree.getBool( "not( /test/something )" ) );
		BOOST_CHECK( ! tree.getBool( "not( /test/string )" ) );
		BOOST_CHECK( tree.getBool( "boolean( /test/string )" ) );
		BOOST_CHECK( ! tree.getBool( "boolean( /test/something )" ) );
		BOOST_CHECK( tree.getBool( "boolean( 'anything' )" ) );
		BOOST_CHECK( ! tree.getBool( "boolean( '' )" ) );
		try
		{
			// xpath won't return a boolean
			tree.getBool( "/test/string" );
			BOOST_CHECK( ! "Exception expected!!!" );
		}
		catch (XPathObject::Exception const & e )
		{
			BOOST_CHECK(e.getCode() == XPathObject::NotABool_);
		}
	}

	{
		// test getFloat
		BOOST_CHECK_CLOSE(double(-118.1), tree.getFloat( "sum( /test/float )" ), 1E-10);
		BOOST_CHECK_CLOSE( double(-123.2), tree.getFloat( "number( /test/float[position() = 3 ])" ), 1E-10 );
		BOOST_CHECK( tree.getFloat( "count( /test/boolean )" ) == 3 );
		try {
			// xpath won't return a number
			tree.getFloat( "/test/string" );
			BOOST_CHECK( ! "Exception expected!!!" );
		}
		catch (XPathObject::Exception const & e )
		{
			BOOST_CHECK(e.getCode() == XPathObject::NotAFloat_);
		}
	}

	{
		std::auto_ptr<XMLNodeSet> set = tree.getNodeSet( "/test/*" );
		BOOST_CHECK( set->size() == 8 );
		try {
			// xpath will return an empty set
			std::auto_ptr<XMLNodeSet> set = tree.getNodeSet( "/something" );
			BOOST_CHECK( ! "Exception expected!!!" );
		}
		catch (XMLNodeSet::Exception const & e )
		{
			BOOST_CHECK(e.getCode() == XMLNodeSet::NoMatch_);
		}
		try {
			// xpath won't return a set
			std::auto_ptr<XMLNodeSet> set = tree.getNodeSet( "true()" );
			BOOST_CHECK( ! "Exception expected!!!" );
		}
		catch (XMLNodeSet::Exception const & e)
		{
			BOOST_CHECK(e.getCode() == XMLNodeSet::NoSet_);
		}
	}

	{
		// Serialisation tests
		BOOST_CHECK( xml + "\n" == tree.getXML( "/" ) );
		BOOST_CHECK( tree.getXML() == tree.getTree()->getXML() );
	}
}

BOOST_AUTO_TEST_CASE(compat10_XMLNode)
{
	// test XMLNode's following functions:
	// getNext
	// getPrev
	// getParent
	// getLast
	// getChild
	// getName
	// getContent
	// getAttribute( name )
	std::string xml(
		"<?xml version=\"1.0\"?>\n"
		"<test><node>node1</node><node name=\"node2\">node2<child>child of node 2</child></node><node>last node</node></test>");
	XMLTree tree(xml);

	XMLNode node1;
	XMLNode node2;
	XMLNode node3;
	std::auto_ptr<XMLNodeSet> set = tree.getNodeSet( "/test/node" );
	for ( XMLNodeSet::Iterator iter = set->begin(); iter != set->end(); ++iter )
	{
		switch ( iter.getPosition() )
		{
		case 0:
			node1 = *iter;
			break;
		case 1:
			node2 = *iter;
			break;
		case 2:
			node3 = *iter;
			break;
		default:
			BOOST_CHECK( false ); //should never get here.
		}
	}

	BOOST_CHECK(node1.getNodePtr());
	BOOST_CHECK(node2.getNodePtr());
	BOOST_CHECK(node3.getNodePtr());


	BOOST_CHECK( node1.getName() == "node" );
	BOOST_CHECK( node1.getContent() == "node1" );
	BOOST_CHECK( node1.getNext().getContent() == node2.getContent() );
	//	BOOST_CHECK( node1.getLast().getContent() == node3.getContent() );

	BOOST_CHECK( node2.getContent() == "node2" );
	BOOST_CHECK( node2.getNext().getContent() == node3.getContent() );
	BOOST_CHECK( node2.getAttribute( "name" ) == "node2" );
	// first child of node2 is its content text-node, therefore take child->next
	BOOST_CHECK( node2.getChild().getNext().getContent() == "child of node 2" );

	BOOST_CHECK( node3.getPrev().getContent() == node2.getContent() );
	BOOST_CHECK( node3.getParent().getName() == node2.getParent().getName() );
	BOOST_CHECK( node3.getParent().getChild().getContent() == node1.getContent() );
	BOOST_CHECK( node3.getParent().getLast().getContent() == node3.getContent() );

	// first child of node with content is a text-node
	BOOST_CHECK( node3.getChild().getName() == "text" );

	// test for exceptions
	try
	{
		node3 = node3.getNext();
		BOOST_CHECK( ! "Exception expected!!!" );
	}
	catch (XMLNode::Exception const & e)
	{
		BOOST_CHECK(e.getCode() == XMLNode::LastNode_);
	}
	try
	{
		node1 = node1.getPrev();
		BOOST_CHECK( ! "Exception expected!!!" );
	}
	catch (XMLNode::Exception const & e)
	{
		BOOST_CHECK(e.getCode() == XMLNode::FirstNode_);
	}
	try
	{
		// parent of parent is document node, which has no parent
		node1 = node1.getParent().getParent().getParent();
		BOOST_CHECK( ! "Exception expected!!!" );
	}
	catch (XMLNode::Exception const & e )
	{
		BOOST_CHECK( e.getCode() == XMLNode::NoParent_ );
	}
	try
	{
		node1 = node1.getPrev();
		BOOST_CHECK( ! "Exception expected!!!" );
	}
	catch (XMLNode::Exception const & e)
	{
		BOOST_CHECK( e.getCode() == XMLNode::FirstNode_ );
	}
}

}}
