/*
 * Copyright (c) 2010 Remko Tronçon
 * Licensed under the GNU General Public License v3.
 * See Documentation/Licenses/GPLv3.txt for more information.
 */

#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <vector>

#include <string>
#include <Swiften/Parser/XMLParserClient.h>
#ifdef HAVE_EXPAT
#include <Swiften/Parser/ExpatParser.h>
#endif
#ifdef HAVE_LIBXML
#include <Swiften/Parser/LibXMLParser.h>
#endif

using namespace Swift;

template <typename ParserType>
class XMLParserTest : public CppUnit::TestFixture {
		CPPUNIT_TEST_SUITE(XMLParserTest);
		CPPUNIT_TEST(testParse_NestedElements);
		CPPUNIT_TEST(testParse_ElementInNamespacedElement);
		CPPUNIT_TEST(testParse_CharacterData);
		CPPUNIT_TEST(testParse_XMLEntity);
		CPPUNIT_TEST(testParse_NamespacePrefix);
		CPPUNIT_TEST(testParse_UnhandledXML);
		CPPUNIT_TEST(testParse_InvalidXML);
		CPPUNIT_TEST(testParse_InErrorState);
		CPPUNIT_TEST(testParse_Incremental);
		CPPUNIT_TEST(testParse_WhitespaceInAttribute);
		CPPUNIT_TEST(testParse_AttributeWithoutNamespace);
		CPPUNIT_TEST(testParse_AttributeWithNamespace);
		CPPUNIT_TEST(testParse_BillionLaughs);
		CPPUNIT_TEST(testParse_InternalEntity);
		//CPPUNIT_TEST(testParse_UndefinedPrefix);
		//CPPUNIT_TEST(testParse_UndefinedAttributePrefix);
		CPPUNIT_TEST_SUITE_END();

	public:
		void testParse_NestedElements() {
			ParserType testling(&client_);

			CPPUNIT_ASSERT(testling.parse(
				"<iq type=\"get\">"
				"<query xmlns='jabber:iq:version'/>"
				"</iq>"));

			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), client_.events.size());

			CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type);
			CPPUNIT_ASSERT_EQUAL(std::string("iq"), client_.events[0].data);
			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), client_.events[0].attributes.getEntries().size());
			CPPUNIT_ASSERT_EQUAL(std::string("get"), client_.events[0].attributes.getAttribute("type"));
			CPPUNIT_ASSERT_EQUAL(std::string(), client_.events[0].ns);

			CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[1].type);
			CPPUNIT_ASSERT_EQUAL(std::string("query"), client_.events[1].data);
			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(0), client_.events[1].attributes.getEntries().size());
			CPPUNIT_ASSERT_EQUAL(std::string("jabber:iq:version"), client_.events[1].ns);

			CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[2].type);
			CPPUNIT_ASSERT_EQUAL(std::string("query"), client_.events[2].data);
			CPPUNIT_ASSERT_EQUAL(std::string("jabber:iq:version"), client_.events[2].ns);

			CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[3].type);
			CPPUNIT_ASSERT_EQUAL(std::string("iq"), client_.events[3].data);
			CPPUNIT_ASSERT_EQUAL(std::string(), client_.events[3].ns);
		}

		void testParse_ElementInNamespacedElement() {
			ParserType testling(&client_);

			CPPUNIT_ASSERT(testling.parse(
				"<query xmlns='jabber:iq:version'>"
					"<name>Swift</name>"
				"</query>"));

			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(5), client_.events.size());

			CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type);
			CPPUNIT_ASSERT_EQUAL(std::string("query"), client_.events[0].data);
			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(0), client_.events[0].attributes.getEntries().size());
			CPPUNIT_ASSERT_EQUAL(std::string("jabber:iq:version"), client_.events[0].ns);

			CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[1].type);
			CPPUNIT_ASSERT_EQUAL(std::string("name"), client_.events[1].data);
			CPPUNIT_ASSERT_EQUAL(std::string("jabber:iq:version"), client_.events[1].ns);

			CPPUNIT_ASSERT_EQUAL(Client::CharacterData, client_.events[2].type);
			CPPUNIT_ASSERT_EQUAL(std::string("Swift"), client_.events[2].data);

			CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[3].type);
			CPPUNIT_ASSERT_EQUAL(std::string("name"), client_.events[3].data);
			CPPUNIT_ASSERT_EQUAL(std::string("jabber:iq:version"), client_.events[3].ns);

			CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[4].type);
			CPPUNIT_ASSERT_EQUAL(std::string("query"), client_.events[4].data);
			CPPUNIT_ASSERT_EQUAL(std::string("jabber:iq:version"), client_.events[4].ns);
		}

		void testParse_CharacterData() {
			ParserType testling(&client_);

			CPPUNIT_ASSERT(testling.parse("<html>bla<i>bli</i>blo</html>"));

			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(7), client_.events.size());

			CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type);
			CPPUNIT_ASSERT_EQUAL(std::string("html"), client_.events[0].data);

			CPPUNIT_ASSERT_EQUAL(Client::CharacterData, client_.events[1].type);
			CPPUNIT_ASSERT_EQUAL(std::string("bla"), client_.events[1].data);

			CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[2].type);
			CPPUNIT_ASSERT_EQUAL(std::string("i"), client_.events[2].data);

			CPPUNIT_ASSERT_EQUAL(Client::CharacterData, client_.events[3].type);
			CPPUNIT_ASSERT_EQUAL(std::string("bli"), client_.events[3].data);

			CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[4].type);
			CPPUNIT_ASSERT_EQUAL(std::string("i"), client_.events[4].data);

			CPPUNIT_ASSERT_EQUAL(Client::CharacterData, client_.events[5].type);
			CPPUNIT_ASSERT_EQUAL(std::string("blo"), client_.events[5].data);

			CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[6].type);
			CPPUNIT_ASSERT_EQUAL(std::string("html"), client_.events[6].data);
		}

		void testParse_XMLEntity() {
			ParserType testling(&client_);

			CPPUNIT_ASSERT(testling.parse("<html>&lt;&gt;</html>"));

			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), client_.events.size());

			CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type);
			CPPUNIT_ASSERT_EQUAL(std::string("html"), client_.events[0].data);

			CPPUNIT_ASSERT_EQUAL(Client::CharacterData, client_.events[1].type);
			CPPUNIT_ASSERT_EQUAL(std::string("<"), client_.events[1].data);

			CPPUNIT_ASSERT_EQUAL(Client::CharacterData, client_.events[2].type);
			CPPUNIT_ASSERT_EQUAL(std::string(">"), client_.events[2].data);

			CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[3].type);
			CPPUNIT_ASSERT_EQUAL(std::string("html"), client_.events[3].data);
		}

		void testParse_NamespacePrefix() {
			ParserType testling(&client_);

			CPPUNIT_ASSERT(testling.parse("<p:x xmlns:p='bla'><p:y/></p:x>"));

			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), client_.events.size());

			CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type);
			CPPUNIT_ASSERT_EQUAL(std::string("x"), client_.events[0].data);
			CPPUNIT_ASSERT_EQUAL(std::string("bla"), client_.events[0].ns);

			CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[1].type);
			CPPUNIT_ASSERT_EQUAL(std::string("y"), client_.events[1].data);
			CPPUNIT_ASSERT_EQUAL(std::string("bla"), client_.events[1].ns);

			CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[2].type);
			CPPUNIT_ASSERT_EQUAL(std::string("y"), client_.events[2].data);
			CPPUNIT_ASSERT_EQUAL(std::string("bla"), client_.events[2].ns);

			CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[3].type);
			CPPUNIT_ASSERT_EQUAL(std::string("x"), client_.events[3].data);
			CPPUNIT_ASSERT_EQUAL(std::string("bla"), client_.events[3].ns);
		}

		void testParse_UnhandledXML() {
			ParserType testling(&client_);

			CPPUNIT_ASSERT(testling.parse("<iq><!-- Testing --></iq>"));

			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), client_.events.size());

			CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type);
			CPPUNIT_ASSERT_EQUAL(std::string("iq"), client_.events[0].data);

			CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[1].type);
			CPPUNIT_ASSERT_EQUAL(std::string("iq"), client_.events[1].data);
		}

		void testParse_InvalidXML() {
			ParserType testling(&client_);

			CPPUNIT_ASSERT(!testling.parse("<iq><bla></iq>"));
		}

		void testParse_InErrorState() {
			ParserType testling(&client_);

			CPPUNIT_ASSERT(!testling.parse("<iq><bla></iq>"));
			CPPUNIT_ASSERT(!testling.parse("<iq/>"));
		}

		void testParse_Incremental() {
			ParserType testling(&client_);

			CPPUNIT_ASSERT(testling.parse("<iq"));
			CPPUNIT_ASSERT(testling.parse("></iq>"));

			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(2), client_.events.size());

			CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type);
			CPPUNIT_ASSERT_EQUAL(std::string("iq"), client_.events[0].data);

			CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[1].type);
			CPPUNIT_ASSERT_EQUAL(std::string("iq"), client_.events[1].data);
		}

		void testParse_WhitespaceInAttribute() {
			ParserType testling(&client_);

			CPPUNIT_ASSERT(testling.parse(
				"<query xmlns='http://www.xmpp.org/extensions/xep-0084.html#ns-data '>"));
			CPPUNIT_ASSERT(testling.parse(
				"<presence/>"));
			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(3), client_.events.size());
			CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type);
			CPPUNIT_ASSERT_EQUAL(std::string("query"), client_.events[0].data);
			CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[1].type);
			CPPUNIT_ASSERT_EQUAL(std::string("presence"), client_.events[1].data);
			CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[2].type);
			CPPUNIT_ASSERT_EQUAL(std::string("presence"), client_.events[2].data);
		}

		void testParse_AttributeWithoutNamespace() {
			ParserType testling(&client_);

			CPPUNIT_ASSERT(testling.parse(
				"<query xmlns='http://swift.im' attr='3'/>"));

			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), client_.events[0].attributes.getEntries().size());
			CPPUNIT_ASSERT_EQUAL(std::string("attr"), client_.events[0].attributes.getEntries()[0].getAttribute().getName());
			CPPUNIT_ASSERT_EQUAL(std::string(""), client_.events[0].attributes.getEntries()[0].getAttribute().getNamespace());
		}

		void testParse_AttributeWithNamespace() {
			ParserType testling(&client_);

			CPPUNIT_ASSERT(testling.parse(
				"<query xmlns='http://swift.im' xmlns:f='http://swift.im/f' f:attr='3'/>"));

			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), client_.events[0].attributes.getEntries().size());
			CPPUNIT_ASSERT_EQUAL(std::string("attr"), client_.events[0].attributes.getEntries()[0].getAttribute().getName());
			CPPUNIT_ASSERT_EQUAL(std::string("http://swift.im/f"), client_.events[0].attributes.getEntries()[0].getAttribute().getNamespace());
		}

		void testParse_BillionLaughs() {
			ParserType testling(&client_);

			CPPUNIT_ASSERT(!testling.parse(
				"<?xml version=\"1.0\"?>"
				"<!DOCTYPE lolz ["
				"  <!ENTITY lol \"lol\">"
				"  <!ENTITY lol2 \"&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;&lol;\">"
				"  <!ENTITY lol3 \"&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;&lol2;\">"
				"  <!ENTITY lol4 \"&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;&lol3;\">"
				"  <!ENTITY lol5 \"&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;&lol4;\">"
				"  <!ENTITY lol6 \"&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;&lol5;\">"
				"  <!ENTITY lol7 \"&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;&lol6;\">"
				"  <!ENTITY lol8 \"&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;&lol7;\">"
				"  <!ENTITY lol9 \"&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;&lol8;\">"
				"]>"
				"<lolz>&lol9;</lolz>"
			));
		}

		void testParse_InternalEntity() {
			ParserType testling(&client_);

			CPPUNIT_ASSERT(!testling.parse("<!DOCTYPE foo [<!ENTITY bar \"Bar\">]><foo>&bar;</foo>"));
		}

		void testParse_UndefinedPrefix() {
			ParserType testling(&client_);

			CPPUNIT_ASSERT(testling.parse(
				"<foo:bar><bla/></foo:bar>"));

			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(4), client_.events.size());

			CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[0].type);
			CPPUNIT_ASSERT_EQUAL(std::string("foo:bar"), client_.events[0].data);
			CPPUNIT_ASSERT_EQUAL(std::string(""), client_.events[0].ns);

			CPPUNIT_ASSERT_EQUAL(Client::StartElement, client_.events[1].type);
			CPPUNIT_ASSERT_EQUAL(std::string("bla"), client_.events[1].data);
			CPPUNIT_ASSERT_EQUAL(std::string(""), client_.events[1].ns);

			CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[2].type);
			CPPUNIT_ASSERT_EQUAL(std::string("bla"), client_.events[2].data);
			CPPUNIT_ASSERT_EQUAL(std::string(""), client_.events[2].ns);

			CPPUNIT_ASSERT_EQUAL(Client::EndElement, client_.events[3].type);
			CPPUNIT_ASSERT_EQUAL(std::string("foo:bar"), client_.events[3].data);
			CPPUNIT_ASSERT_EQUAL(std::string(""), client_.events[3].ns);
		}

		void testParse_UndefinedAttributePrefix() {
			ParserType testling(&client_);

			CPPUNIT_ASSERT(testling.parse(
				"<foo bar:baz='bla'/>"));

			CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), client_.events[0].attributes.getEntries().size());
			CPPUNIT_ASSERT_EQUAL(std::string("bar:baz"), client_.events[0].attributes.getEntries()[0].getAttribute().getName());
		}
	
	private:
		class Client : public XMLParserClient {
			public:
				enum Type { StartElement, EndElement, CharacterData };
				struct Event {
					Event(
							Type type, 
							const std::string& data, 
							const std::string& ns,
							const AttributeMap& attributes)
								: type(type), data(data), ns(ns), attributes(attributes) {}
					Event(Type type, const std::string& data, const std::string& ns = std::string())
								: type(type), data(data), ns(ns) {}

					Type type;
					std::string data;
					std::string ns;
					AttributeMap attributes;
				};

				Client() {}

				virtual void handleStartElement(const std::string& element, const std::string& ns, const AttributeMap& attributes) {
					events.push_back(Event(StartElement, element, ns, attributes));
				}

				virtual void handleEndElement(const std::string& element, const std::string& ns) {
					events.push_back(Event(EndElement, element, ns));
				}

				virtual void handleCharacterData(const std::string& data) {
					events.push_back(Event(CharacterData, data));
				}

				std::vector<Event> events;
		} client_;
};

#ifdef HAVE_EXPAT
CPPUNIT_TEST_SUITE_REGISTRATION(XMLParserTest<ExpatParser>);
#endif
#ifdef HAVE_LIBXML
CPPUNIT_TEST_SUITE_REGISTRATION(XMLParserTest<LibXMLParser>);
#endif
