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

// TODO: Test registering on different interfaces
#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/extensions/TestFactoryRegistry.h>
#include <boost/bind.hpp>

#include <algorithm>

#include "Swiften/Base/sleep.h"
#include "Swiften/Base/ByteArray.h"
#include "Swiften/EventLoop/DummyEventLoop.h"
#include "Swiften/LinkLocal/LinkLocalServiceInfo.h"
#include "Swiften/LinkLocal/DNSSD/DNSSDQuerier.h"
#include "Swiften/LinkLocal/DNSSD/DNSSDBrowseQuery.h"
#include "Swiften/LinkLocal/DNSSD/DNSSDRegisterQuery.h"
#include "Swiften/LinkLocal/DNSSD/DNSSDResolveServiceQuery.h"
#ifdef HAVE_AVAHI
#include "Swiften/LinkLocal/DNSSD/Avahi/AvahiQuerier.h"
#endif

#define SLEEP_INTERVALS 20

using namespace Swift;

template <typename DNSSDQuerierType>
class DNSSDTest : public CppUnit::TestFixture {
		CPPUNIT_TEST_SUITE(DNSSDTest);
		CPPUNIT_TEST(testPublish);
		CPPUNIT_TEST_SUITE_END();

	public:
		void setUp() {
			eventLoop = new DummyEventLoop();
			querier = boost::shared_ptr<DNSSDQuerier>(new DNSSDQuerierType());
			querier->start();
		}

		void tearDown() {
			querier->stop();
			querier.reset();
			delete eventLoop;
		}

		void testPublish() {
			boost::shared_ptr<DNSSDBrowseQuery> browseQuery = querier->createBrowseQuery();
			browseQuery->onServiceAdded.connect(boost::bind(&DNSSDTest::handleServiceAdded, this, _1));
			browseQuery->onServiceRemoved.connect(boost::bind(&DNSSDTest::handleServiceRemoved, this, _1));
			browseQuery->onError.connect(boost::bind(&DNSSDTest::handleBrowseError, this));
			browseQuery->startBrowsing();
			eventLoop->processEvents();

			// Publish the service
			LinkLocalServiceInfo info;
			boost::shared_ptr<DNSSDRegisterQuery> registerQuery = querier->createRegisterQuery("DNSSDTest", 1234, info.toTXTRecord());
			registerQuery->onRegisterFinished.connect(boost::bind(&DNSSDTest::handleRegisterFinished, this, _1));
			registerQuery->registerService();

			// Wait for a while
			wait();

			// Check that our registered queries are correct
			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>((registered.size())));
			CPPUNIT_ASSERT_EQUAL(std::string("DNSSDTest"), registered[0].getName());
			CPPUNIT_ASSERT_EQUAL(std::string("local"), registered[0].getDomain());
			CPPUNIT_ASSERT_EQUAL(std::string("_presence._tcp"), registered[0].getType());

			// Check that our browse query discovered us
			std::sort(added.begin(), added.end());
			CPPUNIT_ASSERT(added.size() >= 1);
			//for (size_t i = 0; i < added.size(); ++i) {
			for (size_t i = 0; i < added.size(); ++i) {
				CPPUNIT_ASSERT_EQUAL(std::string("DNSSDTest"), added[i].getName());
				CPPUNIT_ASSERT_EQUAL(std::string("local"), added[i].getDomain());
				CPPUNIT_ASSERT_EQUAL(std::string("_presence._tcp"), added[i].getType());
				CPPUNIT_ASSERT(added[i].getNetworkInterfaceID() != 0);
			}

			// Resolve all added services
			for (size_t i = 0; i < added.size(); ++i) {
				resolvedServices.clear();
				boost::shared_ptr<DNSSDResolveServiceQuery> resolveServiceQuery = querier->createResolveServiceQuery(added[i]);
				resolveServiceQuery->onServiceResolved.connect(boost::bind(&DNSSDTest::handleResolveFinished, this, _1));
				resolveServiceQuery->start();
				wait();
				CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(resolvedServices.size()));
				resolveServiceQuery->stop();
			}

			// Unregister the service & check if the browse query picks this up
			toRemove.clear();
			toRemove.insert(toRemove.begin(), added.begin(), added.end());
			registerQuery->unregisterService();
			while (toRemove.size() > 0) {
				Swift::sleep(100);
				eventLoop->processEvents();
			}

			browseQuery->stopBrowsing();
			eventLoop->processEvents();
		}
	
	private:
		void handleServiceAdded(const DNSSDServiceID& id) {
			std::cout << "Service added: " << id.getNetworkInterfaceID() << std::endl;
			added.push_back(id);
		}

		void handleServiceRemoved(const DNSSDServiceID& id) {
			CPPUNIT_ASSERT(std::find(toRemove.begin(), toRemove.end(), id) != toRemove.end());
			toRemove.erase(std::remove(toRemove.begin(), toRemove.end(), id));
		}

		void handleRegisterFinished(boost::optional<DNSSDServiceID> id) {
			if (id) {
				registered.push_back(*id);
			}
		}

		void handleBrowseError() {
		}
	
		void wait() {
			for (int i = 0; i < SLEEP_INTERVALS; ++i) {
				Swift::sleep(100);
				eventLoop->processEvents();
			}
		}

		void handleResolveFinished(const boost::optional<DNSSDResolveServiceQuery::Result>& result) {
			CPPUNIT_ASSERT(result);
			resolvedServices.push_back(*result);
		}

	private:
		DummyEventLoop* eventLoop;
		boost::shared_ptr<DNSSDQuerier> querier;
		std::vector<DNSSDServiceID> added;
		std::vector<DNSSDServiceID> registered;
		std::vector<DNSSDServiceID> toRemove;
		std::vector<DNSSDResolveServiceQuery::Result> resolvedServices;
};

#ifdef HAVE_AVAHI
CPPUNIT_TEST_SUITE_REGISTRATION(DNSSDTest<AvahiQuerier>);
#endif