From 26994474c1ebfe874c2cd62ededf9a82b0496136 Mon Sep 17 00:00:00 2001
From: Richard Maudsley <richard.maudsley@isode.com>
Date: Mon, 16 Dec 2013 09:45:40 +0000
Subject: Add affiliations to tooltips for MUC occupant lists.

Also extracts MUC into an interface and MUCImpl the existing implementation, adds a MockMUC for using in unit tests, and adds unit tests for the MUCController changes.

Change-Id: I25034384f59d3c274c46ffc37b2d1ae60ec660f4

diff --git a/Swift/Controllers/Chat/MUCController.cpp b/Swift/Controllers/Chat/MUCController.cpp
index afcb782..14d1767 100644
--- a/Swift/Controllers/Chat/MUCController.cpp
+++ b/Swift/Controllers/Chat/MUCController.cpp
@@ -32,6 +32,7 @@
 #include <Swift/Controllers/Roster/GroupRosterItem.h>
 #include <Swift/Controllers/Roster/ItemOperations/SetAvatar.h>
 #include <Swift/Controllers/Roster/ItemOperations/SetPresence.h>
+#include <Swift/Controllers/Roster/ItemOperations/SetMUC.h>
 #include <Swift/Controllers/Roster/Roster.h>
 #include <Swift/Controllers/Roster/RosterVCardProvider.h>
 #include <Swift/Controllers/UIEvents/InviteToMUCUIEvent.h>
@@ -385,6 +386,7 @@ void MUCController::handleOccupantJoined(const MUCOccupant& occupant) {
 	appendToJoinParts(joinParts_, event);
 	std::string groupName(roleToGroupName(occupant.getRole()));
 	roster_->addContact(jid, realJID, occupant.getNick(), groupName, avatarManager_->getAvatarPath(jid));
+	roster_->applyOnItems(SetMUC(jid, occupant.getRole(), occupant.getAffiliation()));
 	roster_->getGroup(groupName)->setManualSort(roleToSortName(occupant.getRole()));
 	if (joined_) {
 		std::string joinString;
@@ -537,6 +539,7 @@ void MUCController::handleOccupantRoleChanged(const std::string& nick, const MUC
 	std::string group(roleToGroupName(occupant.getRole()));
 	roster_->addContact(jid, realJID, nick, group, avatarManager_->getAvatarPath(jid));
 	roster_->getGroup(group)->setManualSort(roleToSortName(occupant.getRole()));
+	roster_->applyOnItems(SetMUC(jid, occupant.getRole(), occupant.getAffiliation()));
 	chatWindow_->addSystemMessage(chatMessageParser_->parseMessageBody(str(format(QT_TRANSLATE_NOOP("", "%1% is now a %2%")) % nick % roleToFriendlyName(occupant.getRole()))), ChatWindow::DefaultDirection);
 	if (nick == nick_) {
 		setAvailableRoomActions(occupant.getAffiliation(), occupant.getRole());
@@ -548,6 +551,9 @@ void MUCController::handleOccupantAffiliationChanged(const std::string& nick, co
 	if (nick == nick_) {
 		setAvailableRoomActions(affiliation, muc_->getOccupant(nick_).getRole());
 	}
+	JID jid(nickToJID(nick));
+	MUCOccupant occupant = muc_->getOccupant(nick);
+	roster_->applyOnItems(SetMUC(jid, occupant.getRole(), affiliation));
 }
 
 std::string MUCController::roleToGroupName(MUCOccupant::Role role) {
diff --git a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
index 291fb22..3652e86 100644
--- a/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
+++ b/Swift/Controllers/Chat/UnitTest/MUCControllerTest.cpp
@@ -9,6 +9,7 @@
 #include <boost/algorithm/string.hpp>
 #include <hippomocks.h>
 
+#include "Swiften/Base/foreach.h"
 #include "Swift/Controllers/XMPPEvents/EventController.h"
 #include "Swiften/Presence/DirectedPresenceSender.h"
 #include "Swiften/Presence/StanzaChannelPresenceSender.h"
@@ -20,6 +21,7 @@
 #include "Swiften/Roster/XMPPRoster.h"
 #include "Swift/Controllers/UIEvents/UIEventStream.h"
 #include "Swift/Controllers/UnitTest/MockChatWindow.h"
+#include "Swiften/MUC/UnitTest/MockMUC.h"
 #include "Swiften/Client/DummyStanzaChannel.h"
 #include "Swiften/Queries/DummyIQChannel.h"
 #include "Swiften/Presence/PresenceOracle.h"
@@ -33,6 +35,8 @@
 #include <Swift/Controllers/Chat/ChatMessageParser.h>
 #include <Swift/Controllers/Chat/UserSearchController.h>
 #include <Swift/Controllers/UIInterfaces/UserSearchWindowFactory.h>
+#include <Swift/Controllers/Roster/Roster.h>
+#include <Swift/Controllers/Roster/GroupRosterItem.h>
 #include <Swiften/Crypto/CryptoProvider.h>
 
 using namespace Swift;
@@ -48,6 +52,7 @@ class MUCControllerTest : public CppUnit::TestFixture {
 	CPPUNIT_TEST(testMessageWithEmptyLabelItem);
 	CPPUNIT_TEST(testMessageWithLabelItem);
 	CPPUNIT_TEST(testCorrectMessageWithLabelItem);
+	CPPUNIT_TEST(testRoleAffiliationStates);
 	CPPUNIT_TEST_SUITE_END();
 
 public:
@@ -74,7 +79,7 @@ public:
 		entityCapsProvider_ = new DummyEntityCapsProvider();
 		settings_ = new DummySettingsProvider();
 		highlightManager_ = new HighlightManager(settings_);
-		muc_ = boost::make_shared<MUC>(stanzaChannel_, iqRouter_, directedPresenceSender_, mucJID_, mucRegistry_);
+		muc_ = boost::make_shared<MockMUC>(mucJID_);
 		mocks_->ExpectCall(chatWindowFactory_, ChatWindowFactory::createChatWindow).With(muc_->getJID(), uiEventStream_).Return(window_);
 		chatMessageParser_ = new ChatMessageParser(std::map<std::string, std::string>());
 		vcardStorage_ = new VCardMemoryStorage(crypto_.get());
@@ -337,10 +342,73 @@ public:
 		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 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_;
-	MUC::ref muc_;
+	MockMUC::ref muc_;
 	std::string nick_;
 	DummyStanzaChannel* stanzaChannel_;
 	DummyIQChannel* iqChannel_;
diff --git a/Swift/Controllers/Roster/ContactRosterItem.cpp b/Swift/Controllers/Roster/ContactRosterItem.cpp
index 622b6ae..fde4c97 100644
--- a/Swift/Controllers/Roster/ContactRosterItem.cpp
+++ b/Swift/Controllers/Roster/ContactRosterItem.cpp
@@ -11,13 +11,15 @@
 #include <Swiften/Base/foreach.h>
 #include <Swiften/Base/DateTime.h>
 #include <Swiften/Elements/Idle.h>
-
+#include <Swift/Controllers/Intl.h>
 #include <Swift/Controllers/Roster/GroupRosterItem.h>
 
 namespace Swift {
 
 
-ContactRosterItem::ContactRosterItem(const JID& jid, const JID& displayJID, const std::string& name, GroupRosterItem* parent) : RosterItem(name, parent), jid_(jid), displayJID_(displayJID), blockState_(BlockingNotSupported) {
+ContactRosterItem::ContactRosterItem(const JID& jid, const JID& displayJID, const std::string& name, GroupRosterItem* parent)
+: RosterItem(name, parent), jid_(jid), displayJID_(displayJID), mucRole_(MUCOccupant::NoRole), mucAffiliation_(MUCOccupant::NoAffiliation), blockState_(BlockingNotSupported)
+{
 }
 
 ContactRosterItem::~ContactRosterItem() {
@@ -137,6 +139,40 @@ void ContactRosterItem::removeGroup(const std::string& group) {
 	groups_.erase(std::remove(groups_.begin(), groups_.end(), group), groups_.end());
 }
 
+MUCOccupant::Role ContactRosterItem::getMUCRole() const
+{
+	return mucRole_;
+}
+
+void ContactRosterItem::setMUCRole(const MUCOccupant::Role& role)
+{
+	mucRole_ = role;
+}
+
+MUCOccupant::Affiliation ContactRosterItem::getMUCAffiliation() const
+{
+	return mucAffiliation_;
+}
+
+void ContactRosterItem::setMUCAffiliation(const MUCOccupant::Affiliation& affiliation)
+{
+	mucAffiliation_ = affiliation;
+}
+
+std::string ContactRosterItem::getMUCAffiliationText() const
+{
+	std::string affiliationString;
+	switch (mucAffiliation_) {
+		case MUCOccupant::Owner: affiliationString = QT_TRANSLATE_NOOP("", "Owner"); break;
+		case MUCOccupant::Admin: affiliationString = QT_TRANSLATE_NOOP("", "Admin"); break;
+		case MUCOccupant::Member: affiliationString = QT_TRANSLATE_NOOP("", "Member"); break;
+		case MUCOccupant::Outcast: affiliationString = QT_TRANSLATE_NOOP("", "Outcast"); break;
+		case MUCOccupant::NoAffiliation: affiliationString = ""; break;
+	}
+
+	return affiliationString;
+}
+
 void ContactRosterItem::setSupportedFeatures(const std::set<Feature>& features) {
 	features_ = features;
 	onDataChanged();
diff --git a/Swift/Controllers/Roster/ContactRosterItem.h b/Swift/Controllers/Roster/ContactRosterItem.h
index 6de7909..ab10c66 100644
--- a/Swift/Controllers/Roster/ContactRosterItem.h
+++ b/Swift/Controllers/Roster/ContactRosterItem.h
@@ -18,6 +18,7 @@
 #include <Swiften/Base/boost_bsignals.h>
 #include <Swiften/Elements/Presence.h>
 #include <Swiften/Elements/StatusShow.h>
+#include <Swiften/Elements/MUCOccupant.h>
 #include <Swiften/Elements/VCard.h>
 #include <Swiften/JID/JID.h>
 
@@ -61,7 +62,13 @@ class ContactRosterItem : public RosterItem {
 		/** Only used so a contact can know about the groups it's in*/
 		void addGroup(const std::string& group);
 		void removeGroup(const std::string& group);
-		
+
+		MUCOccupant::Role getMUCRole() const;
+		void setMUCRole(const MUCOccupant::Role& role);
+		MUCOccupant::Affiliation getMUCAffiliation() const;
+		void setMUCAffiliation(const MUCOccupant::Affiliation& affiliation);
+		std::string getMUCAffiliationText() const;
+
 		void setSupportedFeatures(const std::set<Feature>& features);
 		bool supportsFeature(Feature feature) const;
 
@@ -82,6 +89,8 @@ class ContactRosterItem : public RosterItem {
 		boost::shared_ptr<Presence> offlinePresence_;
 		boost::shared_ptr<Presence> shownPresence_;
 		std::vector<std::string> groups_;
+		MUCOccupant::Role mucRole_;
+		MUCOccupant::Affiliation mucAffiliation_;
 		std::set<Feature> features_;
 		BlockState blockState_;
 		VCard::ref vcard_;
diff --git a/Swift/Controllers/Roster/ItemOperations/SetMUC.h b/Swift/Controllers/Roster/ItemOperations/SetMUC.h
new file mode 100644
index 0000000..de40e04
--- /dev/null
+++ b/Swift/Controllers/Roster/ItemOperations/SetMUC.h
@@ -0,0 +1,39 @@
+/*
+ * Copyright (c) 2013 Kevin Smith and Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/JID/JID.h>
+
+#include <Swift/Controllers/Roster/ItemOperations/RosterItemOperation.h>
+#include <Swift/Controllers/Roster/ContactRosterItem.h>
+
+namespace Swift {
+
+class RosterItem;
+
+class SetMUC : public RosterItemOperation {
+	public:
+		SetMUC(const JID& jid, const MUCOccupant::Role& role, const MUCOccupant::Affiliation& affiliation)
+		: RosterItemOperation(true, jid), jid_(jid), mucRole_(role), mucAffiliation_(affiliation) {
+		}
+
+		virtual void operator() (RosterItem* item) const {
+			ContactRosterItem* contact = dynamic_cast<ContactRosterItem*>(item);
+			if (contact && contact->getJID().equals(jid_, JID::WithResource)) {
+				contact->setMUCRole(mucRole_);
+				contact->setMUCAffiliation(mucAffiliation_);
+			}
+		}
+
+	private:
+		JID jid_;
+		bool mucParticipant_;
+		MUCOccupant::Role mucRole_;
+		MUCOccupant::Affiliation mucAffiliation_;
+};
+
+}
diff --git a/Swift/Controllers/UnitTest/MockChatWindow.h b/Swift/Controllers/UnitTest/MockChatWindow.h
index 43779c5..59ed0f1 100644
--- a/Swift/Controllers/UnitTest/MockChatWindow.h
+++ b/Swift/Controllers/UnitTest/MockChatWindow.h
@@ -49,7 +49,8 @@ namespace Swift {
 			virtual void setSecurityLabelsError() {}
 			virtual SecurityLabelsCatalog::Item getSelectedSecurityLabel() {return label_;}
 			virtual void setInputEnabled(bool /*enabled*/) {}
-			virtual void setRosterModel(Roster* /*roster*/) {}
+			virtual void setRosterModel(Roster* roster) { roster_ = roster; }
+			Roster* getRosterModel() { return roster_; }
 			virtual void setTabComplete(TabComplete*) {}
 
 			void setAckState(const std::string& /*id*/, AckState /*state*/) {}
@@ -86,6 +87,7 @@ namespace Swift {
 			std::vector<SecurityLabelsCatalog::Item> labels_;
 			bool labelsEnabled_;
 			SecurityLabelsCatalog::Item label_;
+			Roster* roster_;
 	};
 }
 
diff --git a/Swift/QtUI/Roster/RosterTooltip.cpp b/Swift/QtUI/Roster/RosterTooltip.cpp
index edf9c99..045a955 100644
--- a/Swift/QtUI/Roster/RosterTooltip.cpp
+++ b/Swift/QtUI/Roster/RosterTooltip.cpp
@@ -39,6 +39,7 @@ QString RosterTooltip::buildDetailedTooltip(ContactRosterItem* contact, QtScaled
 						"%6"
 						"%7"
 						"%8"
+						"%9"
 					"</td>"
 				"</tr>"
 			"</table>");
@@ -55,6 +56,7 @@ QString RosterTooltip::buildDetailedTooltip(ContactRosterItem* contact, QtScaled
 						"%6"
 						"%7"
 						"%8"
+						"%9"
 					"</td>"
 				"</tr>"
 			"</table>");
@@ -97,7 +99,12 @@ QString RosterTooltip::buildDetailedTooltip(ContactRosterItem* contact, QtScaled
 		lastSeen = htmlEscape(lastSeen) + "<br/>";
 	}
 
-	return tooltipTemplate.arg(scaledAvatarPath, htmlEscape(fullName), htmlEscape(bareJID), presenceIconTag, htmlEscape(statusMessage), idleString, lastSeen, vCardSummary);
+	QString mucOccupant= P2QSTRING(contact->getMUCAffiliationText());
+	if (!mucOccupant.isEmpty()) {
+		mucOccupant = htmlEscape(mucOccupant) + "<br/>";
+	}
+
+	return tooltipTemplate.arg(scaledAvatarPath, htmlEscape(fullName), htmlEscape(bareJID), presenceIconTag, htmlEscape(statusMessage), mucOccupant, idleString, lastSeen, vCardSummary);
 }
 
 QString RosterTooltip::buildVCardSummary(VCard::ref vcard) {
diff --git a/Swiften/MUC/MUC.cpp b/Swiften/MUC/MUC.cpp
index f85cf8d..c7ba470 100644
--- a/Swiften/MUC/MUC.cpp
+++ b/Swiften/MUC/MUC.cpp
@@ -1,430 +1,14 @@
 /*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2013 Kevin Smith
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
 
 #include <Swiften/MUC/MUC.h>
 
-#include <boost/bind.hpp>
-#include <boost/shared_ptr.hpp>
-#include <boost/smart_ptr/make_shared.hpp>
-
-#include <Swiften/Base/foreach.h>
-#include <Swiften/Presence/DirectedPresenceSender.h>
-#include <Swiften/Client/StanzaChannel.h>
-#include <Swiften/Queries/IQRouter.h>
-#include <Swiften/Elements/Form.h>
-#include <Swiften/Elements/Message.h>
-#include <Swiften/Elements/IQ.h>
-#include <Swiften/Elements/MUCUserPayload.h>
-#include <Swiften/Elements/MUCAdminPayload.h>
-#include <Swiften/Elements/MUCPayload.h>
-#include <Swiften/Elements/MUCDestroyPayload.h>
-#include <Swiften/Elements/MUCInvitationPayload.h>
-#include <Swiften/MUC/MUCRegistry.h>
-#include <Swiften/Queries/GenericRequest.h>
-
 namespace Swift {
 
-typedef std::pair<std::string, MUCOccupant> StringMUCOccupantPair;
-
-MUC::MUC(StanzaChannel* stanzaChannel, IQRouter* iqRouter, DirectedPresenceSender* presenceSender, const JID &muc, MUCRegistry* mucRegistry) : ownMUCJID(muc), stanzaChannel(stanzaChannel), iqRouter_(iqRouter), presenceSender(presenceSender), mucRegistry(mucRegistry), createAsReservedIfNew(false), unlocking(false), isUnlocked_(false) {
-	scopedConnection_ = stanzaChannel->onPresenceReceived.connect(boost::bind(&MUC::handleIncomingPresence, this, _1));
-}
-
-//FIXME: discover reserved nickname
-
-/**
- * Join the MUC with default context.
- */
-void MUC::joinAs(const std::string &nick) {
-	joinSince_ = boost::posix_time::not_a_date_time;
-	internalJoin(nick);
-}
-
-/**
- * Set the password used for entering the room.
- */
-void MUC::setPassword(const boost::optional<std::string>& newPassword) {
-	password = newPassword;
-}
-
-/**
- * Join the MUC with context since date.
- */
-void MUC::joinWithContextSince(const std::string &nick, const boost::posix_time::ptime& since) {
-	joinSince_ = since;
-	internalJoin(nick);
-}
-
-std::map<std::string, MUCOccupant> MUC::getOccupants() const {
-	return occupants;
-}
-
-void MUC::internalJoin(const std::string &nick) {
-	//TODO: history request
-	joinComplete_ = false;
-	joinSucceeded_ = false;
-
-	mucRegistry->addMUC(getJID());
-
-	ownMUCJID = JID(ownMUCJID.getNode(), ownMUCJID.getDomain(), nick);
-
-	Presence::ref joinPresence = boost::make_shared<Presence>(*presenceSender->getLastSentUndirectedPresence());
-	assert(joinPresence->getType() == Presence::Available);
-	joinPresence->setTo(ownMUCJID);
-	MUCPayload::ref mucPayload = boost::make_shared<MUCPayload>();
-	if (joinSince_ != boost::posix_time::not_a_date_time) {
-		mucPayload->setSince(joinSince_);
-	}
-	if (password) {
-		mucPayload->setPassword(*password);
-	}
-	joinPresence->addPayload(mucPayload);
-
-	presenceSender->sendPresence(joinPresence);
-}
-
-void MUC::part() {
-	presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence);
-	mucRegistry->removeMUC(getJID());
-}
-
-void MUC::handleUserLeft(LeavingType type) {
-	std::map<std::string,MUCOccupant>::iterator i = occupants.find(ownMUCJID.getResource());
-	if (i != occupants.end()) {
-		MUCOccupant me = i->second;
-		occupants.erase(i);
-		onOccupantLeft(me, type, "");
-	}
-	occupants.clear();
-	joinComplete_ = false;
-	joinSucceeded_ = false;
-	isUnlocked_ = false;
-	presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::DontSendPresence);
-}
-
-void MUC::handleIncomingPresence(Presence::ref presence) {
-	if (!isFromMUC(presence->getFrom())) {
-		return;
-	}
-
-	MUCUserPayload::ref mucPayload;
-	foreach (MUCUserPayload::ref payload, presence->getPayloads<MUCUserPayload>()) {
-		if (!payload->getItems().empty() || !payload->getStatusCodes().empty()) {
-			mucPayload = payload;
-		}
-	}
-	
-	// On the first incoming presence, check if our join has succeeded 
-	// (i.e. we start getting non-error presence from the MUC) or not
-	if (!joinSucceeded_) {
-		if (presence->getType() == Presence::Error) {
-			std::string reason;
-			onJoinFailed(presence->getPayload<ErrorPayload>());
-			return;
-		}
-		else {
-			joinSucceeded_ = true;
-			presenceSender->addDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence);
-		}
-	}
-
-	std::string nick = presence->getFrom().getResource();
-	if (nick.empty()) {
-		return;
-	}
-	MUCOccupant::Role role(MUCOccupant::NoRole);
-	MUCOccupant::Affiliation affiliation(MUCOccupant::NoAffiliation);
-	boost::optional<JID> realJID;
-	if (mucPayload && mucPayload->getItems().size() > 0) {
-		role = mucPayload->getItems()[0].role ? mucPayload->getItems()[0].role.get() : MUCOccupant::NoRole;
-		affiliation = mucPayload->getItems()[0].affiliation ? mucPayload->getItems()[0].affiliation.get() : MUCOccupant::NoAffiliation;
-		realJID = mucPayload->getItems()[0].realJID;
-	}
-
-	//100 is non-anonymous
-	//TODO: 100 may also be specified in a <message/>
-	//170 is room logging to http
-	//TODO: Nick changes
-	if (presence->getType() == Presence::Unavailable) {
-		LeavingType type = LeavePart;
-		if (mucPayload) {
-			if (boost::dynamic_pointer_cast<MUCDestroyPayload>(mucPayload->getPayload())) {
-				type = LeaveDestroy;
-			}
-			else foreach (MUCUserPayload::StatusCode status, mucPayload->getStatusCodes()) {
-				if (status.code == 307) {
-					type = LeaveKick;
-				}
-				else if (status.code == 301) {
-					type = LeaveBan;
-				}
-				else if (status.code == 321) {
-					type = LeaveNotMember;
-				}
-			}
-		}
-
-		if (presence->getFrom() == ownMUCJID) {
-			handleUserLeft(type);
-			return;
-		} 
-		else {
-			std::map<std::string,MUCOccupant>::iterator i = occupants.find(nick);
-			if (i != occupants.end()) {
-				//TODO: part type
-				MUCOccupant occupant = i->second;
-				occupants.erase(i);
-				onOccupantLeft(occupant, type, "");
-			}
-		}
-	} 
-	else if (presence->getType() == Presence::Available) {
-		std::map<std::string, MUCOccupant>::iterator it = occupants.find(nick);
-		MUCOccupant occupant(nick, role, affiliation);
-		bool isJoin = true;
-		if (realJID) {
-			occupant.setRealJID(realJID.get());
-		}
-		if (it != occupants.end()) {
-			isJoin = false;
-			MUCOccupant oldOccupant = it->second;
-			if (oldOccupant.getRole() != role) {
-				onOccupantRoleChanged(nick, occupant, oldOccupant.getRole());
-			}
-			if (oldOccupant.getAffiliation() != affiliation) {
-				onOccupantAffiliationChanged(nick, affiliation, oldOccupant.getAffiliation());
-			}
-			occupants.erase(it);
-		}
-		std::pair<std::map<std::string, MUCOccupant>::iterator, bool> result = occupants.insert(std::make_pair(nick, occupant));
-		if (isJoin) {
-			onOccupantJoined(result.first->second);
-		}
-		onOccupantPresenceChange(presence);
-	}
-	if (mucPayload && !joinComplete_) {
-		bool isLocked = false;
-		foreach (MUCUserPayload::StatusCode status, mucPayload->getStatusCodes()) {
-			if (status.code == 110) {
-				/* Simply knowing this is your presence is enough, 210 doesn't seem to be necessary. */
-				joinComplete_ = true;
-				if (ownMUCJID != presence->getFrom()) {
-					presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::DontSendPresence);
-					ownMUCJID = presence->getFrom();
-					presenceSender->addDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence);
-				}
-				onJoinComplete(getOwnNick());
-			}
-			if (status.code == 201) {
-				isLocked = true;
-				/* Room is created and locked */
-				/* Currently deal with this by making an instant room */
-				if (ownMUCJID != presence->getFrom()) {
-					presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::DontSendPresence);
-					ownMUCJID = presence->getFrom();
-					presenceSender->addDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence);
-				}
-				if (createAsReservedIfNew) {
-					unlocking = true;
-					requestConfigurationForm();
-				}
-				else {
-					MUCOwnerPayload::ref mucPayload(new MUCOwnerPayload());
-					presenceSender->addDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::DontSendPresence);
-					mucPayload->setPayload(boost::make_shared<Form>(Form::SubmitType));
-					GenericRequest<MUCOwnerPayload>* request = new GenericRequest<MUCOwnerPayload>(IQ::Set, getJID(), mucPayload, iqRouter_);
-					request->onResponse.connect(boost::bind(&MUC::handleCreationConfigResponse, this, _1, _2));
-					request->send();
-				}
-			}
-		}
-		if (!isLocked && !isUnlocked_ && (presence->getFrom() == ownMUCJID)) {
-			isUnlocked_ = true;
-			onUnlocked();
-		}
-	}
-}
-
-void MUC::handleCreationConfigResponse(MUCOwnerPayload::ref /*unused*/, ErrorPayload::ref error) {
-	unlocking = false;
-	if (error) {
-		presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence);
-		onJoinFailed(error);
-	} else {
-		onJoinComplete(getOwnNick()); /* Previously, this wasn't needed here, as the presence duplication bug caused an emit elsewhere. */
-		isUnlocked_ = true;
-		onUnlocked();
-	}
-}
-
-bool MUC::hasOccupant(const std::string& nick) {
-	return occupants.find(nick) != occupants.end();
-}
-
-const MUCOccupant& MUC::getOccupant(const std::string& nick) {
-	return occupants.find(nick)->second;
-}
-
-void MUC::kickOccupant(const JID& jid) {
-	changeOccupantRole(jid, MUCOccupant::NoRole);
-}
-
-/**
- * Call with the room JID, not the real JID.
- */
-void MUC::changeOccupantRole(const JID& jid, MUCOccupant::Role role) {
-	MUCAdminPayload::ref mucPayload = boost::make_shared<MUCAdminPayload>();
-	MUCItem item;
-	item.role = role;
-	item.nick = jid.getResource();
-	mucPayload->addItem(item);
-	GenericRequest<MUCAdminPayload>* request = new GenericRequest<MUCAdminPayload>(IQ::Set, getJID(), mucPayload, iqRouter_);
-	request->onResponse.connect(boost::bind(&MUC::handleOccupantRoleChangeResponse, this, _1, _2, jid, role));
-	request->send();
-	
-}
-
-void MUC::handleOccupantRoleChangeResponse(MUCAdminPayload::ref /*unused*/, ErrorPayload::ref error, const JID& jid, MUCOccupant::Role role) {
-	if (error) {
-		onRoleChangeFailed(error, jid, role);
-	}
-}
-
-void MUC::requestAffiliationList(MUCOccupant::Affiliation affiliation) {
-	MUCAdminPayload::ref mucPayload = boost::make_shared<MUCAdminPayload>();
-	MUCItem item;
-	item.affiliation = affiliation;
-	mucPayload->addItem(item);
-	GenericRequest<MUCAdminPayload>* request = new GenericRequest<MUCAdminPayload>(IQ::Get, getJID(), mucPayload, iqRouter_);
-	request->onResponse.connect(boost::bind(&MUC::handleAffiliationListResponse, this, _1, _2, affiliation));
-	request->send();
-}
-
-/**
- * Must be called with the real JID, not the room JID.
- */
-void MUC::changeAffiliation(const JID& jid, MUCOccupant::Affiliation affiliation) {
-	MUCAdminPayload::ref mucPayload = boost::make_shared<MUCAdminPayload>();
-	MUCItem item;
-	item.affiliation = affiliation;
-	item.realJID = jid.toBare();
-	mucPayload->addItem(item);
-	GenericRequest<MUCAdminPayload>* request = new GenericRequest<MUCAdminPayload>(IQ::Set, getJID(), mucPayload, iqRouter_);
-	request->onResponse.connect(boost::bind(&MUC::handleAffiliationChangeResponse, this, _1, _2, jid, affiliation));
-	request->send();
-}
-
-void MUC::handleAffiliationListResponse(MUCAdminPayload::ref payload, ErrorPayload::ref error, MUCOccupant::Affiliation affiliation) {
-	if (error) {
-		onAffiliationListFailed(error);
-	}
-	else {
-		std::vector<JID> jids;
-		foreach (MUCItem item, payload->getItems()) {
-			if (item.realJID) {
-				jids.push_back(*item.realJID);
-			}
-		}
-		onAffiliationListReceived(affiliation, jids);
-	}
-}
-
-void MUC::handleAffiliationChangeResponse(MUCAdminPayload::ref /*unused*/, ErrorPayload::ref error, const JID& jid, MUCOccupant::Affiliation affiliation) {
-	if (error) {
-		onAffiliationChangeFailed(error, jid, affiliation);
-	}
-}
-
-void MUC::changeSubject(const std::string& subject) {
-	Message::ref message = boost::make_shared<Message>();
-	message->setSubject(subject);
-	message->setType(Message::Groupchat);
-	message->setTo(ownMUCJID.toBare());
-	stanzaChannel->sendMessage(message);
-}
-
-void MUC::requestConfigurationForm() {
-	MUCOwnerPayload::ref mucPayload(new MUCOwnerPayload());
-	GenericRequest<MUCOwnerPayload>* request = new GenericRequest<MUCOwnerPayload>(IQ::Get, getJID(), mucPayload, iqRouter_);
-	request->onResponse.connect(boost::bind(&MUC::handleConfigurationFormReceived, this, _1, _2));
-	request->send();
+MUC::~MUC() {
 }
 
-void MUC::cancelConfigureRoom() {
-	MUCOwnerPayload::ref mucPayload(new MUCOwnerPayload());
-	mucPayload->setPayload(boost::make_shared<Form>(Form::CancelType));
-	GenericRequest<MUCOwnerPayload>* request = new GenericRequest<MUCOwnerPayload>(IQ::Set, getJID(), mucPayload, iqRouter_);
-	request->send();
-}
-
-void MUC::handleConfigurationFormReceived(MUCOwnerPayload::ref payload, ErrorPayload::ref error) {
-	Form::ref form;
-	if (payload) {
-		form = payload->getForm();
-	}
-	if (error || !form) {
-		onConfigurationFailed(error);
-	} else {
-		onConfigurationFormReceived(form);
-	}
-}
-
-void MUC::handleConfigurationResultReceived(MUCOwnerPayload::ref /*payload*/, ErrorPayload::ref error) {
-	if (error) {
-		onConfigurationFailed(error);
-	}
-}
-
-void MUC::configureRoom(Form::ref form) {
-	MUCOwnerPayload::ref mucPayload(new MUCOwnerPayload());
-	mucPayload->setPayload(form);
-	GenericRequest<MUCOwnerPayload>* request = new GenericRequest<MUCOwnerPayload>(IQ::Set, getJID(), mucPayload, iqRouter_);
-	if (unlocking) {
-		request->onResponse.connect(boost::bind(&MUC::handleCreationConfigResponse, this, _1, _2));
-	}
-	else {
-		request->onResponse.connect(boost::bind(&MUC::handleConfigurationResultReceived, this, _1, _2));
-	}
-	request->send();
-}
-
-void MUC::destroyRoom() {
-	MUCOwnerPayload::ref mucPayload = boost::make_shared<MUCOwnerPayload>();
-	MUCDestroyPayload::ref mucDestroyPayload = boost::make_shared<MUCDestroyPayload>();
-	mucPayload->setPayload(mucDestroyPayload);
-	GenericRequest<MUCOwnerPayload>* request = new GenericRequest<MUCOwnerPayload>(IQ::Set, getJID(), mucPayload, iqRouter_);
-	request->onResponse.connect(boost::bind(&MUC::handleConfigurationResultReceived, this, _1, _2));
-	request->send();
-}
-
-void MUC::invitePerson(const JID& person, const std::string& reason, bool isImpromptu, bool isReuseChat) {
-	Message::ref message = boost::make_shared<Message>();
-	message->setTo(person);
-	message->setType(Message::Normal);
-	MUCInvitationPayload::ref invite = boost::make_shared<MUCInvitationPayload>();
-	invite->setReason(reason);
-	invite->setJID(ownMUCJID.toBare());
-	invite->setIsImpromptu(isImpromptu);
-	invite->setIsContinuation(isReuseChat);
-	message->addPayload(invite);
-	stanzaChannel->sendMessage(message);
-}
-
-//TODO: Invites(direct/mediated)
-
-//TODO: requesting membership
-
-//TODO: get member list
-
-//TODO: request voice
-
-//TODO: moderator use cases
-
-//TODO: Admin use cases
-
-//TODO: Owner use cases
-
 }
diff --git a/Swiften/MUC/MUC.h b/Swiften/MUC/MUC.h
index 6a0ab75..0dcccd9 100644
--- a/Swiften/MUC/MUC.h
+++ b/Swiften/MUC/MUC.h
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2010 Kevin Smith
+ * Copyright (c) 2010-2013 Kevin Smith
  * Licensed under the GNU General Public License v3.
  * See Documentation/Licenses/GPLv3.txt for more information.
  */
@@ -36,50 +36,46 @@ namespace Swift {
 			enum LeavingType { LeavePart, LeaveKick, LeaveBan, LeaveDestroy, LeaveNotMember, Disconnect };
 
 		public:
-			MUC(StanzaChannel* stanzaChannel, IQRouter* iqRouter, DirectedPresenceSender* presenceSender, const JID &muc, MUCRegistry* mucRegistry);
+			virtual ~MUC();
 
 			/**
 			 * Returns the (bare) JID of the MUC.
 			 */
-			JID getJID() const {
-				return ownMUCJID.toBare();
-			}
+			virtual JID getJID() const = 0;
 
 			/**
 			 * Returns if the room is unlocked and other people can join the room.
 			 * @return True if joinable by others; false otherwise.
 			 */
-			bool isUnlocked() const {
-				return isUnlocked_;
-			}
+			virtual bool isUnlocked() const = 0;
 
-			void joinAs(const std::string &nick);
-			void joinWithContextSince(const std::string &nick, const boost::posix_time::ptime& since);
-			/*void queryRoomInfo(); */
-			/*void queryRoomItems(); */
-			std::string getCurrentNick();
-			std::map<std::string, MUCOccupant> getOccupants() const;
-			void part();
-			void handleIncomingMessage(Message::ref message);
+			virtual void joinAs(const std::string &nick) = 0;
+			virtual void joinWithContextSince(const std::string &nick, const boost::posix_time::ptime& since) = 0;
+			/*virtual void queryRoomInfo(); */
+			/*virtual void queryRoomItems(); */
+			/*virtual std::string getCurrentNick() = 0; */
+			virtual std::map<std::string, MUCOccupant> getOccupants() const = 0;
+			virtual void part() = 0;
+			/*virtual void handleIncomingMessage(Message::ref message) = 0; */
 			/** Expose public so it can be called when e.g. user goes offline */
-			void handleUserLeft(LeavingType);
+			virtual void handleUserLeft(LeavingType) = 0;
 			/** Get occupant information*/
-			const MUCOccupant& getOccupant(const std::string& nick);
-			bool hasOccupant(const std::string& nick);
-			void kickOccupant(const JID& jid);
-			void changeOccupantRole(const JID& jid, MUCOccupant::Role role);
-			void requestAffiliationList(MUCOccupant::Affiliation);
-			void changeAffiliation(const JID& jid, MUCOccupant::Affiliation affiliation);
-			void changeSubject(const std::string& subject);
-			void requestConfigurationForm();
-			void configureRoom(Form::ref);
-			void cancelConfigureRoom();
-			void destroyRoom();
+			virtual const MUCOccupant& getOccupant(const std::string& nick) = 0;
+			virtual bool hasOccupant(const std::string& nick) = 0;
+			virtual void kickOccupant(const JID& jid) = 0;
+			virtual void changeOccupantRole(const JID& jid, MUCOccupant::Role role) = 0;
+			virtual void requestAffiliationList(MUCOccupant::Affiliation) = 0;
+			virtual void changeAffiliation(const JID& jid, MUCOccupant::Affiliation affiliation) = 0;
+			virtual void changeSubject(const std::string& subject) = 0;
+			virtual void requestConfigurationForm() = 0;
+			virtual void configureRoom(Form::ref) = 0;
+			virtual void cancelConfigureRoom() = 0;
+			virtual void destroyRoom() = 0;
 			/** Send an invite for the person to join the MUC */
-			void invitePerson(const JID& person, const std::string& reason = "", bool isImpromptu = false, bool isReuseChat = false);
-			void setCreateAsReservedIfNew() {createAsReservedIfNew = true;}
-			void setPassword(const boost::optional<std::string>& password);
-			
+			virtual void invitePerson(const JID& person, const std::string& reason = "", bool isImpromptu = false, bool isReuseChat = false) = 0;
+			virtual void setCreateAsReservedIfNew() = 0;
+			virtual void setPassword(const boost::optional<std::string>& password) = 0;
+
 		public:
 			boost::signal<void (const std::string& /*nick*/)> onJoinComplete;
 			boost::signal<void (ErrorPayload::ref)> onJoinFailed;
@@ -97,41 +93,6 @@ namespace Swift {
 			boost::signal<void ()> onUnlocked;
 			/* boost::signal<void (const MUCInfo&)> onInfoResult; */
 			/* boost::signal<void (const blah&)> onItemsResult; */
-			
-
-		private:
-			bool isFromMUC(const JID& j) const {
-				return ownMUCJID.equals(j, JID::WithoutResource);
-			}
-
-			const std::string& getOwnNick() const {
-				return ownMUCJID.getResource();
-			}
-
-		private:
-			void handleIncomingPresence(Presence::ref presence);
-			void internalJoin(const std::string& nick);
-			void handleCreationConfigResponse(MUCOwnerPayload::ref, ErrorPayload::ref);
-			void handleOccupantRoleChangeResponse(MUCAdminPayload::ref, ErrorPayload::ref, const JID&, MUCOccupant::Role);
-			void handleAffiliationChangeResponse(MUCAdminPayload::ref, ErrorPayload::ref, const JID&, MUCOccupant::Affiliation);
-			void handleAffiliationListResponse(MUCAdminPayload::ref, ErrorPayload::ref, MUCOccupant::Affiliation);
-			void handleConfigurationFormReceived(MUCOwnerPayload::ref, ErrorPayload::ref);
-			void handleConfigurationResultReceived(MUCOwnerPayload::ref, ErrorPayload::ref);
 
-		private:
-			JID ownMUCJID;
-			StanzaChannel* stanzaChannel;
-			IQRouter* iqRouter_;
-			DirectedPresenceSender* presenceSender;
-			MUCRegistry* mucRegistry;
-			std::map<std::string, MUCOccupant> occupants;
-			bool joinSucceeded_;
-			bool joinComplete_;
-			boost::bsignals::scoped_connection scopedConnection_;
-			boost::posix_time::ptime joinSince_;
-			bool createAsReservedIfNew;
-			bool unlocking;
-			bool isUnlocked_;
-			boost::optional<std::string> password;
 	};
 }
diff --git a/Swiften/MUC/MUCImpl.cpp b/Swiften/MUC/MUCImpl.cpp
new file mode 100644
index 0000000..99789c0
--- /dev/null
+++ b/Swiften/MUC/MUCImpl.cpp
@@ -0,0 +1,434 @@
+/*
+ * Copyright (c) 2010-2013 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swiften/MUC/MUCImpl.h>
+
+#include <boost/bind.hpp>
+#include <boost/shared_ptr.hpp>
+#include <boost/smart_ptr/make_shared.hpp>
+
+#include <Swiften/Base/foreach.h>
+#include <Swiften/Presence/DirectedPresenceSender.h>
+#include <Swiften/Client/StanzaChannel.h>
+#include <Swiften/Queries/IQRouter.h>
+#include <Swiften/Elements/Form.h>
+#include <Swiften/Elements/Message.h>
+#include <Swiften/Elements/IQ.h>
+#include <Swiften/Elements/MUCUserPayload.h>
+#include <Swiften/Elements/MUCAdminPayload.h>
+#include <Swiften/Elements/MUCPayload.h>
+#include <Swiften/Elements/MUCDestroyPayload.h>
+#include <Swiften/Elements/MUCInvitationPayload.h>
+#include <Swiften/MUC/MUCRegistry.h>
+#include <Swiften/Queries/GenericRequest.h>
+
+namespace Swift {
+
+typedef std::pair<std::string, MUCOccupant> StringMUCOccupantPair;
+
+MUCImpl::MUCImpl(StanzaChannel* stanzaChannel, IQRouter* iqRouter, DirectedPresenceSender* presenceSender, const JID &muc, MUCRegistry* mucRegistry) : ownMUCJID(muc), stanzaChannel(stanzaChannel), iqRouter_(iqRouter), presenceSender(presenceSender), mucRegistry(mucRegistry), createAsReservedIfNew(false), unlocking(false), isUnlocked_(false) {
+	scopedConnection_ = stanzaChannel->onPresenceReceived.connect(boost::bind(&MUCImpl::handleIncomingPresence, this, _1));
+}
+
+MUCImpl::~MUCImpl()
+{
+}
+
+//FIXME: discover reserved nickname
+
+/**
+ * Join the MUC with default context.
+ */
+void MUCImpl::joinAs(const std::string &nick) {
+	joinSince_ = boost::posix_time::not_a_date_time;
+	internalJoin(nick);
+}
+
+/**
+ * Set the password used for entering the room.
+ */
+void MUCImpl::setPassword(const boost::optional<std::string>& newPassword) {
+	password = newPassword;
+}
+
+/**
+ * Join the MUC with context since date.
+ */
+void MUCImpl::joinWithContextSince(const std::string &nick, const boost::posix_time::ptime& since) {
+	joinSince_ = since;
+	internalJoin(nick);
+}
+
+std::map<std::string, MUCOccupant> MUCImpl::getOccupants() const {
+	return occupants;
+}
+
+void MUCImpl::internalJoin(const std::string &nick) {
+	//TODO: history request
+	joinComplete_ = false;
+	joinSucceeded_ = false;
+
+	mucRegistry->addMUC(getJID());
+
+	ownMUCJID = JID(ownMUCJID.getNode(), ownMUCJID.getDomain(), nick);
+
+	Presence::ref joinPresence = boost::make_shared<Presence>(*presenceSender->getLastSentUndirectedPresence());
+	assert(joinPresence->getType() == Presence::Available);
+	joinPresence->setTo(ownMUCJID);
+	MUCPayload::ref mucPayload = boost::make_shared<MUCPayload>();
+	if (joinSince_ != boost::posix_time::not_a_date_time) {
+		mucPayload->setSince(joinSince_);
+	}
+	if (password) {
+		mucPayload->setPassword(*password);
+	}
+	joinPresence->addPayload(mucPayload);
+
+	presenceSender->sendPresence(joinPresence);
+}
+
+void MUCImpl::part() {
+	presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence);
+	mucRegistry->removeMUC(getJID());
+}
+
+void MUCImpl::handleUserLeft(LeavingType type) {
+	std::map<std::string,MUCOccupant>::iterator i = occupants.find(ownMUCJID.getResource());
+	if (i != occupants.end()) {
+		MUCOccupant me = i->second;
+		occupants.erase(i);
+		onOccupantLeft(me, type, "");
+	}
+	occupants.clear();
+	joinComplete_ = false;
+	joinSucceeded_ = false;
+	isUnlocked_ = false;
+	presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::DontSendPresence);
+}
+
+void MUCImpl::handleIncomingPresence(Presence::ref presence) {
+	if (!isFromMUC(presence->getFrom())) {
+		return;
+	}
+
+	MUCUserPayload::ref mucPayload;
+	foreach (MUCUserPayload::ref payload, presence->getPayloads<MUCUserPayload>()) {
+		if (!payload->getItems().empty() || !payload->getStatusCodes().empty()) {
+			mucPayload = payload;
+		}
+	}
+	
+	// On the first incoming presence, check if our join has succeeded 
+	// (i.e. we start getting non-error presence from the MUC) or not
+	if (!joinSucceeded_) {
+		if (presence->getType() == Presence::Error) {
+			std::string reason;
+			onJoinFailed(presence->getPayload<ErrorPayload>());
+			return;
+		}
+		else {
+			joinSucceeded_ = true;
+			presenceSender->addDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence);
+		}
+	}
+
+	std::string nick = presence->getFrom().getResource();
+	if (nick.empty()) {
+		return;
+	}
+	MUCOccupant::Role role(MUCOccupant::NoRole);
+	MUCOccupant::Affiliation affiliation(MUCOccupant::NoAffiliation);
+	boost::optional<JID> realJID;
+	if (mucPayload && mucPayload->getItems().size() > 0) {
+		role = mucPayload->getItems()[0].role ? mucPayload->getItems()[0].role.get() : MUCOccupant::NoRole;
+		affiliation = mucPayload->getItems()[0].affiliation ? mucPayload->getItems()[0].affiliation.get() : MUCOccupant::NoAffiliation;
+		realJID = mucPayload->getItems()[0].realJID;
+	}
+
+	//100 is non-anonymous
+	//TODO: 100 may also be specified in a <message/>
+	//170 is room logging to http
+	//TODO: Nick changes
+	if (presence->getType() == Presence::Unavailable) {
+		LeavingType type = LeavePart;
+		if (mucPayload) {
+			if (boost::dynamic_pointer_cast<MUCDestroyPayload>(mucPayload->getPayload())) {
+				type = LeaveDestroy;
+			}
+			else foreach (MUCUserPayload::StatusCode status, mucPayload->getStatusCodes()) {
+				if (status.code == 307) {
+					type = LeaveKick;
+				}
+				else if (status.code == 301) {
+					type = LeaveBan;
+				}
+				else if (status.code == 321) {
+					type = LeaveNotMember;
+				}
+			}
+		}
+
+		if (presence->getFrom() == ownMUCJID) {
+			handleUserLeft(type);
+			return;
+		} 
+		else {
+			std::map<std::string,MUCOccupant>::iterator i = occupants.find(nick);
+			if (i != occupants.end()) {
+				//TODO: part type
+				MUCOccupant occupant = i->second;
+				occupants.erase(i);
+				onOccupantLeft(occupant, type, "");
+			}
+		}
+	} 
+	else if (presence->getType() == Presence::Available) {
+		std::map<std::string, MUCOccupant>::iterator it = occupants.find(nick);
+		MUCOccupant occupant(nick, role, affiliation);
+		bool isJoin = true;
+		if (realJID) {
+			occupant.setRealJID(realJID.get());
+		}
+		if (it != occupants.end()) {
+			isJoin = false;
+			MUCOccupant oldOccupant = it->second;
+			if (oldOccupant.getRole() != role) {
+				onOccupantRoleChanged(nick, occupant, oldOccupant.getRole());
+			}
+			if (oldOccupant.getAffiliation() != affiliation) {
+				onOccupantAffiliationChanged(nick, affiliation, oldOccupant.getAffiliation());
+			}
+			occupants.erase(it);
+		}
+		std::pair<std::map<std::string, MUCOccupant>::iterator, bool> result = occupants.insert(std::make_pair(nick, occupant));
+		if (isJoin) {
+			onOccupantJoined(result.first->second);
+		}
+		onOccupantPresenceChange(presence);
+	}
+	if (mucPayload && !joinComplete_) {
+		bool isLocked = false;
+		foreach (MUCUserPayload::StatusCode status, mucPayload->getStatusCodes()) {
+			if (status.code == 110) {
+				/* Simply knowing this is your presence is enough, 210 doesn't seem to be necessary. */
+				joinComplete_ = true;
+				if (ownMUCJID != presence->getFrom()) {
+					presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::DontSendPresence);
+					ownMUCJID = presence->getFrom();
+					presenceSender->addDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence);
+				}
+				onJoinComplete(getOwnNick());
+			}
+			if (status.code == 201) {
+				isLocked = true;
+				/* Room is created and locked */
+				/* Currently deal with this by making an instant room */
+				if (ownMUCJID != presence->getFrom()) {
+					presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::DontSendPresence);
+					ownMUCJID = presence->getFrom();
+					presenceSender->addDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence);
+				}
+				if (createAsReservedIfNew) {
+					unlocking = true;
+					requestConfigurationForm();
+				}
+				else {
+					MUCOwnerPayload::ref mucPayload(new MUCOwnerPayload());
+					presenceSender->addDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::DontSendPresence);
+					mucPayload->setPayload(boost::make_shared<Form>(Form::SubmitType));
+					GenericRequest<MUCOwnerPayload>* request = new GenericRequest<MUCOwnerPayload>(IQ::Set, getJID(), mucPayload, iqRouter_);
+					request->onResponse.connect(boost::bind(&MUCImpl::handleCreationConfigResponse, this, _1, _2));
+					request->send();
+				}
+			}
+		}
+		if (!isLocked && !isUnlocked_ && (presence->getFrom() == ownMUCJID)) {
+			isUnlocked_ = true;
+			onUnlocked();
+		}
+	}
+}
+
+void MUCImpl::handleCreationConfigResponse(MUCOwnerPayload::ref /*unused*/, ErrorPayload::ref error) {
+	unlocking = false;
+	if (error) {
+		presenceSender->removeDirectedPresenceReceiver(ownMUCJID, DirectedPresenceSender::AndSendPresence);
+		onJoinFailed(error);
+	} else {
+		onJoinComplete(getOwnNick()); /* Previously, this wasn't needed here, as the presence duplication bug caused an emit elsewhere. */
+		isUnlocked_ = true;
+		onUnlocked();
+	}
+}
+
+bool MUCImpl::hasOccupant(const std::string& nick) {
+	return occupants.find(nick) != occupants.end();
+}
+
+const MUCOccupant& MUCImpl::getOccupant(const std::string& nick) {
+	return occupants.find(nick)->second;
+}
+
+void MUCImpl::kickOccupant(const JID& jid) {
+	changeOccupantRole(jid, MUCOccupant::NoRole);
+}
+
+/**
+ * Call with the room JID, not the real JID.
+ */
+void MUCImpl::changeOccupantRole(const JID& jid, MUCOccupant::Role role) {
+	MUCAdminPayload::ref mucPayload = boost::make_shared<MUCAdminPayload>();
+	MUCItem item;
+	item.role = role;
+	item.nick = jid.getResource();
+	mucPayload->addItem(item);
+	GenericRequest<MUCAdminPayload>* request = new GenericRequest<MUCAdminPayload>(IQ::Set, getJID(), mucPayload, iqRouter_);
+	request->onResponse.connect(boost::bind(&MUCImpl::handleOccupantRoleChangeResponse, this, _1, _2, jid, role));
+	request->send();
+	
+}
+
+void MUCImpl::handleOccupantRoleChangeResponse(MUCAdminPayload::ref /*unused*/, ErrorPayload::ref error, const JID& jid, MUCOccupant::Role role) {
+	if (error) {
+		onRoleChangeFailed(error, jid, role);
+	}
+}
+
+void MUCImpl::requestAffiliationList(MUCOccupant::Affiliation affiliation) {
+	MUCAdminPayload::ref mucPayload = boost::make_shared<MUCAdminPayload>();
+	MUCItem item;
+	item.affiliation = affiliation;
+	mucPayload->addItem(item);
+	GenericRequest<MUCAdminPayload>* request = new GenericRequest<MUCAdminPayload>(IQ::Get, getJID(), mucPayload, iqRouter_);
+	request->onResponse.connect(boost::bind(&MUCImpl::handleAffiliationListResponse, this, _1, _2, affiliation));
+	request->send();
+}
+
+/**
+ * Must be called with the real JID, not the room JID.
+ */
+void MUCImpl::changeAffiliation(const JID& jid, MUCOccupant::Affiliation affiliation) {
+	MUCAdminPayload::ref mucPayload = boost::make_shared<MUCAdminPayload>();
+	MUCItem item;
+	item.affiliation = affiliation;
+	item.realJID = jid.toBare();
+	mucPayload->addItem(item);
+	GenericRequest<MUCAdminPayload>* request = new GenericRequest<MUCAdminPayload>(IQ::Set, getJID(), mucPayload, iqRouter_);
+	request->onResponse.connect(boost::bind(&MUCImpl::handleAffiliationChangeResponse, this, _1, _2, jid, affiliation));
+	request->send();
+}
+
+void MUCImpl::handleAffiliationListResponse(MUCAdminPayload::ref payload, ErrorPayload::ref error, MUCOccupant::Affiliation affiliation) {
+	if (error) {
+		onAffiliationListFailed(error);
+	}
+	else {
+		std::vector<JID> jids;
+		foreach (MUCItem item, payload->getItems()) {
+			if (item.realJID) {
+				jids.push_back(*item.realJID);
+			}
+		}
+		onAffiliationListReceived(affiliation, jids);
+	}
+}
+
+void MUCImpl::handleAffiliationChangeResponse(MUCAdminPayload::ref /*unused*/, ErrorPayload::ref error, const JID& jid, MUCOccupant::Affiliation affiliation) {
+	if (error) {
+		onAffiliationChangeFailed(error, jid, affiliation);
+	}
+}
+
+void MUCImpl::changeSubject(const std::string& subject) {
+	Message::ref message = boost::make_shared<Message>();
+	message->setSubject(subject);
+	message->setType(Message::Groupchat);
+	message->setTo(ownMUCJID.toBare());
+	stanzaChannel->sendMessage(message);
+}
+
+void MUCImpl::requestConfigurationForm() {
+	MUCOwnerPayload::ref mucPayload(new MUCOwnerPayload());
+	GenericRequest<MUCOwnerPayload>* request = new GenericRequest<MUCOwnerPayload>(IQ::Get, getJID(), mucPayload, iqRouter_);
+	request->onResponse.connect(boost::bind(&MUCImpl::handleConfigurationFormReceived, this, _1, _2));
+	request->send();
+}
+
+void MUCImpl::cancelConfigureRoom() {
+	MUCOwnerPayload::ref mucPayload(new MUCOwnerPayload());
+	mucPayload->setPayload(boost::make_shared<Form>(Form::CancelType));
+	GenericRequest<MUCOwnerPayload>* request = new GenericRequest<MUCOwnerPayload>(IQ::Set, getJID(), mucPayload, iqRouter_);
+	request->send();
+}
+
+void MUCImpl::handleConfigurationFormReceived(MUCOwnerPayload::ref payload, ErrorPayload::ref error) {
+	Form::ref form;
+	if (payload) {
+		form = payload->getForm();
+	}
+	if (error || !form) {
+		onConfigurationFailed(error);
+	} else {
+		onConfigurationFormReceived(form);
+	}
+}
+
+void MUCImpl::handleConfigurationResultReceived(MUCOwnerPayload::ref /*payload*/, ErrorPayload::ref error) {
+	if (error) {
+		onConfigurationFailed(error);
+	}
+}
+
+void MUCImpl::configureRoom(Form::ref form) {
+	MUCOwnerPayload::ref mucPayload(new MUCOwnerPayload());
+	mucPayload->setPayload(form);
+	GenericRequest<MUCOwnerPayload>* request = new GenericRequest<MUCOwnerPayload>(IQ::Set, getJID(), mucPayload, iqRouter_);
+	if (unlocking) {
+		request->onResponse.connect(boost::bind(&MUCImpl::handleCreationConfigResponse, this, _1, _2));
+	}
+	else {
+		request->onResponse.connect(boost::bind(&MUCImpl::handleConfigurationResultReceived, this, _1, _2));
+	}
+	request->send();
+}
+
+void MUCImpl::destroyRoom() {
+	MUCOwnerPayload::ref mucPayload = boost::make_shared<MUCOwnerPayload>();
+	MUCDestroyPayload::ref mucDestroyPayload = boost::make_shared<MUCDestroyPayload>();
+	mucPayload->setPayload(mucDestroyPayload);
+	GenericRequest<MUCOwnerPayload>* request = new GenericRequest<MUCOwnerPayload>(IQ::Set, getJID(), mucPayload, iqRouter_);
+	request->onResponse.connect(boost::bind(&MUCImpl::handleConfigurationResultReceived, this, _1, _2));
+	request->send();
+}
+
+void MUCImpl::invitePerson(const JID& person, const std::string& reason, bool isImpromptu, bool isReuseChat) {
+	Message::ref message = boost::make_shared<Message>();
+	message->setTo(person);
+	message->setType(Message::Normal);
+	MUCInvitationPayload::ref invite = boost::make_shared<MUCInvitationPayload>();
+	invite->setReason(reason);
+	invite->setJID(ownMUCJID.toBare());
+	invite->setIsImpromptu(isImpromptu);
+	invite->setIsContinuation(isReuseChat);
+	message->addPayload(invite);
+	stanzaChannel->sendMessage(message);
+}
+
+//TODO: Invites(direct/mediated)
+
+//TODO: requesting membership
+
+//TODO: get member list
+
+//TODO: request voice
+
+//TODO: moderator use cases
+
+//TODO: Admin use cases
+
+//TODO: Owner use cases
+
+}
diff --git a/Swiften/MUC/MUCImpl.h b/Swiften/MUC/MUCImpl.h
new file mode 100644
index 0000000..846ddcf
--- /dev/null
+++ b/Swiften/MUC/MUCImpl.h
@@ -0,0 +1,117 @@
+/*
+ * Copyright (c) 2010-2013 Kevin Smith
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/MUC/MUC.h>
+#include <Swiften/JID/JID.h>
+#include <Swiften/Base/API.h>
+#include <string>
+#include <Swiften/Elements/Message.h>
+#include <Swiften/Elements/Presence.h>
+#include <Swiften/Elements/MUCOccupant.h>
+#include <Swiften/MUC/MUCRegistry.h>
+#include <Swiften/Elements/MUCOwnerPayload.h>
+#include <Swiften/Elements/MUCAdminPayload.h>
+#include <Swiften/Elements/Form.h>
+
+#include <boost/shared_ptr.hpp>
+#include <Swiften/Base/boost_bsignals.h>
+#include <boost/signals/connection.hpp>
+
+#include <map>
+
+namespace Swift {
+	class StanzaChannel;
+	class IQRouter;
+	class DirectedPresenceSender;
+
+	class SWIFTEN_API MUCImpl : public MUC {
+		public:
+			typedef boost::shared_ptr<MUCImpl> ref;
+
+		public:
+			MUCImpl(StanzaChannel* stanzaChannel, IQRouter* iqRouter, DirectedPresenceSender* presenceSender, const JID &muc, MUCRegistry* mucRegistry);
+			virtual ~MUCImpl();
+
+			/**
+			 * Returns the (bare) JID of the MUC.
+			 */
+			virtual JID getJID() const {
+				return ownMUCJID.toBare();
+			}
+
+			/**
+			 * Returns if the room is unlocked and other people can join the room.
+			 * @return True if joinable by others; false otherwise.
+			 */
+			virtual bool isUnlocked() const {
+				return isUnlocked_;
+			}
+
+			virtual void joinAs(const std::string &nick);
+			virtual void joinWithContextSince(const std::string &nick, const boost::posix_time::ptime& since);
+			/*virtual void queryRoomInfo(); */
+			/*virtual void queryRoomItems(); */
+			/*virtual std::string getCurrentNick(); */
+			virtual std::map<std::string, MUCOccupant> getOccupants() const;
+			virtual void part();
+			/*virtual void handleIncomingMessage(Message::ref message); */
+			/** Expose public so it can be called when e.g. user goes offline */
+			virtual void handleUserLeft(LeavingType);
+			/** Get occupant information*/
+			virtual const MUCOccupant& getOccupant(const std::string& nick);
+			virtual bool hasOccupant(const std::string& nick);
+			virtual void kickOccupant(const JID& jid);
+			virtual void changeOccupantRole(const JID& jid, MUCOccupant::Role role);
+			virtual void requestAffiliationList(MUCOccupant::Affiliation);
+			virtual void changeAffiliation(const JID& jid, MUCOccupant::Affiliation affiliation);
+			virtual void changeSubject(const std::string& subject);
+			virtual void requestConfigurationForm();
+			virtual void configureRoom(Form::ref);
+			virtual void cancelConfigureRoom();
+			virtual void destroyRoom();
+			/** Send an invite for the person to join the MUC */
+			virtual void invitePerson(const JID& person, const std::string& reason = "", bool isImpromptu = false, bool isReuseChat = false);
+			virtual void setCreateAsReservedIfNew() {createAsReservedIfNew = true;}
+			virtual void setPassword(const boost::optional<std::string>& password);
+
+		private:
+			bool isFromMUC(const JID& j) const {
+				return ownMUCJID.equals(j, JID::WithoutResource);
+			}
+
+			const std::string& getOwnNick() const {
+				return ownMUCJID.getResource();
+			}
+
+		private:
+			void handleIncomingPresence(Presence::ref presence);
+			void internalJoin(const std::string& nick);
+			void handleCreationConfigResponse(MUCOwnerPayload::ref, ErrorPayload::ref);
+			void handleOccupantRoleChangeResponse(MUCAdminPayload::ref, ErrorPayload::ref, const JID&, MUCOccupant::Role);
+			void handleAffiliationChangeResponse(MUCAdminPayload::ref, ErrorPayload::ref, const JID&, MUCOccupant::Affiliation);
+			void handleAffiliationListResponse(MUCAdminPayload::ref, ErrorPayload::ref, MUCOccupant::Affiliation);
+			void handleConfigurationFormReceived(MUCOwnerPayload::ref, ErrorPayload::ref);
+			void handleConfigurationResultReceived(MUCOwnerPayload::ref, ErrorPayload::ref);
+
+		private:
+			JID ownMUCJID;
+			StanzaChannel* stanzaChannel;
+			IQRouter* iqRouter_;
+			DirectedPresenceSender* presenceSender;
+			MUCRegistry* mucRegistry;
+			std::map<std::string, MUCOccupant> occupants;
+			bool joinSucceeded_;
+			bool joinComplete_;
+			boost::bsignals::scoped_connection scopedConnection_;
+			boost::posix_time::ptime joinSince_;
+			bool createAsReservedIfNew;
+			bool unlocking;
+			bool isUnlocked_;
+			boost::optional<std::string> password;
+	};
+}
diff --git a/Swiften/MUC/MUCManager.cpp b/Swiften/MUC/MUCManager.cpp
index 6e9b820..0581829 100644
--- a/Swiften/MUC/MUCManager.cpp
+++ b/Swiften/MUC/MUCManager.cpp
@@ -5,6 +5,7 @@
  */
 
 #include <Swiften/MUC/MUCManager.h>
+#include <Swiften/MUC/MUCImpl.h>
 
 namespace Swift {
 
@@ -12,7 +13,7 @@ MUCManager::MUCManager(StanzaChannel* stanzaChannel, IQRouter* iqRouter, Directe
 }
 
 MUC::ref MUCManager::createMUC(const JID& jid) {
-	return MUC::ref(new MUC(stanzaChannel, iqRouter, presenceSender, jid, mucRegistry));
+	return boost::make_shared<MUCImpl>(stanzaChannel, iqRouter, presenceSender, jid, mucRegistry);
 }
 
 }
diff --git a/Swiften/MUC/UnitTest/MUCTest.cpp b/Swiften/MUC/UnitTest/MUCTest.cpp
index 427e938..d1a21b0 100644
--- a/Swiften/MUC/UnitTest/MUCTest.cpp
+++ b/Swiften/MUC/UnitTest/MUCTest.cpp
@@ -10,7 +10,7 @@
 #include <boost/smart_ptr/make_shared.hpp>
 #include <boost/bind.hpp>
 
-#include <Swiften/MUC/MUC.h>
+#include <Swiften/MUC/MUCImpl.h>
 #include <Swiften/Client/DummyStanzaChannel.h>
 #include <Swiften/Presence/StanzaChannelPresenceSender.h>
 #include <Swiften/Presence/DirectedPresenceSender.h>
@@ -158,7 +158,7 @@ class MUCTest : public CppUnit::TestFixture {
 
 	private:
 		MUC::ref createMUC(const JID& jid) {
-			return boost::make_shared<MUC>(channel, router, presenceSender, jid, mucRegistry);
+			return boost::make_shared<MUCImpl>(channel, router, presenceSender, jid, mucRegistry);
 		}
 
 		void handleJoinFinished(const std::string& nick, ErrorPayload::ref error) {
diff --git a/Swiften/MUC/UnitTest/MockMUC.cpp b/Swiften/MUC/UnitTest/MockMUC.cpp
new file mode 100644
index 0000000..9ca35ec
--- /dev/null
+++ b/Swiften/MUC/UnitTest/MockMUC.cpp
@@ -0,0 +1,51 @@
+/*
+ * Copyright (c) 2013 Kevin Smith and Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#include <Swiften/MUC/UnitTest/MockMUC.h>
+
+namespace Swift {
+
+MockMUC::MockMUC(const JID &muc)
+: ownMUCJID(muc)
+{
+}
+
+MockMUC::~MockMUC() {
+}
+
+void MockMUC::insertOccupant(const MUCOccupant& occupant)
+{
+	occupants_.insert(std::make_pair(occupant.getNick(), occupant));
+	onOccupantJoined(occupant);
+}
+
+const MUCOccupant& MockMUC::getOccupant(const std::string& nick) {
+	return occupants_.find(nick)->second;
+}
+
+bool MockMUC::hasOccupant(const std::string& nick) {
+	return occupants_.find(nick) != occupants_.end();
+}
+
+void MockMUC::changeAffiliation(const JID &jid, MUCOccupant::Affiliation newAffilation) {
+	std::map<std::string, MUCOccupant>::iterator i = occupants_.find(jid.getResource());
+	if (i != occupants_.end()) {
+		const MUCOccupant old = i->second;
+		i->second = MUCOccupant(old.getNick(), old.getRole(), newAffilation);
+		onOccupantAffiliationChanged(i->first, newAffilation, old.getAffiliation());
+	}
+}
+
+void MockMUC::changeOccupantRole(const JID &jid, MUCOccupant::Role newRole) {
+	std::map<std::string, MUCOccupant>::iterator i = occupants_.find(jid.getResource());
+	if (i != occupants_.end()) {
+		const MUCOccupant old = i->second;
+		i->second = MUCOccupant(old.getNick(), newRole, old.getAffiliation());
+		onOccupantRoleChanged(i->first, i->second, old.getRole());
+	}
+}
+
+}
diff --git a/Swiften/MUC/UnitTest/MockMUC.h b/Swiften/MUC/UnitTest/MockMUC.h
new file mode 100644
index 0000000..78c2fb5
--- /dev/null
+++ b/Swiften/MUC/UnitTest/MockMUC.h
@@ -0,0 +1,95 @@
+/*
+ * Copyright (c) 2013 Kevin Smith and Remko Tronçon
+ * Licensed under the GNU General Public License v3.
+ * See Documentation/Licenses/GPLv3.txt for more information.
+ */
+
+#pragma once
+
+#include <Swiften/MUC/MUC.h>
+#include <Swiften/MUC/MUCRegistry.h>
+#include <Swiften/JID/JID.h>
+#include <Swiften/Elements/Message.h>
+#include <Swiften/Elements/Presence.h>
+#include <Swiften/Elements/MUCOccupant.h>
+#include <Swiften/Elements/MUCOwnerPayload.h>
+#include <Swiften/Elements/MUCAdminPayload.h>
+#include <Swiften/Elements/Form.h>
+#include <Swiften/Base/API.h>
+#include <Swiften/Base/boost_bsignals.h>
+#include <boost/signals/connection.hpp>
+#include <boost/shared_ptr.hpp>
+#include <string>
+#include <map>
+
+namespace Swift {
+	class StanzaChannel;
+	class IQRouter;
+	class DirectedPresenceSender;
+
+	class SWIFTEN_API MockMUC : public MUC{
+		public:
+			typedef boost::shared_ptr<MockMUC> ref;
+
+		public:
+			MockMUC(const JID &muc);
+			virtual ~MockMUC();
+
+			/**
+			 * Cause a user to appear to have entered the room. For testing only.
+			 */
+			void insertOccupant(const MUCOccupant& occupant);
+
+			/**
+			 * Returns the (bare) JID of the MUC.
+			 */
+			virtual JID getJID() const {
+				return ownMUCJID.toBare();
+			}
+			/**
+			 * Returns if the room is unlocked and other people can join the room.
+			 * @return True if joinable by others; false otherwise.
+			 */
+			virtual bool isUnlocked() const { return true; }
+
+			virtual void joinAs(const std::string&) {}
+			virtual void joinWithContextSince(const std::string&, const boost::posix_time::ptime&) {}
+			/*virtual void queryRoomInfo(); */
+			/*virtual void queryRoomItems(); */
+			/*virtual std::string getCurrentNick() = 0; */
+			virtual std::map<std::string, MUCOccupant> getOccupants() const { return occupants_; }
+			virtual void part() {}
+			/*virtual void handleIncomingMessage(Message::ref message) = 0; */
+			/** Expose public so it can be called when e.g. user goes offline */
+			virtual void handleUserLeft(LeavingType) {}
+			/** Get occupant information*/
+			virtual const MUCOccupant& getOccupant(const std::string&);
+			virtual bool hasOccupant(const std::string&);
+			virtual void kickOccupant(const JID&) {}
+			virtual void changeOccupantRole(const JID&, MUCOccupant::Role);
+			virtual void requestAffiliationList(MUCOccupant::Affiliation) {}
+			virtual void changeAffiliation(const JID&, MUCOccupant::Affiliation);
+			virtual void changeSubject(const std::string&) {}
+			virtual void requestConfigurationForm() {}
+			virtual void configureRoom(Form::ref) {}
+			virtual void cancelConfigureRoom() {}
+			virtual void destroyRoom() {}
+			/** Send an invite for the person to join the MUC */
+			virtual void invitePerson(const JID&, const std::string&, bool, bool) {}
+			virtual void setCreateAsReservedIfNew() {}
+			virtual void setPassword(const boost::optional<std::string>&) {}
+
+		protected:
+			virtual bool isFromMUC(const JID& j) const {
+				return ownMUCJID.equals(j, JID::WithoutResource);
+			}
+
+			virtual const std::string& getOwnNick() const {
+				return ownMUCJID.getResource();
+			}
+
+		private:
+			JID ownMUCJID;
+			std::map<std::string, MUCOccupant> occupants_;
+	};
+}
diff --git a/Swiften/SConscript b/Swiften/SConscript
index a8daac5..75944f0 100644
--- a/Swiften/SConscript
+++ b/Swiften/SConscript
@@ -142,6 +142,7 @@ if env["SCONS_STAGE"] == "build" :
 			"Entity/Entity.cpp",
 			"Entity/PayloadPersister.cpp",
 			"MUC/MUC.cpp",
+			"MUC/MUCImpl.cpp",
 			"MUC/MUCManager.cpp",
 			"MUC/MUCRegistry.cpp",
 			"MUC/MUCBookmarkManager.cpp",
@@ -365,6 +366,7 @@ if env["SCONS_STAGE"] == "build" :
 			File("LinkLocal/UnitTest/LinkLocalServiceInfoTest.cpp"),
 			File("LinkLocal/UnitTest/LinkLocalServiceTest.cpp"),
 			File("MUC/UnitTest/MUCTest.cpp"),
+			File("MUC/UnitTest/MockMUC.cpp"),
 			File("Network/UnitTest/HostAddressTest.cpp"),
 			File("Network/UnitTest/ConnectorTest.cpp"),
 			File("Network/UnitTest/ChainedConnectorTest.cpp"),
-- 
cgit v0.10.2-6-g49f6