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

#include <Swiften/Base/ByteArray.h>
#include <QA/Checker/IO.h>

#include <vector>
#include <boost/bind.hpp>
#include <boost/smart_ptr.hpp>
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/extensions/TestFactoryRegistry.h>

#include <Swiften/Base/ByteArray.h>
#include <Swiften/Base/Concat.h>
#include <Swiften/StreamStack/StreamStack.h>
#include <Swiften/StreamStack/LowLayer.h>
#include <Swiften/StreamStack/XMPPLayer.h>
#include <Swiften/StreamStack/StreamLayer.h>
#include <Swiften/Parser/PlatformXMLParserFactory.h>
#include <Swiften/Parser/PayloadParsers/FullPayloadParserFactoryCollection.h>
#include <Swiften/Serializer/PayloadSerializers/FullPayloadSerializerCollection.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_);
			boost::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_);
			boost::shared_ptr<MyStreamLayer> xStream(new MyStreamLayer("X"));
			boost::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));
			boost::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));
			boost::shared_ptr<MyStreamLayer> xStream(new MyStreamLayer("s"));
			boost::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));
			boost::shared_ptr<MyStreamLayer> xStream(new MyStreamLayer("X"));
			testling.addLayer(xStream.get());

			xmppStream_->writeData("foo");

			CPPUNIT_ASSERT_EQUAL(1, dataWriteReceived_);
		}

		void handleElement(boost::shared_ptr<Element>) {
			++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);