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

#include <boost/algorithm/string.hpp>

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

#include <Swiften/Avatars/NullAvatarManager.h>
#include <Swiften/Base/foreach.h>
#include <Swiften/Client/ClientBlockListManager.h>
#include <Swiften/Client/DummyStanzaChannel.h>
#include <Swiften/Client/NickResolver.h>
#include <Swiften/Crypto/CryptoProvider.h>
#include <Swiften/Crypto/PlatformCryptoProvider.h>
#include <Swiften/Disco/DummyEntityCapsProvider.h>
#include <Swiften/Elements/MUCUserPayload.h>
#include <Swiften/Elements/Thread.h>
#include <Swiften/MUC/MUCBookmarkManager.h>
#include <Swiften/MUC/UnitTest/MockMUC.h>
#include <Swiften/Network/TimerFactory.h>
#include <Swiften/Presence/DirectedPresenceSender.h>
#include <Swiften/Presence/PresenceOracle.h>
#include <Swiften/Presence/StanzaChannelPresenceSender.h>
#include <Swiften/Queries/DummyIQChannel.h>
#include <Swiften/Roster/XMPPRoster.h>
#include <Swiften/Roster/XMPPRosterImpl.h>
#include <Swiften/VCards/VCardManager.h>
#include <Swiften/VCards/VCardMemoryStorage.h>

#include <Swift/Controllers/Chat/ChatMessageParser.h>
#include <Swift/Controllers/Chat/MUCController.h>
#include <Swift/Controllers/Chat/UserSearchController.h>
#include <Swift/Controllers/Roster/GroupRosterItem.h>
#include <Swift/Controllers/Roster/Roster.h>
#include <Swift/Controllers/Settings/DummySettingsProvider.h>
#include <Swift/Controllers/UIEvents/UIEventStream.h>
#include <Swift/Controllers/UIInterfaces/ChatWindow.h>
#include <Swift/Controllers/UIInterfaces/ChatWindowFactory.h>
#include <Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h>
#include <Swift/Controllers/UnitTest/MockChatWindow.h>
#include <Swift/Controllers/XMPPEvents/EventController.h>

using namespace Swift;

class MUCControllerTest : public CppUnit::TestFixture {
    CPPUNIT_TEST_SUITE(MUCControllerTest);
    CPPUNIT_TEST(testJoinPartStringContructionSimple);
    CPPUNIT_TEST(testJoinPartStringContructionMixed);
    CPPUNIT_TEST(testAppendToJoinParts);
    CPPUNIT_TEST(testAddressedToSelf);
    CPPUNIT_TEST(testNotAddressedToSelf);
    CPPUNIT_TEST(testAddressedToSelfBySelf);
    CPPUNIT_TEST(testMessageWithEmptyLabelItem);
    CPPUNIT_TEST(testMessageWithLabelItem);
    CPPUNIT_TEST(testCorrectMessageWithLabelItem);
    CPPUNIT_TEST(testRoleAffiliationStates);
    CPPUNIT_TEST(testSubjectChangeCorrect);
    CPPUNIT_TEST(testSubjectChangeIncorrectA);
    CPPUNIT_TEST(testSubjectChangeIncorrectB);
    CPPUNIT_TEST(testSubjectChangeIncorrectC);
    CPPUNIT_TEST_SUITE_END();

public:
    void setUp() {
        crypto_ = std::shared_ptr<CryptoProvider>(PlatformCryptoProvider::create());
        self_ = JID("girl@wonderland.lit/rabbithole");
        nick_ = "aLiCe";
        mucJID_ = JID("teaparty@rooms.wonderland.lit");
        mocks_ = new MockRepository();
        stanzaChannel_ = new DummyStanzaChannel();
        iqChannel_ = new DummyIQChannel();
        iqRouter_ = new IQRouter(iqChannel_);
        eventController_ = new EventController();
        chatWindowFactory_ = mocks_->InterfaceMock<ChatWindowFactory>();
        userSearchWindowFactory_ = mocks_->InterfaceMock<UserSearchWindowFactory>();
        xmppRoster_ = new XMPPRosterImpl();
        presenceOracle_ = new PresenceOracle(stanzaChannel_, xmppRoster_);
        presenceSender_ = new StanzaChannelPresenceSender(stanzaChannel_);
        directedPresenceSender_ = new DirectedPresenceSender(presenceSender_);
        uiEventStream_ = new UIEventStream();
        avatarManager_ = new NullAvatarManager();
        TimerFactory* timerFactory = nullptr;
        window_ = new MockChatWindow();
        mucRegistry_ = new MUCRegistry();
        entityCapsProvider_ = new DummyEntityCapsProvider();
        settings_ = new DummySettingsProvider();
        highlightManager_ = new HighlightManager(settings_);
        muc_ = std::make_shared<MockMUC>(mucJID_);
        mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_);
        chatMessageParser_ = std::make_shared<ChatMessageParser>(std::map<std::string, std::string>(), highlightManager_->getRules(), true);
        vcardStorage_ = new VCardMemoryStorage(crypto_.get());
        vcardManager_ = new VCardManager(self_, iqRouter_, vcardStorage_);
        clientBlockListManager_ = new ClientBlockListManager(iqRouter_);
        mucBookmarkManager_ = new MUCBookmarkManager(iqRouter_);
        controller_ = new MUCController (self_, muc_, boost::optional<std::string>(), nick_, stanzaChannel_, iqRouter_, chatWindowFactory_, presenceOracle_, avatarManager_, uiEventStream_, false, timerFactory, eventController_, entityCapsProvider_, nullptr, nullptr, mucRegistry_, highlightManager_, clientBlockListManager_, chatMessageParser_, false, nullptr, vcardManager_, mucBookmarkManager_);
    }

    void tearDown() {
        delete controller_;
        delete mucBookmarkManager_;
        delete clientBlockListManager_;
        delete vcardManager_;
        delete vcardStorage_;
        delete highlightManager_;
        delete settings_;
        delete entityCapsProvider_;
        delete eventController_;
        delete presenceOracle_;
        delete xmppRoster_;
        delete mocks_;
        delete uiEventStream_;
        delete stanzaChannel_;
        delete presenceSender_;
        delete directedPresenceSender_;
        delete iqRouter_;
        delete iqChannel_;
        delete mucRegistry_;
        delete avatarManager_;
    }

    void finishJoin() {
        Presence::ref presence(new Presence());
        presence->setFrom(JID(muc_->getJID().toString() + "/" + nick_));
        MUCUserPayload::ref status(new MUCUserPayload());
        MUCUserPayload::StatusCode code;
        code.code = 110;
        status->addStatusCode(code);
        presence->addPayload(status);
        stanzaChannel_->onPresenceReceived(presence);
    }

    void testAddressedToSelf() {
        finishJoin();
        Message::ref message(new Message());

        message = Message::ref(new Message());
        message->setFrom(JID(muc_->getJID().toString() + "/otherperson"));
        message->setBody("basic " + nick_ + " test.");
        message->setType(Message::Groupchat);
        controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
        CPPUNIT_ASSERT_EQUAL((size_t)1, eventController_->getEvents().size());

        message = Message::ref(new Message());
        message->setFrom(JID(muc_->getJID().toString() + "/otherperson"));
        message->setBody(nick_ + ": hi there");
        message->setType(Message::Groupchat);
        controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
        CPPUNIT_ASSERT_EQUAL((size_t)2, eventController_->getEvents().size());

        message->setFrom(JID(muc_->getJID().toString() + "/other"));
        message->setBody("Hi there " + nick_);
        message->setType(Message::Groupchat);
        controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
        CPPUNIT_ASSERT_EQUAL((size_t)3, eventController_->getEvents().size());

        message = Message::ref(new Message());
        message->setFrom(JID(muc_->getJID().toString() + "/other2"));
        message->setBody("Hi " + boost::to_lower_copy(nick_) + ".");
        message->setType(Message::Groupchat);
        controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
        CPPUNIT_ASSERT_EQUAL((size_t)4, eventController_->getEvents().size());

        message = Message::ref(new Message());
        message->setFrom(JID(muc_->getJID().toString() + "/other3"));
        message->setBody("Hi bert.");
        message->setType(Message::Groupchat);
        controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
        CPPUNIT_ASSERT_EQUAL((size_t)4, eventController_->getEvents().size());

        message = Message::ref(new Message());
        message->setFrom(JID(muc_->getJID().toString() + "/other2"));
        message->setBody("Hi " + boost::to_lower_copy(nick_) + "ie.");
        message->setType(Message::Groupchat);
        controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
        CPPUNIT_ASSERT_EQUAL((size_t)4, eventController_->getEvents().size());
    }

    void testNotAddressedToSelf() {
        finishJoin();
        Message::ref message(new Message());
        message->setFrom(JID(muc_->getJID().toString() + "/other3"));
        message->setBody("Hi there Hatter");
        message->setType(Message::Groupchat);
        controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
        CPPUNIT_ASSERT_EQUAL((size_t)0, eventController_->getEvents().size());
    }

    void testAddressedToSelfBySelf() {
        finishJoin();
        Message::ref message(new Message());
        message->setFrom(JID(muc_->getJID().toString() + "/" + nick_));
        message->setBody("Hi there " + nick_);
        message->setType(Message::Groupchat);
        controller_->handleIncomingMessage(MessageEvent::ref(new MessageEvent(message)));
        CPPUNIT_ASSERT_EQUAL((size_t)0, eventController_->getEvents().size());
    }

    void testMessageWithEmptyLabelItem() {
        SecurityLabelsCatalog::Item label;
        label.setSelector("Bob");
        window_->label_ = label;
        std::shared_ptr<DiscoInfo> features = std::make_shared<DiscoInfo>();
        features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature);
        controller_->setAvailableServerFeatures(features);
        IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1];
        SecurityLabelsCatalog::ref labelPayload = std::make_shared<SecurityLabelsCatalog>();
        labelPayload->addItem(label);
        IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload);
        iqChannel_->onIQReceived(result);
        std::string messageBody("agamemnon");
        window_->onSendMessageRequest(messageBody, false);
        std::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1];
        Message::ref message = std::dynamic_pointer_cast<Message>(rawStanza);
        CPPUNIT_ASSERT_EQUAL(iq->getTo(), result->getFrom());
        CPPUNIT_ASSERT(window_->labelsEnabled_);
        CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */
        CPPUNIT_ASSERT(message);
        CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody().get());
        CPPUNIT_ASSERT(!message->getPayload<SecurityLabel>());
    }

    void testMessageWithLabelItem() {
        std::shared_ptr<SecurityLabel> label = std::make_shared<SecurityLabel>();
        label->setLabel("a");
        SecurityLabelsCatalog::Item labelItem;
        labelItem.setSelector("Bob");
        labelItem.setLabel(label);
        window_->label_ = labelItem;
        std::shared_ptr<DiscoInfo> features = std::make_shared<DiscoInfo>();
        features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature);
        controller_->setAvailableServerFeatures(features);
        IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1];
        SecurityLabelsCatalog::ref labelPayload = std::make_shared<SecurityLabelsCatalog>();
        labelPayload->addItem(labelItem);
        IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload);
        iqChannel_->onIQReceived(result);
        std::string messageBody("agamemnon");
        window_->onSendMessageRequest(messageBody, false);
        std::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1];
        Message::ref message = std::dynamic_pointer_cast<Message>(rawStanza);
        CPPUNIT_ASSERT_EQUAL(iq->getTo(), result->getFrom());
        CPPUNIT_ASSERT(window_->labelsEnabled_);
        CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */
        CPPUNIT_ASSERT(message);
        CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody().get());
        CPPUNIT_ASSERT_EQUAL(label, message->getPayload<SecurityLabel>());
    }

    void testCorrectMessageWithLabelItem() {
        std::shared_ptr<SecurityLabel> label = std::make_shared<SecurityLabel>();
        label->setLabel("a");
        SecurityLabelsCatalog::Item labelItem;
        labelItem.setSelector("Bob");
        labelItem.setLabel(label);
        std::shared_ptr<SecurityLabel> label2 = std::make_shared<SecurityLabel>();
        label->setLabel("b");
        SecurityLabelsCatalog::Item labelItem2;
        labelItem2.setSelector("Charlie");
        labelItem2.setLabel(label2);
        window_->label_ = labelItem;
        std::shared_ptr<DiscoInfo> features = std::make_shared<DiscoInfo>();
        features->addFeature(DiscoInfo::SecurityLabelsCatalogFeature);
        controller_->setAvailableServerFeatures(features);
        IQ::ref iq = iqChannel_->iqs_[iqChannel_->iqs_.size() - 1];
        SecurityLabelsCatalog::ref labelPayload = std::make_shared<SecurityLabelsCatalog>();
        labelPayload->addItem(labelItem);
        IQ::ref result = IQ::createResult(self_, iq->getID(), labelPayload);
        iqChannel_->onIQReceived(result);
        std::string messageBody("agamemnon");
        window_->onSendMessageRequest(messageBody, false);
        std::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1];
        Message::ref message = std::dynamic_pointer_cast<Message>(rawStanza);
        CPPUNIT_ASSERT_EQUAL(iq->getTo(), result->getFrom());
        CPPUNIT_ASSERT(window_->labelsEnabled_);
        CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */
        CPPUNIT_ASSERT(message);
        CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody().get());
        CPPUNIT_ASSERT_EQUAL(label, message->getPayload<SecurityLabel>());
        window_->label_ = labelItem2;
        window_->onSendMessageRequest(messageBody, true);
        rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1];
        message = std::dynamic_pointer_cast<Message>(rawStanza);
        CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody().get());
        CPPUNIT_ASSERT_EQUAL(label, message->getPayload<SecurityLabel>());
    }

    void checkEqual(const std::vector<NickJoinPart>& expected, const std::vector<NickJoinPart>& actual) {
        CPPUNIT_ASSERT_EQUAL(expected.size(), actual.size());
        for (size_t i = 0; i < expected.size(); i++) {
            CPPUNIT_ASSERT_EQUAL(expected[i].nick, actual[i].nick);
            CPPUNIT_ASSERT_EQUAL(expected[i].type, actual[i].type);
        }
    }

    void testAppendToJoinParts() {
        std::vector<NickJoinPart> list;
        std::vector<NickJoinPart> gold;
        MUCController::appendToJoinParts(list, NickJoinPart("Kev", Join));
        gold.push_back(NickJoinPart("Kev", Join));
        checkEqual(gold, list);
        MUCController::appendToJoinParts(list, NickJoinPart("Remko", Join));
        gold.push_back(NickJoinPart("Remko", Join));
        checkEqual(gold, list);
        MUCController::appendToJoinParts(list, NickJoinPart("Bert", Join));
        gold.push_back(NickJoinPart("Bert", Join));
        checkEqual(gold, list);
        MUCController::appendToJoinParts(list, NickJoinPart("Bert", Part));
        gold[2].type = JoinThenPart;
        checkEqual(gold, list);
        MUCController::appendToJoinParts(list, NickJoinPart("Kev", Part));
        gold[0].type = JoinThenPart;
        checkEqual(gold, list);
        MUCController::appendToJoinParts(list, NickJoinPart("Remko", Part));
        gold[1].type = JoinThenPart;
        checkEqual(gold, list);
        MUCController::appendToJoinParts(list, NickJoinPart("Ernie", Part));
        gold.push_back(NickJoinPart("Ernie", Part));
        checkEqual(gold, list);
        MUCController::appendToJoinParts(list, NickJoinPart("Ernie", Join));
        gold[3].type = PartThenJoin;
        checkEqual(gold, list);
        MUCController::appendToJoinParts(list, NickJoinPart("Kev", Join));
        gold[0].type = Join;
        checkEqual(gold, list);
        MUCController::appendToJoinParts(list, NickJoinPart("Ernie", Part));
        gold[3].type = Part;
        checkEqual(gold, list);

    }

    void testJoinPartStringContructionSimple() {
        std::vector<NickJoinPart> list;
        list.push_back(NickJoinPart("Kev", Join));
        CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered the room"), MUCController::generateJoinPartString(list, false));
        list.push_back(NickJoinPart("Remko", Part));
        CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false));
        list.push_back(NickJoinPart("Bert", Join));
        CPPUNIT_ASSERT_EQUAL(std::string("Kev and Bert have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false));
        list.push_back(NickJoinPart("Ernie", Join));
        CPPUNIT_ASSERT_EQUAL(std::string("Kev, Bert and Ernie have entered the room and Remko has left the room"), MUCController::generateJoinPartString(list, false));
    }

    void testJoinPartStringContructionMixed() {
        std::vector<NickJoinPart> list;
        list.push_back(NickJoinPart("Kev", JoinThenPart));
        CPPUNIT_ASSERT_EQUAL(std::string("Kev has entered then left the room"), MUCController::generateJoinPartString(list, false));
        list.push_back(NickJoinPart("Remko", Part));
        CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room and Kev has entered then left the room"), MUCController::generateJoinPartString(list, false));
        list.push_back(NickJoinPart("Bert", PartThenJoin));
        CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room, Kev has entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list, false));
        list.push_back(NickJoinPart("Ernie", JoinThenPart));
        CPPUNIT_ASSERT_EQUAL(std::string("Remko has left the room, Kev and Ernie have entered then left the room and Bert has left then returned to the room"), MUCController::generateJoinPartString(list, false));
    }

    JID jidFromOccupant(const MUCOccupant& occupant) {
        return JID(mucJID_.toString()+"/"+occupant.getNick());
    }

    void testRoleAffiliationStates() {

        typedef std::map<std::string, MUCOccupant> occupant_map;
        occupant_map occupants;
        occupants.insert(occupant_map::value_type("Kev", MUCOccupant("Kev", MUCOccupant::Participant, MUCOccupant::Owner)));
        occupants.insert(occupant_map::value_type("Remko", MUCOccupant("Remko", MUCOccupant::Participant, MUCOccupant::Owner)));
        occupants.insert(occupant_map::value_type("Bert", MUCOccupant("Bert", MUCOccupant::Participant, MUCOccupant::Owner)));
        occupants.insert(occupant_map::value_type("Ernie", MUCOccupant("Ernie", MUCOccupant::Participant, MUCOccupant::Owner)));

        /* populate the MUC with fake users */
        typedef const std::pair<std::string,MUCOccupant> occupantIterator;
        foreach(occupantIterator &occupant, occupants) {
            muc_->insertOccupant(occupant.second);
        }

        std::vector<MUCOccupant> alterations;
        alterations.push_back(MUCOccupant("Kev", MUCOccupant::Visitor, MUCOccupant::Admin));
        alterations.push_back(MUCOccupant("Remko", MUCOccupant::Moderator, MUCOccupant::Member));
        alterations.push_back(MUCOccupant("Bert", MUCOccupant::Visitor, MUCOccupant::Outcast));
        alterations.push_back(MUCOccupant("Ernie", MUCOccupant::NoRole, MUCOccupant::Member));
        alterations.push_back(MUCOccupant("Bert", MUCOccupant::Moderator, MUCOccupant::Owner));
        alterations.push_back(MUCOccupant("Kev", MUCOccupant::Participant, MUCOccupant::Outcast));
        alterations.push_back(MUCOccupant("Bert", MUCOccupant::Visitor, MUCOccupant::NoAffiliation));
        alterations.push_back(MUCOccupant("Remko", MUCOccupant::NoRole, MUCOccupant::NoAffiliation));
        alterations.push_back(MUCOccupant("Ernie", MUCOccupant::Visitor, MUCOccupant::Outcast));

        foreach(const MUCOccupant& alteration, alterations) {
            /* perform an alteration to a user's role and affiliation */
            occupant_map::iterator occupant = occupants.find(alteration.getNick());
            CPPUNIT_ASSERT(occupant != occupants.end());
            const JID jid = jidFromOccupant(occupant->second);
            /* change the affiliation, leave the role in place */
            muc_->changeAffiliation(jid, alteration.getAffiliation());
            occupant->second = MUCOccupant(occupant->first, occupant->second.getRole(), alteration.getAffiliation());
            testRoleAffiliationStatesVerify(occupants);
            /* change the role, leave the affiliation in place */
            muc_->changeOccupantRole(jid, alteration.getRole());
            occupant->second = MUCOccupant(occupant->first, alteration.getRole(), occupant->second.getAffiliation());
            testRoleAffiliationStatesVerify(occupants);
        }
    }

    void testSubjectChangeCorrect() {
        std::string messageBody("test message");
        window_->onSendMessageRequest(messageBody, false);
        std::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1];
        Message::ref message = std::dynamic_pointer_cast<Message>(rawStanza);
        CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */
        CPPUNIT_ASSERT(message);
        CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody().get_value_or(""));

        {
            Message::ref message = std::make_shared<Message>();
            message->setType(Message::Groupchat);
            message->setTo(self_);
            message->setFrom(mucJID_.withResource("SomeNickname"));
            message->setID(iqChannel_->getNewIQID());
            message->setSubject("New Room Subject");

            controller_->handleIncomingMessage(std::make_shared<MessageEvent>(message));
            CPPUNIT_ASSERT_EQUAL(std::string("The room subject is now: New Room Subject"), std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(window_->lastAddedSystemMessage_.getParts()[0])->text);
        }
    }

    /*
     * Test that message stanzas with subject element and non-empty body element do not cause a subject change.
     */
    void testSubjectChangeIncorrectA() {
        std::string messageBody("test message");
        window_->onSendMessageRequest(messageBody, false);
        std::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1];
        Message::ref message = std::dynamic_pointer_cast<Message>(rawStanza);
        CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */
        CPPUNIT_ASSERT(message);
        CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody().get_value_or(""));

        {
            Message::ref message = std::make_shared<Message>();
            message->setType(Message::Groupchat);
            message->setTo(self_);
            message->setFrom(mucJID_.withResource("SomeNickname"));
            message->setID(iqChannel_->getNewIQID());
            message->setSubject("New Room Subject");
            message->setBody("Some body text that prevents this stanza from being a subject change.");

            controller_->handleIncomingMessage(std::make_shared<MessageEvent>(message));
            CPPUNIT_ASSERT_EQUAL(std::string("Trying to enter room teaparty@rooms.wonderland.lit"), std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(window_->lastAddedSystemMessage_.getParts()[0])->text);
        }
    }

    /*
     * Test that message stanzas with subject element and thread element do not cause a subject change.
     */
    void testSubjectChangeIncorrectB() {
        std::string messageBody("test message");
        window_->onSendMessageRequest(messageBody, false);
        std::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1];
        Message::ref message = std::dynamic_pointer_cast<Message>(rawStanza);
        CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */
        CPPUNIT_ASSERT(message);
        CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody().get_value_or(""));

        {
            Message::ref message = std::make_shared<Message>();
            message->setType(Message::Groupchat);
            message->setTo(self_);
            message->setFrom(mucJID_.withResource("SomeNickname"));
            message->setID(iqChannel_->getNewIQID());
            message->setSubject("New Room Subject");
            message->addPayload(std::make_shared<Thread>("Thread that prevents the subject change."));

            controller_->handleIncomingMessage(std::make_shared<MessageEvent>(message));
            CPPUNIT_ASSERT_EQUAL(std::string("Trying to enter room teaparty@rooms.wonderland.lit"), std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(window_->lastAddedSystemMessage_.getParts()[0])->text);
        }
    }

    /*
     * Test that message stanzas with subject element and empty body element do not cause a subject change.
     */
    void testSubjectChangeIncorrectC() {
        std::string messageBody("test message");
        window_->onSendMessageRequest(messageBody, false);
        std::shared_ptr<Stanza> rawStanza = stanzaChannel_->sentStanzas[stanzaChannel_->sentStanzas.size() - 1];
        Message::ref message = std::dynamic_pointer_cast<Message>(rawStanza);
        CPPUNIT_ASSERT(stanzaChannel_->isAvailable()); /* Otherwise will prevent sends. */
        CPPUNIT_ASSERT(message);
        CPPUNIT_ASSERT_EQUAL(messageBody, message->getBody().get_value_or(""));

        {
            Message::ref message = std::make_shared<Message>();
            message->setType(Message::Groupchat);
            message->setTo(self_);
            message->setFrom(mucJID_.withResource("SomeNickname"));
            message->setID(iqChannel_->getNewIQID());
            message->setSubject("New Room Subject");
            message->setBody("");

            controller_->handleIncomingMessage(std::make_shared<MessageEvent>(message));
            CPPUNIT_ASSERT_EQUAL(std::string("Trying to enter room teaparty@rooms.wonderland.lit"), std::dynamic_pointer_cast<ChatWindow::ChatTextMessagePart>(window_->lastAddedSystemMessage_.getParts()[0])->text);
        }
    }

    void testRoleAffiliationStatesVerify(const std::map<std::string, MUCOccupant> &occupants) {
        /* verify that the roster is in sync */
        GroupRosterItem* group = window_->getRosterModel()->getRoot();
        foreach(RosterItem* rosterItem, group->getChildren()) {
            GroupRosterItem* child = dynamic_cast<GroupRosterItem*>(rosterItem);
            CPPUNIT_ASSERT(child);
            foreach(RosterItem* childItem, child->getChildren()) {
                ContactRosterItem* item = dynamic_cast<ContactRosterItem*>(childItem);
                CPPUNIT_ASSERT(item);
                std::map<std::string, MUCOccupant>::const_iterator occupant = occupants.find(item->getJID().getResource());
                CPPUNIT_ASSERT(occupant != occupants.end());
                CPPUNIT_ASSERT(item->getMUCRole() == occupant->second.getRole());
                CPPUNIT_ASSERT(item->getMUCAffiliation() == occupant->second.getAffiliation());
            }
        }
    }

private:
    JID self_;
    JID mucJID_;
    MockMUC::ref muc_;
    std::string nick_;
    DummyStanzaChannel* stanzaChannel_;
    DummyIQChannel* iqChannel_;
    IQRouter* iqRouter_;
    EventController* eventController_;
    ChatWindowFactory* chatWindowFactory_;
    UserSearchWindowFactory* userSearchWindowFactory_;
    MUCController* controller_;
//    NickResolver* nickResolver_;
    PresenceOracle* presenceOracle_;
    AvatarManager* avatarManager_;
    StanzaChannelPresenceSender* presenceSender_;
    DirectedPresenceSender* directedPresenceSender_;
    MockRepository* mocks_;
    UIEventStream* uiEventStream_;
    MockChatWindow* window_;
    MUCRegistry* mucRegistry_;
    DummyEntityCapsProvider* entityCapsProvider_;
    DummySettingsProvider* settings_;
    HighlightManager* highlightManager_;
    std::shared_ptr<ChatMessageParser> chatMessageParser_;
    std::shared_ptr<CryptoProvider> crypto_;
    VCardManager* vcardManager_;
    VCardMemoryStorage* vcardStorage_;
    ClientBlockListManager* clientBlockListManager_;
    MUCBookmarkManager* mucBookmarkManager_;
    XMPPRoster* xmppRoster_;
};

CPPUNIT_TEST_SUITE_REGISTRATION(MUCControllerTest);