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

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

#include <Swiften/Base/foreach.h>
#include <Swiften/Chat/ChatStateNotifier.h>
#include <Swiften/Client/DummyStanzaChannel.h>
#include <Swiften/Disco/DummyEntityCapsProvider.h>

using namespace Swift;

class ChatStateNotifierTest : public CppUnit::TestFixture {
	CPPUNIT_TEST_SUITE(ChatStateNotifierTest);
	CPPUNIT_TEST(testStartTypingReply_CapsNotIncluded);
	CPPUNIT_TEST(testStartTypingReply_CapsIncluded);
	CPPUNIT_TEST(testCancelledNewMessage);
	CPPUNIT_TEST(testContinueTypingReply_CapsIncluded);
	CPPUNIT_TEST(testTypeReplies_WentOffline);
	CPPUNIT_TEST(testContactShouldReceiveStates_CapsOnly);
	CPPUNIT_TEST(testContactShouldReceiveStates_CapsNorActive);
	CPPUNIT_TEST(testContactShouldReceiveStates_ActiveOverrideOn);	
	CPPUNIT_TEST(testContactShouldReceiveStates_ActiveOverrideOff);
	CPPUNIT_TEST_SUITE_END();
	
public:
	void setUp() {
		stanzaChannel = new DummyStanzaChannel();
		stanzaChannel->setAvailable(true);
		entityCapsProvider = new DummyEntityCapsProvider();
		notifier_ = new ChatStateNotifier(stanzaChannel, JID("foo@bar.com/baz"), entityCapsProvider);
		notifier_->setContactIsOnline(true);
	}
	
	void tearDown() {
		delete notifier_;
		delete entityCapsProvider;
		delete stanzaChannel;
	}
	
	void testStartTypingReply_CapsNotIncluded() {
		notifier_->setUserIsTyping();
		CPPUNIT_ASSERT_EQUAL(0, getComposingCount());
	}

	void testSendTwoMessages() {
		setContactHas85Caps();
		notifier_->setUserIsTyping();
		notifier_->userSentMessage();
		notifier_->setUserIsTyping();
		notifier_->userSentMessage();
		CPPUNIT_ASSERT_EQUAL(2, getComposingCount());
	}

	void testCancelledNewMessage() {
		setContactHas85Caps();
		notifier_->setUserIsTyping();
		notifier_->userCancelledNewMessage();
		CPPUNIT_ASSERT_EQUAL(1, getComposingCount());
		CPPUNIT_ASSERT_EQUAL(1, getActiveCount());
		CPPUNIT_ASSERT_EQUAL(ChatState::Active, stanzaChannel->sentStanzas[stanzaChannel->sentStanzas.size()-1]->getPayload<ChatState>()->getChatState());
	}


	void testContactShouldReceiveStates_CapsOnly() {
		setContactHas85Caps();
		boost::shared_ptr<Message> message(new Message());
		notifier_->addChatStateRequest(message);
		CPPUNIT_ASSERT(message->getPayload<ChatState>());
		CPPUNIT_ASSERT_EQUAL(ChatState::Active, message->getPayload<ChatState>()->getChatState());
	}

	void testContactShouldReceiveStates_CapsNorActive() {
		boost::shared_ptr<Message> message(new Message());
		notifier_->addChatStateRequest(message);
		CPPUNIT_ASSERT(!message->getPayload<ChatState>());
	}

	void testContactShouldReceiveStates_ActiveOverrideOn() {
		notifier_->receivedMessageFromContact(true);
		boost::shared_ptr<Message> message(new Message());
		notifier_->addChatStateRequest(message);
		CPPUNIT_ASSERT(message->getPayload<ChatState>());
		CPPUNIT_ASSERT_EQUAL(ChatState::Active, message->getPayload<ChatState>()->getChatState());
	}

	void testContactShouldReceiveStates_ActiveOverrideOff() {
		setContactHas85Caps();
		notifier_->receivedMessageFromContact(false);
		/* I originally read the MUST NOT send after receiving without Active and
		 * thought this should check for false, but I later found it was OPTIONAL
		 * (MAY) behaviour only for if you didn't receive caps.
		 */
		boost::shared_ptr<Message> message(new Message());
		notifier_->addChatStateRequest(message);
		CPPUNIT_ASSERT(message->getPayload<ChatState>());
		CPPUNIT_ASSERT_EQUAL(ChatState::Active, message->getPayload<ChatState>()->getChatState());
	}


	void testStartTypingReply_CapsIncluded() {
		setContactHas85Caps();
		notifier_->setUserIsTyping();
		CPPUNIT_ASSERT_EQUAL(1, getComposingCount());
	}

	void testContinueTypingReply_CapsIncluded() {
		setContactHas85Caps();
		notifier_->setUserIsTyping();
		notifier_->setUserIsTyping();
		notifier_->setUserIsTyping();
		CPPUNIT_ASSERT_EQUAL(1, getComposingCount());
		notifier_->userSentMessage();
		notifier_->setUserIsTyping();
		CPPUNIT_ASSERT_EQUAL(2, getComposingCount());

	}

	void testTypeReplies_WentOffline() {
			setContactHas85Caps();
			notifier_->setUserIsTyping();
			CPPUNIT_ASSERT_EQUAL(1, getComposingCount());
			notifier_->setContactIsOnline(false);
			notifier_->userSentMessage();
			notifier_->setUserIsTyping();
			CPPUNIT_ASSERT_EQUAL(1, getComposingCount());
		}

	private:
		void setContactHas85Caps() {
			DiscoInfo::ref caps(new DiscoInfo());
			caps->addFeature(DiscoInfo::ChatStatesFeature);
			entityCapsProvider->caps[JID("foo@bar.com/baz")] = caps;
			entityCapsProvider->onCapsChanged(JID("foo@bar.com/baz"));
		}

		int getComposingCount() const {
			int result = 0;
			foreach(boost::shared_ptr<Stanza> stanza, stanzaChannel->sentStanzas) {
				if (stanza->getPayload<ChatState>() && stanza->getPayload<ChatState>()->getChatState() == ChatState::Composing) {
					result++;
				}
			}
			return result;
		}

		int getActiveCount() const {
			int result = 0;
			foreach(boost::shared_ptr<Stanza> stanza, stanzaChannel->sentStanzas) {
				if (stanza->getPayload<ChatState>() && stanza->getPayload<ChatState>()->getChatState() == ChatState::Active) {
					result++;
				}
			}
			return result;
		}

	private:
		DummyStanzaChannel* stanzaChannel;
		DummyEntityCapsProvider* entityCapsProvider;
		ChatStateNotifier* notifier_;
};

CPPUNIT_TEST_SUITE_REGISTRATION(ChatStateNotifierTest);