/*
 * Copyright (c) 2011 Tobias Markmann
 * Licensed under the simplified BSD license.
 * See Documentation/Licenses/BSD-simplified.txt for more information.
 */

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

#include <boost/smart_ptr/make_shared.hpp>

#include <Swiften/Base/ByteArray.h>
#include <Swiften/Base/Log.h>
#include <Swiften/Client/DummyStanzaChannel.h>
#include <Swiften/Elements/IBB.h>
#include <Swiften/Elements/JingleIBBTransportPayload.h>
#include <Swiften/Elements/JingleS5BTransportPayload.h>
#include <Swiften/FileTransfer/ByteArrayWriteBytestream.h>
#include <Swiften/FileTransfer/IncomingJingleFileTransfer.h>
#include <Swiften/FileTransfer/LocalJingleTransportCandidateGenerator.h>
#include <Swiften/FileTransfer/LocalJingleTransportCandidateGeneratorFactory.h>
#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelector.h>
#include <Swiften/FileTransfer/RemoteJingleTransportCandidateSelectorFactory.h>
#include <Swiften/FileTransfer/SOCKS5BytestreamRegistry.h>
#include <Swiften/FileTransfer/SOCKS5BytestreamProxy.h>
#include <Swiften/Jingle/FakeJingleSession.h>
#include <Swiften/Network/DummyTimerFactory.h>
#include <Swiften/EventLoop/DummyEventLoop.h>
#include <Swiften/Network/DummyConnectionFactory.h>
#include <Swiften/Network/PlatformNATTraversalWorker.h>
#include <Swiften/Queries/IQRouter.h>

#include <iostream>

using namespace Swift;
using namespace boost;

class FakeRemoteJingleTransportCandidateSelector : public RemoteJingleTransportCandidateSelector {
		void addRemoteTransportCandidates(JingleTransportPayload::ref cand) {
			candidate = cand;
		}

		void selectCandidate() {
		    boost::shared_ptr<JingleS5BTransportPayload> payload = make_shared<JingleS5BTransportPayload>();
		    payload->setCandidateError(true);
		    payload->setSessionID(candidate->getSessionID());
		    onRemoteTransportCandidateSelectFinished(payload);
		}

		void setMinimumPriority(int) {

		}

		bool isActualCandidate(JingleTransportPayload::ref) {
			return false;
		}

		int getPriority(JingleTransportPayload::ref) {
			return 0;
		}

		JingleTransport::ref selectTransport(JingleTransportPayload::ref) {
			return JingleTransport::ref();
		}

private:
	JingleTransportPayload::ref candidate;
};

class FakeRemoteJingleTransportCandidateSelectorFactory : public RemoteJingleTransportCandidateSelectorFactory {
public:
	virtual ~FakeRemoteJingleTransportCandidateSelectorFactory() {

	}

	virtual RemoteJingleTransportCandidateSelector* createCandidateSelector() {
		return new FakeRemoteJingleTransportCandidateSelector();
	}
};

class FakeLocalJingleTransportCandidateGenerator : public LocalJingleTransportCandidateGenerator {
public:
	virtual void generateLocalTransportCandidates(JingleTransportPayload::ref payload) {
		JingleS5BTransportPayload::ref payL = make_shared<JingleS5BTransportPayload>();
		payL->setSessionID(payload->getSessionID());
		onLocalTransportCandidatesGenerated(payL);
	}

	void emitonLocalTransportCandidatesGenerated(JingleTransportPayload::ref payload) {
		onLocalTransportCandidatesGenerated(payload);
	}

	virtual bool isActualCandidate(JingleTransportPayload::ref) {
		return false;
	}

	virtual int getPriority(JingleTransportPayload::ref) {
		return 0;
	}

	virtual JingleTransport::ref selectTransport(JingleTransportPayload::ref) {
		return JingleTransport::ref();
	}
};

class FakeLocalJingleTransportCandidateGeneratorFactory : public LocalJingleTransportCandidateGeneratorFactory {
public:
	virtual LocalJingleTransportCandidateGenerator* createCandidateGenerator() {
		return new FakeLocalJingleTransportCandidateGenerator();
	}
};

class IncomingJingleFileTransferTest : public CppUnit::TestFixture {
		CPPUNIT_TEST_SUITE(IncomingJingleFileTransferTest);
		CPPUNIT_TEST(test_AcceptOnyIBBSendsSessionAccept);
		CPPUNIT_TEST(test_OnlyIBBTransferReceiveWorks);
		CPPUNIT_TEST(test_AcceptFailingS5BFallsBackToIBB);
		CPPUNIT_TEST_SUITE_END();
public:
		shared_ptr<IncomingJingleFileTransfer> createTestling() {
			JID ourJID("our@jid.org/full");
			return make_shared<IncomingJingleFileTransfer>(ourJID, shared_ptr<JingleSession>(fakeJingleSession), jingleContentPayload, fakeRJTCSF.get(), fakeLJTCF.get(), iqRouter, bytestreamRegistry, bytestreamProxy, timerFactory);
		}

		IQ::ref createIBBRequest(IBB::ref ibb, const JID& from, const std::string& id) {
			IQ::ref request = IQ::createRequest(IQ::Set, JID("foo@bar.com/baz"), id, ibb);
			request->setFrom(from);
			return request;
		}

		void setUp() {
			eventLoop = new DummyEventLoop();
			fakeJingleSession = new FakeJingleSession("foo@bar.com/baz", "mysession");
			jingleContentPayload = make_shared<JingleContentPayload>();
			fakeRJTCSF = make_shared<FakeRemoteJingleTransportCandidateSelectorFactory>();
			fakeLJTCF = make_shared<FakeLocalJingleTransportCandidateGeneratorFactory>();
			stanzaChannel = new DummyStanzaChannel();
			iqRouter = new IQRouter(stanzaChannel);
			bytestreamRegistry = new SOCKS5BytestreamRegistry();
			timerFactory = new DummyTimerFactory();
			connectionFactory = new DummyConnectionFactory(eventLoop);
			bytestreamProxy = new SOCKS5BytestreamProxy(connectionFactory, timerFactory);
		}

		void tearDown() {
			delete bytestreamProxy;
			delete connectionFactory;
			delete timerFactory;
			delete bytestreamRegistry;
			delete iqRouter;
			delete stanzaChannel;
			delete eventLoop;
		}

		// Tests whether IncomingJingleFileTransfer would accept a IBB only file transfer.
		void test_AcceptOnyIBBSendsSessionAccept() {
			//1. create your test incoming file transfer
			shared_ptr<JingleFileTransferDescription> desc = make_shared<JingleFileTransferDescription>();
			desc->addOffer(StreamInitiationFileInfo("foo.txt", "", 10));
			jingleContentPayload->addDescription(desc);
			JingleIBBTransportPayload::ref tpRef = make_shared<JingleIBBTransportPayload>();
			tpRef->setSessionID("mysession");
			jingleContentPayload->addTransport(tpRef);

			shared_ptr<IncomingJingleFileTransfer> fileTransfer = createTestling();

			//2. do 'accept' on a dummy writebytestream (you'll have to look if there already is one)
			shared_ptr<ByteArrayWriteBytestream> byteStream = make_shared<ByteArrayWriteBytestream>();
			fileTransfer->accept(byteStream);

			// check whether accept has been called
			getCall<FakeJingleSession::AcceptCall>(0);
		}

		void test_OnlyIBBTransferReceiveWorks() {
			//1. create your test incoming file transfer
			shared_ptr<JingleFileTransferDescription> desc = make_shared<JingleFileTransferDescription>();
			desc->addOffer(StreamInitiationFileInfo("file.txt", "", 10));
			jingleContentPayload->addDescription(desc);
			JingleIBBTransportPayload::ref tpRef = make_shared<JingleIBBTransportPayload>();
			tpRef->setSessionID("mysession");
			jingleContentPayload->addTransport(tpRef);

			shared_ptr<IncomingJingleFileTransfer> fileTransfer = createTestling();

			//2. do 'accept' on a dummy writebytestream (you'll have to look if there already is one)
			shared_ptr<ByteArrayWriteBytestream> byteStream = make_shared<ByteArrayWriteBytestream>();
			fileTransfer->accept(byteStream);

			// check whether accept has been called
			getCall<FakeJingleSession::AcceptCall>(0);
			stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBOpen("mysession", 0x10), "foo@bar.com/baz", "id-open"));
			stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBData("mysession", 0, createByteArray("abc")), "foo@bar.com/baz", "id-a"));
			CPPUNIT_ASSERT(createByteArray("abc") == byteStream->getData());
		}

		void test_AcceptFailingS5BFallsBackToIBB() {
			//1. create your test incoming file transfer
			addFileTransferDescription();

			// add SOCKS5BytestreamTransportPayload
			JingleS5BTransportPayload::ref payLoad = addJingleS5BPayload();

			shared_ptr<IncomingJingleFileTransfer> fileTransfer = createTestling();

			//2. do 'accept' on a dummy writebytestream (you'll have to look if there already is one)
			shared_ptr<ByteArrayWriteBytestream> byteStream = make_shared<ByteArrayWriteBytestream>();
			fileTransfer->accept(byteStream);

			// check whether accept has been called
			FakeJingleSession::AcceptCall acceptCall = getCall<FakeJingleSession::AcceptCall>(0);
			CPPUNIT_ASSERT_EQUAL(payLoad->getSessionID(), acceptCall.payload->getSessionID());

			// check for candidate error
			FakeJingleSession::InfoTransportCall infoTransportCall = getCall<FakeJingleSession::InfoTransportCall>(1);
			JingleS5BTransportPayload::ref s5bPayload = dynamic_pointer_cast<JingleS5BTransportPayload>(infoTransportCall.payload);
			CPPUNIT_ASSERT(s5bPayload->hasCandidateError());

			// indicate transport replace (Romeo)
			fakeJingleSession->onTransportReplaceReceived(getContentID(), addJingleIBBPayload());

			FakeJingleSession::AcceptTransportCall acceptTransportCall = getCall<FakeJingleSession::AcceptTransportCall>(2);

			// send a bit of data
			stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBOpen("mysession", 0x10), "foo@bar.com/baz", "id-open"));
			stanzaChannel->onIQReceived(createIBBRequest(IBB::createIBBData("mysession", 0, createByteArray("abc")), "foo@bar.com/baz", "id-a"));
			CPPUNIT_ASSERT(createByteArray("abc") == byteStream->getData());
		}

		void test_S5BTransferReceiveTest() {
			addFileTransferDescription();
			JingleS5BTransportPayload::ref payLoad = addJingleS5BPayload();
		}

private:
	void addFileTransferDescription() {
		shared_ptr<JingleFileTransferDescription> desc = make_shared<JingleFileTransferDescription>();
		desc->addOffer(StreamInitiationFileInfo("file.txt", "", 10));
		jingleContentPayload->addDescription(desc);
	}

	shared_ptr<JingleS5BTransportPayload> addJingleS5BPayload() {
		JingleS5BTransportPayload::ref payLoad = make_shared<JingleS5BTransportPayload>();
		payLoad->setSessionID("mysession");
		jingleContentPayload->addTransport(payLoad);
		return payLoad;
	}

	shared_ptr<JingleIBBTransportPayload> addJingleIBBPayload() {
		JingleIBBTransportPayload::ref payLoad = make_shared<JingleIBBTransportPayload>();
		payLoad->setSessionID("mysession");
		jingleContentPayload->addTransport(payLoad);
		return payLoad;
	}
	
	JingleContentID getContentID() const  {
		return JingleContentID(jingleContentPayload->getName(), jingleContentPayload->getCreator());
	}

	template <typename T> T getCall(int i) const {
		size_t index = static_cast<size_t>(i);
		CPPUNIT_ASSERT(index < fakeJingleSession->calledCommands.size());
		T* cmd = boost::get<T>(&fakeJingleSession->calledCommands[index]);
		CPPUNIT_ASSERT(cmd);
		return *cmd;
	}

private:
	EventLoop* eventLoop;
	FakeJingleSession* fakeJingleSession;
	shared_ptr<JingleContentPayload> jingleContentPayload;
	shared_ptr<FakeRemoteJingleTransportCandidateSelectorFactory> fakeRJTCSF;
	shared_ptr<FakeLocalJingleTransportCandidateGeneratorFactory> fakeLJTCF;
	DummyStanzaChannel* stanzaChannel;
	IQRouter* iqRouter;
	SOCKS5BytestreamRegistry* bytestreamRegistry;
	DummyConnectionFactory* connectionFactory;
	SOCKS5BytestreamProxy* bytestreamProxy;
	DummyTimerFactory* timerFactory;
};

CPPUNIT_TEST_SUITE_REGISTRATION(IncomingJingleFileTransferTest);