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

#include "Swiften/Elements/Presence.h"
#include "Swiften/Elements/RosterPayload.h"
#include "Swiften/Elements/RosterItemPayload.h"
#include "Slimber/LinkLocalPresenceManager.h"
#include "Swiften/LinkLocal/LinkLocalServiceInfo.h"
#include "Swiften/LinkLocal/LinkLocalServiceBrowser.h"
#include "Swiften/LinkLocal/DNSSD/DNSSDServiceID.h"
#include "Swiften/LinkLocal/DNSSD/DNSSDResolveServiceQuery.h"
#include "Swiften/LinkLocal/DNSSD/Fake/FakeDNSSDQuerier.h"
#include "Swiften/EventLoop/DummyEventLoop.h"

using namespace Swift;

class LinkLocalPresenceManagerTest : public CppUnit::TestFixture {
		CPPUNIT_TEST_SUITE(LinkLocalPresenceManagerTest);
		CPPUNIT_TEST(testConstructor);
		CPPUNIT_TEST(testServiceAdded);
		CPPUNIT_TEST(testServiceRemoved);
		CPPUNIT_TEST(testServiceChanged);
		CPPUNIT_TEST(testGetRoster);
		CPPUNIT_TEST(testGetAllPresence);
		CPPUNIT_TEST(testGetRoster_InfoWithNick);
		CPPUNIT_TEST(testGetRoster_InfoWithFirstName);
		CPPUNIT_TEST(testGetRoster_InfoWithLastName);
		CPPUNIT_TEST(testGetRoster_InfoWithFirstAndLastName);
		CPPUNIT_TEST(testGetRoster_NoInfo);
		CPPUNIT_TEST(testGetServiceForJID);
		CPPUNIT_TEST(testGetServiceForJID_NoMatch);
		CPPUNIT_TEST_SUITE_END();

	public:
		void setUp() {
			eventLoop = new DummyEventLoop();
			querier = boost::shared_ptr<FakeDNSSDQuerier>(new FakeDNSSDQuerier("wonderland.lit"));
			browser = new LinkLocalServiceBrowser(querier);
			browser->start();
		}

		void tearDown() {
			browser->stop();
			delete browser;
			delete eventLoop;
		}

		void testConstructor() {
			addService("alice@wonderland");
			addService("rabbit@teaparty");
			std::auto_ptr<LinkLocalPresenceManager> testling(createTestling());

			CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(testling->getRoster()->getItems().size()));
			CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(testling->getAllPresence().size()));
		}

		void testServiceAdded() {
			std::auto_ptr<LinkLocalPresenceManager> testling(createTestling());

			addService("alice@wonderland", "Alice");

			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(rosterChanges.size()));
			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(rosterChanges[0]->getItems().size()));
			boost::optional<RosterItemPayload> item = rosterChanges[0]->getItem(JID("alice@wonderland"));
			CPPUNIT_ASSERT(item);
			CPPUNIT_ASSERT_EQUAL(String("Alice"), item->getName());
			CPPUNIT_ASSERT_EQUAL(RosterItemPayload::Both, item->getSubscription());
			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(presenceChanges.size()));
			CPPUNIT_ASSERT(StatusShow::Online == presenceChanges[0]->getShow());
			CPPUNIT_ASSERT(JID("alice@wonderland") == presenceChanges[0]->getFrom());
		}

		void testServiceRemoved() {
			std::auto_ptr<LinkLocalPresenceManager> testling(createTestling());

			addService("alice@wonderland");
			removeService("alice@wonderland");

			CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(rosterChanges.size()));
			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(rosterChanges[1]->getItems().size()));
			boost::optional<RosterItemPayload> item = rosterChanges[1]->getItem(JID("alice@wonderland"));
			CPPUNIT_ASSERT(item);
			CPPUNIT_ASSERT_EQUAL(RosterItemPayload::Remove, item->getSubscription());
		}

		void testServiceChanged() {
			std::auto_ptr<LinkLocalPresenceManager> testling(createTestling());

			addService("alice@wonderland");
			updateServicePresence("alice@wonderland", LinkLocalServiceInfo::Away, "I'm Away");

			CPPUNIT_ASSERT_EQUAL(1, static_cast<int>(rosterChanges.size()));
			CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(presenceChanges.size()));
			CPPUNIT_ASSERT(StatusShow::Away == presenceChanges[1]->getShow());
			CPPUNIT_ASSERT(JID("alice@wonderland") == presenceChanges[1]->getFrom());
			CPPUNIT_ASSERT_EQUAL(String("I'm Away"), presenceChanges[1]->getStatus());
		}

		void testGetAllPresence() {
			std::auto_ptr<LinkLocalPresenceManager> testling(createTestling());

			addService("alice@wonderland");
			addService("rabbit@teaparty");
			updateServicePresence("rabbit@teaparty", LinkLocalServiceInfo::Away, "Partying");
			
			std::vector<boost::shared_ptr<Presence> > presences = testling->getAllPresence();
			CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(presences.size()));
			// The order doesn't matter
			CPPUNIT_ASSERT(JID("rabbit@teaparty") == presences[0]->getFrom());
			CPPUNIT_ASSERT(StatusShow::Away == presences[0]->getShow());
			CPPUNIT_ASSERT(JID("alice@wonderland") == presences[1]->getFrom());
			CPPUNIT_ASSERT(StatusShow::Online == presences[1]->getShow());
		}

		void testGetRoster() {
			std::auto_ptr<LinkLocalPresenceManager> testling(createTestling());

			addService("alice@wonderland", "Alice");
			addService("rabbit@teaparty", "Rabbit");

			boost::shared_ptr<RosterPayload> roster = testling->getRoster();
			CPPUNIT_ASSERT_EQUAL(2, static_cast<int>(roster->getItems().size()));
			boost::optional<RosterItemPayload> item;
			item = roster->getItem(JID("alice@wonderland"));
			CPPUNIT_ASSERT(item);
			CPPUNIT_ASSERT_EQUAL(String("Alice"), item->getName());
			CPPUNIT_ASSERT_EQUAL(RosterItemPayload::Both, item->getSubscription());
			item = roster->getItem(JID("rabbit@teaparty"));
			CPPUNIT_ASSERT(item);
			CPPUNIT_ASSERT_EQUAL(String("Rabbit"), item->getName());
			CPPUNIT_ASSERT_EQUAL(RosterItemPayload::Both, item->getSubscription());
		}

		void testGetRoster_InfoWithNick() {
			std::auto_ptr<LinkLocalPresenceManager> testling(createTestling());

			addService("alice@wonderland", "Alice", "Alice In", "Wonderland");

			boost::optional<RosterItemPayload> item = testling->getRoster()->getItem(JID("alice@wonderland"));
			CPPUNIT_ASSERT_EQUAL(String("Alice"), item->getName());
		}

		void testGetRoster_InfoWithFirstName() {
			std::auto_ptr<LinkLocalPresenceManager> testling(createTestling());

			addService("alice@wonderland", "", "Alice In", "");

			boost::optional<RosterItemPayload> item = testling->getRoster()->getItem(JID("alice@wonderland"));
			CPPUNIT_ASSERT_EQUAL(String("Alice In"), item->getName());
		}

		void testGetRoster_InfoWithLastName() {
			std::auto_ptr<LinkLocalPresenceManager> testling(createTestling());

			addService("alice@wonderland", "", "", "Wonderland");

			boost::optional<RosterItemPayload> item = testling->getRoster()->getItem(JID("alice@wonderland"));
			CPPUNIT_ASSERT_EQUAL(String("Wonderland"), item->getName());
		}

		void testGetRoster_InfoWithFirstAndLastName() {
			std::auto_ptr<LinkLocalPresenceManager> testling(createTestling());

			addService("alice@wonderland", "", "Alice In", "Wonderland");

			boost::optional<RosterItemPayload> item = testling->getRoster()->getItem(JID("alice@wonderland"));
			CPPUNIT_ASSERT_EQUAL(String("Alice In Wonderland"), item->getName());
		}

		void testGetRoster_NoInfo() {
			std::auto_ptr<LinkLocalPresenceManager> testling(createTestling());

			addService("alice@wonderland");

			boost::optional<RosterItemPayload> item = testling->getRoster()->getItem(JID("alice@wonderland"));
			CPPUNIT_ASSERT_EQUAL(String(""), item->getName());
		}

		void testGetServiceForJID() {
			std::auto_ptr<LinkLocalPresenceManager> testling(createTestling());

			addService("alice@wonderland");
			addService("rabbit@teaparty");
			addService("queen@garden");

			boost::optional<LinkLocalService> service = testling->getServiceForJID(JID("rabbit@teaparty"));
			CPPUNIT_ASSERT(service);
			CPPUNIT_ASSERT_EQUAL(String("rabbit@teaparty"), service->getID().getName());
		}

		void testGetServiceForJID_NoMatch() {
			std::auto_ptr<LinkLocalPresenceManager> testling(createTestling());

			addService("alice@wonderland");
			addService("queen@garden");

			CPPUNIT_ASSERT(!testling->getServiceForJID(JID("rabbit@teaparty")));
		}

	private:
		std::auto_ptr<LinkLocalPresenceManager> createTestling() {
			std::auto_ptr<LinkLocalPresenceManager> testling(
					new LinkLocalPresenceManager(browser));
			testling->onRosterChanged.connect(boost::bind(
					&LinkLocalPresenceManagerTest::handleRosterChanged, this, _1));
			testling->onPresenceChanged.connect(boost::bind(
					&LinkLocalPresenceManagerTest::handlePresenceChanged, this, _1));
			return testling;
		}

		void addService(const String& name, const String& nickName = String(), const String& firstName = String(), const String& lastName = String()) {
			DNSSDServiceID service(name, "local.");
			LinkLocalServiceInfo info;
			info.setFirstName(firstName);
			info.setLastName(lastName);
			info.setNick(nickName);
			querier->setServiceInfo(service, DNSSDResolveServiceQuery::Result(name + "._presence._tcp.local", "rabbithole.local", 1234, info.toTXTRecord()));
			querier->addService(service);
			eventLoop->processEvents();
		}

		void removeService(const String& name) {
			DNSSDServiceID service(name, "local.");
			querier->removeService(DNSSDServiceID(name, "local."));
			eventLoop->processEvents();
		}

		void updateServicePresence(const String& name, LinkLocalServiceInfo::Status status, const String& message) {
			DNSSDServiceID service(name, "local.");
			LinkLocalServiceInfo info;
			info.setStatus(status);
			info.setMessage(message);
			querier->setServiceInfo(service, DNSSDResolveServiceQuery::Result(name + "._presence._tcp.local", "rabbithole.local", 1234, info.toTXTRecord()));
			eventLoop->processEvents();
		}

		void handleRosterChanged(boost::shared_ptr<RosterPayload> payload) {
			rosterChanges.push_back(payload);
		}

		void handlePresenceChanged(boost::shared_ptr<Presence> presence) {
			presenceChanges.push_back(presence);
		}

	private:
		DummyEventLoop* eventLoop;
		boost::shared_ptr<FakeDNSSDQuerier> querier;
		LinkLocalServiceBrowser* browser;
		std::vector< boost::shared_ptr<RosterPayload> > rosterChanges;
		std::vector< boost::shared_ptr<Presence> > presenceChanges;
};

CPPUNIT_TEST_SUITE_REGISTRATION(LinkLocalPresenceManagerTest);