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

#include <memory>
#include <vector>

#include <boost/bind.hpp>

#include <QA/Checker/IO.h>

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

#include <Swiften/Base/ByteArray.h>
#include <Swiften/Base/Concat.h>
#include <Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.h>
#include <Swiften/Parser/PlatformXMLParserFactory.h>
#include <Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.h>
#include <Swiften/StreamStack/LowLayer.h>
#include <Swiften/StreamStack/StreamLayer.h>
#include <Swiften/StreamStack/StreamStack.h>
#include <Swiften/StreamStack/XMPPLayer.h>

using namespace Swift;

class StreamStackTest : public CppUnit::TestFixture {
        CPPUNIT_TEST_SUITE(StreamStackTest);
        CPPUNIT_TEST(testWriteData_NoIntermediateStreamStack);
        CPPUNIT_TEST(testWriteData_OneIntermediateStream);
        CPPUNIT_TEST(testWriteData_TwoIntermediateStreamStack);
        CPPUNIT_TEST(testReadData_NoIntermediateStreamStack);
        CPPUNIT_TEST(testReadData_OneIntermediateStream);
        CPPUNIT_TEST(testReadData_TwoIntermediateStreamStack);
        CPPUNIT_TEST(testAddLayer_ExistingOnWriteDataSlot);
        CPPUNIT_TEST_SUITE_END();

    public:
        void setUp() {
            physicalStream_ = new TestLowLayer();
            xmppStream_ = new XMPPLayer(&parserFactories_, &serializers_, &xmlParserFactory_, ClientStreamType);
            elementsReceived_ = 0;
            dataWriteReceived_ = 0;
        }

        void tearDown() {
            delete physicalStream_;
            delete xmppStream_;
        }

        void testWriteData_NoIntermediateStreamStack() {
            StreamStack testling(xmppStream_, physicalStream_);

            xmppStream_->writeData("foo");

            CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), physicalStream_->data_.size());
            CPPUNIT_ASSERT_EQUAL(createSafeByteArray("foo"), physicalStream_->data_[0]);
        }

        void testWriteData_OneIntermediateStream() {
            StreamStack testling(xmppStream_, physicalStream_);
            std::shared_ptr<MyStreamLayer> xStream(new MyStreamLayer("X"));
            testling.addLayer(xStream.get());

            xmppStream_->writeData("foo");

            CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), physicalStream_->data_.size());
            CPPUNIT_ASSERT_EQUAL(createSafeByteArray("Xfoo"), physicalStream_->data_[0]);
        }

        void testWriteData_TwoIntermediateStreamStack() {
            StreamStack testling(xmppStream_, physicalStream_);
            std::shared_ptr<MyStreamLayer> xStream(new MyStreamLayer("X"));
            std::shared_ptr<MyStreamLayer> yStream(new MyStreamLayer("Y"));
            testling.addLayer(xStream.get());
            testling.addLayer(yStream.get());

            xmppStream_->writeData("foo");

            CPPUNIT_ASSERT_EQUAL(static_cast<size_t>(1), physicalStream_->data_.size());
            CPPUNIT_ASSERT_EQUAL(createSafeByteArray("XYfoo"), physicalStream_->data_[0]);
        }

        void testReadData_NoIntermediateStreamStack() {
            StreamStack testling(xmppStream_, physicalStream_);
            xmppStream_->onElement.connect(boost::bind(&StreamStackTest::handleElement, this, _1));

            physicalStream_->onDataRead(createSafeByteArray("<stream:stream xmlns:stream='http://etherx.jabber.org/streams'><presence/>"));

            CPPUNIT_ASSERT_EQUAL(1, elementsReceived_);
        }

        void testReadData_OneIntermediateStream() {
            StreamStack testling(xmppStream_, physicalStream_);
            xmppStream_->onElement.connect(boost::bind(&StreamStackTest::handleElement, this, _1));
            std::shared_ptr<MyStreamLayer> xStream(new MyStreamLayer("<"));
            testling.addLayer(xStream.get());

            physicalStream_->onDataRead(createSafeByteArray("stream:stream xmlns:stream='http://etherx.jabber.org/streams'><presence/>"));

            CPPUNIT_ASSERT_EQUAL(1, elementsReceived_);
        }

        void testReadData_TwoIntermediateStreamStack() {
            StreamStack testling(xmppStream_, physicalStream_);
            xmppStream_->onElement.connect(boost::bind(&StreamStackTest::handleElement, this, _1));
            std::shared_ptr<MyStreamLayer> xStream(new MyStreamLayer("s"));
            std::shared_ptr<MyStreamLayer> yStream(new MyStreamLayer("<"));
            testling.addLayer(xStream.get());
            testling.addLayer(yStream.get());

            physicalStream_->onDataRead(createSafeByteArray("tream:stream xmlns:stream='http://etherx.jabber.org/streams'><presence/>"));

            CPPUNIT_ASSERT_EQUAL(1, elementsReceived_);
        }

        void testAddLayer_ExistingOnWriteDataSlot() {
            StreamStack testling(xmppStream_, physicalStream_);
            xmppStream_->onWriteData.connect(boost::bind(&StreamStackTest::handleWriteData, this, _1));
            std::shared_ptr<MyStreamLayer> xStream(new MyStreamLayer("X"));
            testling.addLayer(xStream.get());

            xmppStream_->writeData("foo");

            CPPUNIT_ASSERT_EQUAL(1, dataWriteReceived_);
        }

        void handleElement(std::shared_ptr<ToplevelElement>) {
            ++elementsReceived_;
        }

        void handleWriteData(const SafeByteArray&) {
            ++dataWriteReceived_;
        }

    private:
        class MyStreamLayer : public StreamLayer {
            public:
                MyStreamLayer(const std::string& prepend) : prepend_(prepend) {
                }

                virtual void writeData(const SafeByteArray& data) {
                    writeDataToChildLayer(concat(createSafeByteArray(prepend_), data));
                }

                virtual void handleDataRead(const SafeByteArray& data) {
                    writeDataToParentLayer(concat(createSafeByteArray(prepend_), data));
                }

            private:
                std::string prepend_;
        };

        class TestLowLayer : public LowLayer {
            public:
                TestLowLayer() {
                }

                virtual void writeData(const SafeByteArray& data) {
                    data_.push_back(data);
                }

                void onDataRead(const SafeByteArray& data) {
                    writeDataToParentLayer(data);
                }

                std::vector<SafeByteArray> data_;
        };


    private:
        FullPayloadParserFactoryCollection parserFactories_;
        FullPayloadSerializerCollection serializers_;
        TestLowLayer* physicalStream_;
        PlatformXMLParserFactory xmlParserFactory_;
        XMPPLayer* xmppStream_;
        int elementsReceived_;
        int dataWriteReceived_;
};

CPPUNIT_TEST_SUITE_REGISTRATION(StreamStackTest);