/*
 * 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 <Swiften/Parser/StanzaParser.h>
#include <Swiften/Parser/GenericPayloadParser.h>
#include <Swiften/Parser/PayloadParserFactory.h>
#include <Swiften/Parser/PayloadParserFactoryCollection.h>
#include <Swiften/Elements/Stanza.h>
#include <Swiften/Elements/Payload.h>

using namespace Swift;

class StanzaParserTest : public CppUnit::TestFixture {
		CPPUNIT_TEST_SUITE(StanzaParserTest);
		CPPUNIT_TEST(testHandleEndElement_OnePayload);
		CPPUNIT_TEST(testHandleEndElement_MultiplePayloads);
		CPPUNIT_TEST(testHandleEndElement_StrayCharacterData);
		CPPUNIT_TEST(testHandleEndElement_UnknownPayload);
		CPPUNIT_TEST(testHandleParse_BasicAttributes);
		CPPUNIT_TEST_SUITE_END();

	public:
		void setUp() {
			factoryCollection_ = new PayloadParserFactoryCollection();
			factoryCollection_->addFactory(&factory1_);
			factoryCollection_->addFactory(&factory2_);
		}

		void tearDown() {
			delete factoryCollection_;
		}

		void testHandleEndElement_OnePayload() {
			MyStanzaParser testling(factoryCollection_);

			AttributeMap attributes;
			attributes.addAttribute("foo", "", "fum");
			attributes.addAttribute("bar", "", "baz");
			testling.handleStartElement("mystanza", "", attributes);
			testling.handleStartElement("mypayload1", "", attributes);
			testling.handleStartElement("child", "", attributes);
			testling.handleEndElement("child", "");
			testling.handleEndElement("mypayload1", "");
			testling.handleEndElement("mystanza", "");

			CPPUNIT_ASSERT(testling.getStanza()->getPayload<MyPayload1>());
			CPPUNIT_ASSERT(testling.getStanza()->getPayload<MyPayload1>()->hasChild);
		}

		void testHandleEndElement_MultiplePayloads() {
			MyStanzaParser testling(factoryCollection_);

			AttributeMap attributes;
			testling.handleStartElement("mystanza", "", attributes);
			testling.handleStartElement("mypayload1", "", attributes);
			testling.handleEndElement("mypayload1", "");
			testling.handleStartElement("mypayload2", "", attributes);
			testling.handleEndElement("mypayload2", "");
			testling.handleEndElement("mystanza", "");

			CPPUNIT_ASSERT(testling.getStanza()->getPayload<MyPayload1>());
			CPPUNIT_ASSERT(testling.getStanza()->getPayload<MyPayload2>());
		}

		void testHandleEndElement_StrayCharacterData() {
			MyStanzaParser testling(factoryCollection_);

			AttributeMap attributes;
			testling.handleStartElement("mystanza", "", attributes);
			testling.handleStartElement("mypayload1", "", attributes);
			testling.handleEndElement("mypayload1", "");
			testling.handleCharacterData("bla");
			testling.handleStartElement("mypayload2", "", attributes);
			testling.handleEndElement("mypayload2", "");
			testling.handleEndElement("mystanza", "");

			CPPUNIT_ASSERT(testling.getStanza()->getPayload<MyPayload1>());
			CPPUNIT_ASSERT(testling.getStanza()->getPayload<MyPayload2>());
		}

		void testHandleEndElement_UnknownPayload() {
			MyStanzaParser testling(factoryCollection_);

			AttributeMap attributes;
			testling.handleStartElement("mystanza", "", attributes);
			testling.handleStartElement("mypayload1", "", attributes);
			testling.handleEndElement("mypayload1", "");
			testling.handleStartElement("unknown-payload", "", attributes);
			testling.handleStartElement("unknown-payload-child", "", attributes);
			testling.handleEndElement("unknown-payload-child", "");
			testling.handleEndElement("unknown-payload", "");
			testling.handleStartElement("mypayload2", "", attributes);
			testling.handleEndElement("mypayload2", "");
			testling.handleEndElement("mystanza", "");

			CPPUNIT_ASSERT(testling.getStanza()->getPayload<MyPayload1>());
			CPPUNIT_ASSERT(testling.getStanza()->getPayload<MyPayload2>());
		}

		void testHandleParse_BasicAttributes() {
			MyStanzaParser testling(factoryCollection_);

			AttributeMap attributes;
			attributes.addAttribute("to", "", "foo@example.com/blo");
			attributes.addAttribute("from", "", "bar@example.com/baz");
			attributes.addAttribute("id", "", "id-123");
			testling.handleStartElement("mystanza", "", attributes);
			testling.handleEndElement("mypayload1", "");

			CPPUNIT_ASSERT_EQUAL(JID("foo@example.com/blo"), testling.getStanza()->getTo());
			CPPUNIT_ASSERT_EQUAL(JID("bar@example.com/baz"), testling.getStanza()->getFrom());
			CPPUNIT_ASSERT_EQUAL(std::string("id-123"), testling.getStanza()->getID());
		}

	private:
		class MyPayload1 : public Payload
		{
			public:
				MyPayload1() : hasChild(false) {}

				bool hasChild;
		};

		class MyPayload1Parser : public GenericPayloadParser<MyPayload1>
		{
			public:	
				MyPayload1Parser() {}

			virtual void handleStartElement(const std::string& element, const std::string&, const AttributeMap&) { 
				if (element != "mypayload1") {
					getPayloadInternal()->hasChild = true;
				}
			}

			virtual void handleEndElement(const std::string&, const std::string&) {}
			virtual void handleCharacterData(const std::string&) {}
		};

		class MyPayload1ParserFactory : public PayloadParserFactory
		{
			public:
				MyPayload1ParserFactory() {}

				PayloadParser* createPayloadParser() { return new MyPayload1Parser(); }

				bool canParse(const std::string& element, const std::string&, const AttributeMap&) const {
					return element == "mypayload1";
				}
		};

		class MyPayload2 : public Payload
		{
			public:
				MyPayload2() {}
		};

		class MyPayload2Parser : public GenericPayloadParser<MyPayload2>
		{
			public:	
				MyPayload2Parser() {}

			virtual void handleStartElement(const std::string&, const std::string&, const AttributeMap&) {}
			virtual void handleEndElement(const std::string&, const std::string&) {}
			virtual void handleCharacterData(const std::string&) {}
		};


		class MyPayload2ParserFactory : public PayloadParserFactory
		{
			public:
				MyPayload2ParserFactory() {}

				PayloadParser* createPayloadParser() { return new MyPayload2Parser(); }
				bool canParse(const std::string& element, const std::string&, const AttributeMap&) const {
					return element == "mypayload2";
				}
		};

		class MyStanza : public Stanza
		{
			public:
				MyStanza() {}
		};

		class MyStanzaParser : public StanzaParser
		{
			public:
				MyStanzaParser(PayloadParserFactoryCollection* collection) : StanzaParser(collection)
				{
					stanza_ = boost::make_shared<MyStanza>();
				}
			
				virtual boost::shared_ptr<Element> getElement() const {
					return stanza_;
				}

			private:
				boost::shared_ptr<MyStanza> stanza_;
		};
	
		MyPayload1ParserFactory factory1_;
		MyPayload2ParserFactory factory2_;
		PayloadParserFactoryCollection* factoryCollection_;
};

CPPUNIT_TEST_SUITE_REGISTRATION(StanzaParserTest);