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

#include <boost/bind.hpp>

#include <cppunit/extensions/HelperMacros.h>
#include <cppunit/extensions/TestFactoryRegistry.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();
        std::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() {
        std::shared_ptr<Message> message(new Message());
        notifier_->addChatStateRequest(message);
        CPPUNIT_ASSERT(!message->getPayload<ChatState>());
    }

    void testContactShouldReceiveStates_ActiveOverrideOn() {
        notifier_->receivedMessageFromContact(true);
        std::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.
         */
        std::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;
            for (auto&& stanza : stanzaChannel->sentStanzas) {
                if (stanza->getPayload<ChatState>() && stanza->getPayload<ChatState>()->getChatState() == ChatState::Composing) {
                    result++;
                }
            }
            return result;
        }

        int getActiveCount() const {
            int result = 0;
            for (auto&& 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);