/*
 * Copyright (c) 2010-2016 Isode Limited.
 * All rights reserved.
 * See the COPYING file for more information.
 */

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

#include <Swiften/Elements/Payload.h>
#include <Swiften/Elements/Stanza.h>
#include <Swiften/Parser/GenericPayloadParser.h>
#include <Swiften/Parser/PayloadParserFactory.h>
#include <Swiften/Parser/PayloadParserFactoryCollection.h>
#include <Swiften/Parser/StanzaParser.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_ = std::make_shared<MyStanza>();
                }

                virtual std::shared_ptr<ToplevelElement> getElement() const {
                    return stanza_;
                }

            private:
                std::shared_ptr<MyStanza> stanza_;
        };

        MyPayload1ParserFactory factory1_;
        MyPayload2ParserFactory factory2_;
        PayloadParserFactoryCollection* factoryCollection_;
};

CPPUNIT_TEST_SUITE_REGISTRATION(StanzaParserTest);
