/*
 * Jitsi, the OpenSource Java VoIP and Instant Messaging client.
 *
 * Copyright @ 2015 Atlassian Pty Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package net.java.sip.communicator.slick.protocol.jabber;

import java.util.*;

import junit.framework.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.util.*;

import org.jxmpp.jid.impl.*;
import org.jxmpp.stringprep.*;
import org.jxmpp.util.*;

/**
 * Tests multi user chat functionalities.
 *
 * @author Symphorien Wanko
 * @author Valentin Martinet
 */
public class TestOperationSetMultiUserChat2
    extends TestCase
{

    /**
     * logger for <tt>TestOperationSetMultiUserChat2</tt> class
     */
    private static final Logger logger =
        Logger.getLogger(TestOperationSetMultiUserChat2.class);

    /**
     * Room name for each test will be testRoomBaseName + roomID
     */
    private static String testRoomBaseName = "lmuctestroom";

    private static int roomID = 0;

    /**
     * Provides constants and some utilities method.
     */
    private final JabberSlickFixture fixture = new JabberSlickFixture();

    private OperationSetMultiUserChat opSetMUC1;
    private OperationSetMultiUserChat opSetMUC2;
    private OperationSetMultiUserChat opSetMUC3;

    private OperationSetPresence opSetPresence1;
    private OperationSetPresence opSetPresence2;
    private OperationSetPresence opSetPresence3;

    /**
     * Initializes the test with the specified <tt>name</tt>.
     *
     * @param name the name of the test to initialize.
     */
    public TestOperationSetMultiUserChat2(String name)
    {
        super(name);
    }

    /**
     * JUnit setup method.
     * @throws Exception in case anything goes wrong.
     */
    @Override
    protected void setUp() throws Exception
    {
        super.setUp();
        fixture.setUp();

        Map<String, OperationSet> supportedOperationSets1 =
            fixture.provider1.getSupportedOperationSets();

        if (supportedOperationSets1 == null
            || (supportedOperationSets1.size() < 1))
        {
            throw new NullPointerException(
                "No OperationSet implementations are supported by " +
                "this implementation. ");
        }

        //get the operation set presence here.
        opSetMUC1 = (OperationSetMultiUserChat)
            supportedOperationSets1.get(
            OperationSetMultiUserChat.class.getName());

        if (opSetMUC1 == null)
        {
            throw new NullPointerException(
                "No implementation for MUC was found");
        }

        //we also need the presence op set in order to retrieve contacts.
        opSetPresence1 = (OperationSetPresence)
            supportedOperationSets1.get(OperationSetPresence.class.getName());

        //if the op set is null show that we're not happy.
        if (opSetPresence1 == null)
        {
            throw new NullPointerException(
                "An implementation of the service must provide an " +
                "implementation of at least one of the PresenceOperationSets");
        }

        Map<String, OperationSet> supportedOperationSets2 =
            fixture.provider2.getSupportedOperationSets();

        if (supportedOperationSets2 == null
            || (supportedOperationSets2.size() < 1))
        {
            throw new NullPointerException(
                "No OperationSet implementations are supported by " +
                "this implementation. ");
        }

        opSetMUC2 = (OperationSetMultiUserChat)
            supportedOperationSets2.get(
            OperationSetMultiUserChat.class.getName());

        if (opSetMUC2 == null)
        {
            throw new NullPointerException(
                "No implementation for MUC was found");
        }

        opSetPresence2 = (OperationSetPresence)
            supportedOperationSets2.get(OperationSetPresence.class.getName());

        //if the op set is null show that we're not happy.
        if (opSetPresence2 == null)
        {
            throw new NullPointerException(
                "An implementation of the service must provide an " +
                "implementation of at least one of the PresenceOperationSets");
        }

        Map<String, OperationSet> supportedOperationSets3 =
            fixture.provider3.getSupportedOperationSets();

        if (supportedOperationSets3 == null
            || (supportedOperationSets3.size() < 1))
        {
            throw new NullPointerException(
                "No OperationSet implementations are supported by " +
                "this implementation. ");
        }

        opSetMUC3 = (OperationSetMultiUserChat)
            supportedOperationSets3.get(
            OperationSetMultiUserChat.class.getName());

        if (opSetMUC3 == null)
        {
            throw new NullPointerException(
                "No implementation for MUC was found");
        }

        opSetPresence3 = (OperationSetPresence)
            supportedOperationSets3.get(OperationSetPresence.class.getName());

        //if the op set is null show that we're not happy.
        if (opSetPresence3 == null)
        {
            throw new NullPointerException(
                "An implementation of the service must provide an " +
                "implementation of at least one of the PresenceOperationSets");
        }
    }

    /**
     * JUnit teardown method.
     * @throws Exception in case anything goes wrong.
     */
    @Override
    protected void tearDown() throws Exception
    {
        fixture.tearDown();
        super.tearDown();
    }

    /**
     * Create the list to be sure that contacts exchanging messages
     * exists in each other lists
     * @throws Exception
     */
    public void prepareContactList()
        throws Exception
    {
        fixture.clearProvidersLists();

        final Object o = new Object();
        synchronized (o)
        {
            o.wait(2000);
        }

        try
        {
            opSetPresence1.subscribe(fixture.userID2);
        }
        catch (OperationFailedException ex)
        {
            // the contact already exist its OK
        }

        try
        {
            opSetPresence1.subscribe(fixture.userID3);
        }
        catch (OperationFailedException ex)
        {
         // the contact already exist its OK
        }

        try
        {
            opSetPresence2.subscribe(fixture.userID1);
        }
        catch (OperationFailedException ex1)
        {
            // the contact already exist its OK
        }

        try
        {
            opSetPresence2.subscribe(fixture.userID3);
        }
        catch (OperationFailedException ex1)
        {
            // the contact already exist its OK
        }

        try
        {
            opSetPresence3.subscribe(fixture.userID1);
        }
        catch (OperationFailedException ex1)
        {
            // the contact already exist its OK
        }

        try
        {
            opSetPresence3.subscribe(fixture.userID2);
        }
        catch (OperationFailedException ex1)
        {
            // the contact already exist its OK
        }

        logger.info("will wait till the list prepare is completed");
        synchronized (o)
        {
            o.wait(4000);
        }
    }

    /**
     * utiliy method to know if a given nickname is on a
     * <tt>ChatRoomMember</tt> list
     *
     * @param name the name we are looking for
     * @param memberList the list where we search
     *
     * @return true if the name is found, false otherwise
     */
    private boolean nameIsOnMemberList(
        String name, List<ChatRoomMember> memberList)
    {
        for (ChatRoomMember member : memberList)
        {
            if (member.getName().equals(name))
            {
                return true;
            }
        }

        return false;
    }

    /**
     *
     * @return Test a testsuite containing all tests to execute.
     */
    public static Test suite()
    {
        TestSuite suite = new TestSuite();

        suite.addTest(
            new TestOperationSetMultiUserChat2("testCreateChatRoom"));

        suite.addTest(
            new TestOperationSetMultiUserChat2("testJoinRoom"));

        suite.addTest(
            new TestOperationSetMultiUserChat2("testGetJoinedChatRoom"));

        suite.addTest(
            new TestOperationSetMultiUserChat2("testFindRoom"));

        suite.addTest(
            new TestOperationSetMultiUserChat2("testInviteReject"));

        suite.addTest(
            new TestOperationSetMultiUserChat2("testInviteJoin"));

        suite.addTest(
            new TestOperationSetMultiUserChat2("testLeave"));

        //@todo the following two must be tested, they regulary fail, so we are
        //disabling them until fixed.
//        suite.addTest(
//            new TestOperationSetMultiUserChat2("testNickName"));
//
//        suite.addTest(
//            new TestOperationSetMultiUserChat2("testRoomSubject"));
//
//        suite.addTest(
//            new TestOperationSetMultiUserChat2("testConferenceChat"));

        suite.addTest(
            new TestOperationSetMultiUserChat2("testMemberBan"));

        suite.addTest(
            new TestOperationSetMultiUserChat2("testMemberKick"));

        /*
         * Unit tests who belong to users role functionalities are orderly added
         * below. They are added in a specific order which match a predefined
         * scenario. It's not recommended to change this order, some tests may
         * then fail.
         *
         * Please note:
         * - when the room is created, participants are considered (by default)
         *   as members (not guests).
         * - in this test suite, the room will use three participants.
         *
         * Scenario:
         * - testInitialParticipantsRoles: verify participants role in the newly
         *   created room.
         * - testRevokeMembership: administrator/owner of the room revokes
         *   membership to a participant of the room.
         * - testGrantMembership: administrator/owner of the room grants
         *   membership to the participant of the room who just loses it in the
         *   previous test.
         * - testGrantModerator: administrator/owner of the room grants
         *   moderator rights to a participant of the room.
         * - testRevokeVoice: the new moderator of the room (previous test) will
         *   revoke voice right to the remaining guest, who will become a silent
         *   member (the room currently includes the administrator/owner, the
         *   moderator from the previous test, and it remains a participant).
         * - testGrantVoice: moderator will grant voice the user he previously
         *   revokes voice right.
         * - testRevokeModerator: moderator of the room will have his rights
         *   revoked by the owner/admin of the room.
         * - testGrantAdmin: owner/admin of the room grants administrator rights
         *   to the revoked-moderator (previous test).
         * - testGrantOwnership: owner/admin of the room grants the new admin as
         *   co-owner of the room.
         * - testRevokeOwnership: owner/admin of the room revokes ownership to
         *   his co-owner (granted in previous test).
         * - testRevokeAdmin: after having been ownership-revoked, the
         *   considered member is supposed to be an administrator: here his
         *   administrative rights are revoked by owner/admin of the room.
         *
         */
        suite.addTest(
            new TestOperationSetMultiUserChat2("testInitialParticipantsRoles"));

        //suite.addTest(
        //    new TestOperationSetMultiUserChat2("testRevokeMembership"));

        //suite.addTest(
        //    new TestOperationSetMultiUserChat2("testGrantMembership"));

        suite.addTest(new TestOperationSetMultiUserChat2("testGrantModerator"));

        suite.addTest(new TestOperationSetMultiUserChat2("testRevokeVoice"));

        suite.addTest(new TestOperationSetMultiUserChat2("testGrantVoice"));

        suite.addTest(
            new TestOperationSetMultiUserChat2("testRevokeModerator"));

//        suite.addTest(new TestOperationSetMultiUserChat2("testGrantAdmin"));
//
//        suite.addTest(new TestOperationSetMultiUserChat2("testGrantOwnership"));
//
//        suite.addTest(
//            new TestOperationSetMultiUserChat2("testRevokeOwnership"));
//
//        suite.addTest(new TestOperationSetMultiUserChat2("testRevokeAdmin"));

        return suite;
    }

    /**
     * <tt>testCreateChatRoom</tt> creates a room using
     * <tt>OperationSetMultiUserChat#createRoom()</tt>.
     * We then look if the room is on the list returned by
     * <tt>OperationSetMultiUserChat#getExistingChatRooms()</tt>
     *
     * @throws OperationNotSupportedException
     * @throws OperationFailedException
     */
    public void testCreateChatRoom()
        throws OperationNotSupportedException,
               OperationFailedException
    {
        String testRoomName = testRoomBaseName + roomID++;

        // we create the test room
        ChatRoom opSet1Room =
            opSetMUC1.createChatRoom(testRoomName, null);

        assertNotNull("createChatRoom returned null", opSet1Room);

        /*
        Room listing has been disabled on testing server,
        so skip checks
        // and check if it exists on the server
        List<String> existingRooms = opSetMUC1.getExistingChatRooms();

        for (String roomName : existingRooms)
        {
            if (roomName.equals(opSet1Room.getName()))
            {
                return; // ok the created room is listed on server
            }
        }
        fail("the new created room is not listed on server");
        */
    }

    /**
     * <tt>testJoinRoom</tt> looks if <tt>someRoom.isJoined</tt> will returns
     * true for an user after he joins a room with <tt>someRoom.join()</tt>
     *
     * @throws OperationFailedException
     * @throws OperationNotSupportedException
     */
    public void testJoinRoom()
        throws OperationFailedException,
               OperationNotSupportedException
    {
        String testRoomName = testRoomBaseName + roomID++;

        MUCEventCollector opSet1Collector =
            new MUCEventCollector(opSetMUC1, MUCEventCollector.EVENT_PRESENCE);

        // we create the test room
        ChatRoom opSet1Room =
            opSetMUC1.createChatRoom(testRoomName, null);

        opSet1Room.join();

        opSet1Collector.waitForEvent(10000);

        assertEquals("user1 didn't get an event since he joined"
            , 1, opSet1Collector.collectedEvents.size());

        LocalUserChatRoomPresenceChangeEvent changeEvent =
            (LocalUserChatRoomPresenceChangeEvent)
            opSet1Collector.collectedEvents.get(0);

        assertEquals("the event user1 received after he joined is no " +
            "LOCAL_USER_JOINED"
            , LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_JOINED
            , changeEvent.getEventType());

        assertTrue(
            "we are not in the room we just joined", opSet1Room.isJoined());

        assertTrue("user is not listed in the room with his nickname",
            nameIsOnMemberList(
            opSet1Room.getUserNickname(), opSet1Room.getMembers()));
    }

    /**
     * After a user joins a room, we look if the room is present on the list
     * returned by
     * <tt>OperationSetMultiUserChat#getCurrentlyJoinedChatRooms</tt>.
     *
     */
    public void testGetJoinedChatRoom()
        throws OperationFailedException,
               OperationNotSupportedException
    {
        String testRoomName = testRoomBaseName + roomID++;

        // we create the test room
        ChatRoom opSet1Room =
            opSetMUC1.createChatRoom(testRoomName, null);

        opSet1Room.join();

        assertTrue(
            "we are not in the room we just joined", opSet1Room.isJoined());

        // is the joined room list up to date ?
        List<ChatRoom> joinedRooms = opSetMUC1.getCurrentlyJoinedChatRooms();

        for (ChatRoom room : joinedRooms)
        {
            if (room.getName().equals(opSet1Room.getName()))
            {
                return; // ok opSet1Room is on the joined rooms list
            }
        }
        fail("the joined room list does not contains a joined room");
    }

    /**
     * <tt>testFindRoom</tt> will looks for an unexisting room and an existing
     * one to see if results are what we expect.
     */
    public void testFindRoom()
        throws OperationFailedException,
               OperationNotSupportedException
    {
        String testRoomName = testRoomBaseName + roomID++;

        // we create the test room
        ChatRoom opSet1Room =
            opSetMUC1.createChatRoom(testRoomName, null);
        opSet1Room.join();

        ChatRoom foundRoom = null;
        /*
         //findRoom always returns ChatRoom instance. If it doesn't exists in
         //the cache it creates an instance of ChatRoom.

        try
        {
            foundRoom = opSetMUC1.findRoom("WhoCreatedThatRoom");
        }
        catch (Exception ex)
        {
            //ok, no one created a room named "WhoCreatedThatRoom"
        }
        assertNull("wasnt expecting to find the room named " +
            "'WhoCreatedThatRoom' on server", foundRoom);
*/
        // to find the existing room created with opSetMUC1,
        // we will use opSetMUC2 to be sure the room will not be retrieved from
        // opSetMUC1 cache
        try
        {
            foundRoom = opSetMUC2.findRoom(testRoomName);
        }
        catch (Exception ex)
        {
            logger.warn(ex);
        }
        assertNotNull("failed to find an existing room on server", foundRoom);

        assertEquals(
            "the room found is not exactly the one we were looking for"
            , opSet1Room.getName(), foundRoom.getName());
    }

    /**
     * user1 send to user2 an invitation which will be rejected.
     */
    public void testInviteReject()
        throws OperationFailedException,
               OperationNotSupportedException, XmppStringprepException
    {
        MUCEventCollector opSet1Collector =
            new MUCEventCollector(opSetMUC1, MUCEventCollector.EVENT_INVITE);
        MUCEventCollector opSet2Collector =
            new MUCEventCollector(opSetMUC2, MUCEventCollector.EVENT_INVITE);

        String testRoomName = testRoomBaseName + roomID++;

        // we create the test room
        ChatRoom opSet1Room =
            opSetMUC1.createChatRoom(testRoomName, null);
        opSet1Room.join();

        // an invitation user2 must reject
        opSet1Room.invite(fixture.userID2, "testInviteReject");

        opSet2Collector.waitForEvent(10000);

        //now we look if user2 received an event
        assertEquals(
            "The invitation sent from user1 to user2 has not been received "
            , 1, opSet2Collector.collectedEvents.size());

        // does the invitation comes from user1 with the correct reason
        ChatRoomInvitationReceivedEvent invitationReceivedEvent =
            (ChatRoomInvitationReceivedEvent)
            opSet2Collector.collectedEvents.get(0);

        ChatRoomInvitation invitation = invitationReceivedEvent.getInvitation();

        assertEquals("The inviter is not the expected user",
            JidCreate.bareFrom(fixture.userID1),
            JidCreate.bareFrom(invitation.getInviter()));

        assertEquals("The invitation reason received differs from the one sent"
            , "testInviteReject", invitation.getReason());

        //user2 reject the invitation as planned
        opSetMUC2.rejectInvitation(invitation, invitation.getReason());

        opSet1Collector.waitForEvent(10000);

        //is user1 aware his invitation has been declined
        assertEquals(
            "no response received from the invitation sent to user 2"
            , 1, opSet1Collector.collectedEvents.size());

        //who declined the invitation and why
        ChatRoomInvitationRejectedEvent invitationRejectedEvent =
            (ChatRoomInvitationRejectedEvent)
            opSet1Collector.collectedEvents.get(0);

        assertEquals("the invitation has been declined  by an unexpected user",
            JidCreate.bareFrom(fixture.userID2),
            JidCreate.bareFrom(invitationRejectedEvent.getInvitee()));

        assertEquals("the invitation is not declined for the expected reason",
            "testInviteReject", invitationRejectedEvent.getReason());
    }

    /**
     * <tt>testInviteJoin</tt> reproduces the following scenario : user1
     * invite user2. user2 retrieves the room where he is invited then,
     * he joins it.
     */
    public void testInviteJoin()
        throws OperationFailedException,
               OperationNotSupportedException, XmppStringprepException
    {
        String testRoomName = testRoomBaseName + roomID++;

        // we create the test room
        ChatRoom opSet1Room =
            opSetMUC1.createChatRoom(testRoomName, null);
        opSet1Room.join();

        MUCEventCollector opSet1RoomCollector =
            new MUCEventCollector(
            opSet1Room, MUCEventCollector.EVENT_PRESENCE);

        MUCEventCollector opSet2Collector =
            new MUCEventCollector(opSetMUC2, MUCEventCollector.EVENT_INVITE);

        opSet1Room.invite(fixture.userID2, "testInviteAccept");

        opSet2Collector.waitForEvent(10000);

        //make sure the invitation has been received
        assertEquals("the invitation has not been received"
            , 1, opSet2Collector.collectedEvents.size());

        // does the invitation comes from user1 with the correct reason
        ChatRoomInvitationReceivedEvent invitationReceivedEvent =
            (ChatRoomInvitationReceivedEvent)
            opSet2Collector.collectedEvents.get(0);

        ChatRoomInvitation invitation = invitationReceivedEvent.getInvitation();

        assertEquals("The inviter is not the expected user",
            JidCreate.bareFrom(fixture.userID1),
            JidCreate.bareFrom(invitation.getInviter()));

        assertEquals("The invitation reason received differs from the one sent"
            , "testInviteAccept", invitation.getReason());

        //user accept the invitation
        ChatRoom opSet2Room = invitation.getTargetChatRoom();

        MUCEventCollector opSet2RoomCollector =
            new MUCEventCollector(opSet2Room, MUCEventCollector.EVENT_PRESENCE);

        opSet2Collector =
            new MUCEventCollector(opSetMUC2, MUCEventCollector.EVENT_PRESENCE);

        opSet2Room.join();

        opSet2Collector.waitForEvent(10000);      // listening for user2 own join
        opSet2RoomCollector.waitForEvent(10000);  // listening for user1 join
        opSet1RoomCollector.waitForEvent(10000);  // listening for user2 join

        // we know check if both member received events
        assertEquals("a room member has not been notified that someone " +
            "joined the room"
            , 1, opSet1RoomCollector.collectedEvents.size());

        assertEquals("a room member has not been notified that someone " +
            "joined the room"
            , 1, opSet2RoomCollector.collectedEvents.size());

        ChatRoomMemberPresenceChangeEvent memberEvent =
            (ChatRoomMemberPresenceChangeEvent)
            opSet1RoomCollector.collectedEvents.get(0);

        assertEquals("user received an event of the wrong type "
            , ChatRoomMemberPresenceChangeEvent.MEMBER_JOINED
            , memberEvent.getEventType());

        assertEquals("the user who joined is not the one who was invited"
            , fixture.userID2
            , memberEvent.getChatRoomMember().getContactAddress());

        assertEquals("there is not exactly two members in the room"
            , 2, opSet1Room.getMembersCount());

        // is user2 notified of its own join
        assertEquals("user hasn't been notified of its own join"
            , 1, opSet2Collector.collectedEvents.size());

        LocalUserChatRoomPresenceChangeEvent localEvent =
            (LocalUserChatRoomPresenceChangeEvent)
            opSet2Collector.collectedEvents.get(0);

        assertEquals("the event user2 received is not LOCAL_USER_JOINED"
            , LocalUserChatRoomPresenceChangeEvent.LOCAL_USER_JOINED
            , localEvent.getEventType());

        // both sides should report the same members number.
        // here we use opSet2Room.getMembers().size() rather than
        // opSet2Room.getMembersCount() because the last method will not
        // be always accurate for a newly joined jabber room (stated in
        // the smack lib)
        assertEquals("the same room reports different members count " +
            "from one side to other"
            , opSet1Room.getMembers().size()
            , opSet2Room.getMembers().size());

        //user2 must be on room members list now
        assertTrue("user is not listed in the room with his nickname",
            nameIsOnMemberList(
            opSet2Room.getUserNickname(), opSet2Room.getMembers()));

        //and he must be listed on both sides
        assertTrue("user is not listed in the room with his nickname ,from " +
            "peer side",
            nameIsOnMemberList(
            opSet2Room.getUserNickname(), opSet1Room.getMembers()));
    }

    /**
     * In <tt>testJoinLeave</tt>, we join a room then leave it and see if the
     * reported <tt>ChatRoom</tt> members are reflecting our actions.
     */
    public void testLeave()
        throws OperationFailedException,
               OperationNotSupportedException
    {
        String testRoomName = testRoomBaseName + roomID++;

        // we create the test room
        ChatRoom opSet1Room =
            opSetMUC1.createChatRoom(testRoomName, null);
        opSet1Room.join();

        ChatRoom opSet2Room = opSetMUC2.findRoom(testRoomName);

        // first be sure to be outside the room
        if (opSet2Room.isJoined())
            opSet2Room.leave();

        assertFalse("user is in a room he left or didnt joined"
            , opSet2Room.isJoined());

        MUCEventCollector opSet1RoomCollector =
            new MUCEventCollector(opSet1Room, MUCEventCollector.EVENT_PRESENCE);

        MUCEventCollector opSet2RoomCollector =
            new MUCEventCollector(opSet2Room, MUCEventCollector.EVENT_PRESENCE);

        opSet2Room.join();

        opSet2RoomCollector.waitForEvent(10000);
        opSet1RoomCollector.waitForEvent(10000);

        // does users received events since a member joined
        assertEquals("a room member has not been notified that someone else " +
            "joined the room"
            , 1, opSet1RoomCollector.collectedEvents.size());

        assertTrue("user failed to join a room", opSet2Room.isJoined());

        // checking if the user is listed with the right nickname
        assertTrue("user nickname not found in the room after join "
            , nameIsOnMemberList(
            opSet2Room.getUserNickname(), opSet2Room.getMembers()));

        //and we check on the other side
        assertTrue("user nickname not found in the room after join, " +
            "from peer side"
            , nameIsOnMemberList(
            opSet2Room.getUserNickname(), opSet1Room.getMembers()));

        opSet1RoomCollector.collectedEvents.clear();

        // leaving
        opSet2Room.leave();

        opSet1RoomCollector.waitForEvent(10000);

        assertFalse("an user is reported present in a room he left"
            , opSet2Room.isJoined());

        // the nickname shouldnt be listed anymore
        assertFalse("user nickname is still on the room list after he left"
            , nameIsOnMemberList(opSet2Room.getUserNickname()
            , opSet2Room.getMembers()));

        // and the other side

        // does user1 received notifcation since user2 left
        assertEquals("an user joined and left and a room member missed events "
            , 1, opSet1RoomCollector.collectedEvents.size());

        ChatRoomMemberPresenceChangeEvent memberEvent =
            (ChatRoomMemberPresenceChangeEvent)
            opSet1RoomCollector.collectedEvents.get(0);

        assertEquals("user received an event of the wrong type "
            , ChatRoomMemberPresenceChangeEvent.MEMBER_LEFT
            , memberEvent.getEventType());

        assertEquals("the user who left is not the expected one"
            , fixture.userID2
            , memberEvent.getChatRoomMember().getContactAddress());

        assertFalse("user nickname is still on the room list after he left, " +
            "from the peer side"
            , nameIsOnMemberList(opSet2Room.getUserNickname()
            , opSet1Room.getMembers()));

    }

    /**
     * In <tt>testNickName</tt>, user nicknames will be changed
     * and we will check if changes are well reflected on both user
     * sides.
     */
    public void testNickName()
        throws OperationFailedException,
               OperationNotSupportedException
    {
        String testRoomName = testRoomBaseName + roomID++;

        // we create the test room
        ChatRoom opSet1Room =
            opSetMUC1.createChatRoom(testRoomName, null);
        opSet1Room.join();

        String user1FirstNick = opSet1Room.getUserNickname();
        String user1SecondNick = "user1nickchange";

        ChatRoom opSet2Room = opSetMUC2.findRoom(testRoomName);
        opSet2Room.join();

        // checking the nickname from the peer side
        assertTrue("user nickname not found by peer "
            , nameIsOnMemberList(user1FirstNick, opSet2Room.getMembers()));

        MUCEventCollector opSet2RoomCollector =
            new MUCEventCollector(opSet2Room, MUCEventCollector.EVENT_PROPERTY);

        // change the nickname
        opSet1Room.setUserNickname(user1SecondNick);

        assertEquals("failed to change user nickname"
            , user1SecondNick, opSet1Room.getUserNickname());

        //user should be listed with his new nick name
        assertTrue(
            "user nickname not found on member list after modification"
            , nameIsOnMemberList(user1SecondNick, opSet1Room.getMembers()));

        // user2 have to wait for the event
        opSet2RoomCollector.waitForEvent(10000);

        assertEquals("no event received since a member changed his nick"
            , 1, opSet2RoomCollector.collectedEvents.size());

        ChatRoomMemberPropertyChangeEvent changeEvent =
            (ChatRoomMemberPropertyChangeEvent)
            opSet2RoomCollector.collectedEvents.get(0);

        assertEquals("the change event doesnt comes from the expected member"
            , fixture.userID1
            , changeEvent.getSourceChatRoomMember().getContactAddress());

        assertTrue("user nickname not found on member list after modification, " +
            "from peer side",
            nameIsOnMemberList(user1SecondNick, opSet2Room.getMembers()));

        //previous nickname should not rest on the list after change
        assertFalse(
            "both old and new nick are listed in a room after nick change"
            , nameIsOnMemberList(user1FirstNick, opSet1Room.getMembers()));

        assertFalse(
            "both old and new nick are listed in a room after nick change, " +
            "from the peer side",
            nameIsOnMemberList(user1FirstNick, opSet2Room.getMembers()));

        // trying to steal a nickname
        try
        {
            opSet2Room.setUserNickname(user1SecondNick);
        }
        catch (OperationFailedException ex)
        {
            // stealing nickname is not allowed by the room
        }
        if (opSet1Room.getUserNickname().equals(opSet2Room.getUserNickname()))
        {
            fail(
                "different users are holding the same nick in the same room");
        }
    }

    /**
     * <tt>testRoomSubject</tt> test changing the room subject.
     */
    public void testRoomSubject()
        throws OperationFailedException,
        OperationNotSupportedException
    {
        String testRoomName = testRoomBaseName + roomID++;

        String oldSubject;
        String newSubjet = "bingo";

        // we create the test room
        ChatRoom opSet1Room =
            opSetMUC1.createChatRoom(testRoomName, null);
        opSet1Room.join();

        ChatRoom opSet2Room = opSetMUC2.findRoom(testRoomName);
        opSet2Room.join();

        MUCEventCollector opSet2RoomCollector =
            new MUCEventCollector(opSet2Room, MUCEventCollector.EVENT_PROPERTY);

        MUCEventCollector opSet1RoomCollector =
            new MUCEventCollector(opSet2Room, MUCEventCollector.EVENT_PROPERTY);

        oldSubject = opSet1Room.getSubject();

        opSet1Room.setSubject(newSubjet);

        opSet1RoomCollector.waitForEvent(20000);
        opSet2RoomCollector.waitForEvent(20000);

        assertEquals("user1 didnt received an event for room subject change"
            , 1, opSet1RoomCollector.collectedEvents.size());

        assertEquals("user2 didnt received an event for room subject change"
            , 1, opSet2RoomCollector.collectedEvents.size());

        assertEquals("the room subject is not up to date "
            , newSubjet, opSet1Room.getSubject());

        assertEquals("the room subject is not up to date " +
            "from peer side"
            , newSubjet, opSet2Room.getSubject());

        ChatRoomPropertyChangeEvent changeEvent =
            (ChatRoomPropertyChangeEvent)
            opSet1RoomCollector.collectedEvents.get(0);

        assertEquals("the old subject provided by the change event " +
            "is not the good one"
            , oldSubject, changeEvent.getOldValue());

        assertEquals("the new subject provided by the change event " +
            "is not the good one"
            , newSubjet, changeEvent.getNewValue());

        ChatRoomPropertyChangeEvent peerEvent =
            (ChatRoomPropertyChangeEvent)
            opSet2RoomCollector.collectedEvents.get(0);

        assertEquals("both sides didn't received similar change event ",
            changeEvent.getOldValue(), peerEvent.getOldValue());

        assertEquals("both sides didn't received similar change event ",
            changeEvent.getNewValue(), peerEvent.getNewValue());
    }

    /**
     * Here we test conference messaging : sending, receiving messages and
     * corresponding events.
     */
    public void testConferenceChat()
        throws OperationFailedException,
               OperationNotSupportedException
    {
        String testRoomName = testRoomBaseName + roomID++;

        logger.info("--- Start testConferenceChat room:" + testRoomName);

        // we create the test room
        ChatRoom opSet1Room =
            opSetMUC1.createChatRoom(testRoomName, null);
        opSet1Room.join();

        ChatRoom opSet2Room = opSetMUC2.findRoom(testRoomName);
        opSet2Room.join();

        // building and checking the message
        String message1 = "lorem ipsum first";
        Message opSet1Message = opSet1Room.createMessage(message1);

        assertEquals("created message content differ from the one provided "
            ,message1, opSet1Message.getContent());

        MUCEventCollector opSet1RoomCollector = new MUCEventCollector(
            opSet1Room, MUCEventCollector.EVENT_MESSAGE, true);

        MUCEventCollector opSet2RoomCollector = new MUCEventCollector(
            opSet2Room, MUCEventCollector.EVENT_MESSAGE, true);

        // ship it
        opSet1Room.sendMessage(opSet1Message);

        opSet1RoomCollector.waitForEvent(10000);
        opSet2RoomCollector.waitForEvent(10000);

        // does every member received the message
        assertEquals("user1 didn't received an event for message delivery "
            , 1, opSet1RoomCollector.collectedEvents.size());
        assertEquals("user2 didn't received the message"
            , 1, opSet2RoomCollector.collectedEvents.size());

        ChatRoomMessageDeliveredEvent deliveryEvent =
            (ChatRoomMessageDeliveredEvent)
                opSet1RoomCollector.collectedEvents.get(0);

        assertEquals("message type is not CONVERSATION_MESSAGE_DELIVERED"
            , ChatRoomMessageDeliveredEvent.CONVERSATION_MESSAGE_DELIVERED
            , deliveryEvent.getEventType());

        assertSame("the message has been delivered to another room instance"
            , opSet1Room, deliveryEvent.getSourceChatRoom());

        assertEquals(
            "delivered message differ from sent message"
            , message1, deliveryEvent.getMessage().getContent());

        // what happened on user2 side
        ChatRoomMessageReceivedEvent messageEvent =
            (ChatRoomMessageReceivedEvent)
            opSet2RoomCollector.collectedEvents.get(0);

        assertEquals("message type is not CONVERSATION_MESSAGE_RECEIVED"
            , ChatRoomMessageReceivedEvent.CONVERSATION_MESSAGE_RECEIVED
            , messageEvent.getEventType());

        assertSame("the message comes from another room instance"
            , opSet2Room, messageEvent.getSourceChatRoom());

        ChatRoomMember member = messageEvent.getSourceChatRoomMember();

        assertEquals("message comes from an unexpected user"
            , opSet1Room.getUserNickname(), member.getName());

        assertEquals(
            "received message differ from sent message"
            , message1, messageEvent.getMessage().getContent());

        logger.info("--- End testConferenceChat room:" + testRoomName);
    }

    /**
     * In <tt>testMemberBan</tt>, a member joins, then he is banned
     * and try to join again
     */
    public void testMemberBan()
        throws OperationFailedException,
        OperationNotSupportedException
    {
        try
        {
            Thread.sleep(2000);
        }
        catch (InterruptedException ex)
        {}

        String testRoomName = testRoomBaseName + roomID++;

        logger.info("--- Start testMemberBan room:" + testRoomName);

        // we create the test room
        ChatRoom opSet1Room =
            opSetMUC1.createChatRoom(testRoomName, null);
        opSet1Room.join();

        MUCEventCollector opSet1RoomCollector =
            new MUCEventCollector(
            opSet1Room, MUCEventCollector.EVENT_PRESENCE);

        ChatRoom opSet2Room = opSetMUC2.findRoom(testRoomName);
        opSet2Room.join();

        opSet1RoomCollector.waitForEvent(10000);

        assertTrue("user2 not on member list after join"
            , nameIsOnMemberList(fixture.userID2, opSet1Room.getMembers()));

        List<ChatRoomMember> members = opSet1Room.getMembers();

        ChatRoomMember memberToBan = null;
        for (ChatRoomMember member : members)
            if (member.getContactAddress().equals(fixture.userID2))
            {
                memberToBan = member;
                break;
            }

        if (memberToBan == null)
            throw new IllegalStateException("member to ban not found");

        opSet1RoomCollector =
            new MUCEventCollector(
            opSet1Room, MUCEventCollector.EVENT_ROLE);

        assertTrue("user2 not in a room he joined "
            , opSet2Room.isJoined());

        MUCEventCollector opSet2RoomCollector =
            new MUCEventCollector(
            opSet2Room, MUCEventCollector.EVENT_PRESENCE);

        opSet1Room.banParticipant(memberToBan, "testMemberBan");

        // when an user is kicked or banned, the smack lib deliver corresponding
        // events to remaining room members but nothing to the kicked or banned
        // user. altough, if we dont wait some amount of time, the banned user
        // could still been reported as joined on his side.
        opSet2RoomCollector.waitForEvent(2000);

        assertFalse("user2 still in a room after been banned"
            , opSet2Room.isJoined());

        opSet1RoomCollector.waitForEvent(10000);

        assertEquals("user1 didnt received an event for user2 ban"
            , 1, opSet1RoomCollector.collectedEvents.size());

        assertFalse("user2 still on member list after ban"
            , nameIsOnMemberList(fixture.userID2, opSet1Room.getMembers()));

        try
        {
            opSet2Room.join();
        }
        catch (Exception ex)
        {
            // user2 has not been only kicked but banned so
            // he must not be able to join
        }
        assertFalse("user2 just joined a room where he is banned"
            , opSet2Room.isJoined());

        logger.info("--- End testMemberBan room:" + testRoomName);
    }


    /**
     * In <tt>testMemberKick</tt>, a member joins, then he is banned
     * and try to join again
     */
    public void testMemberKick()
        throws OperationFailedException,
        OperationNotSupportedException
    {
        try
        {
            Thread.sleep(2000);
        }
        catch (InterruptedException ex)
        {}

        String testRoomName = testRoomBaseName + roomID++;

        logger.info("--- Start testMemberKick room:" + testRoomName);

        // we create the test room
        ChatRoom opSet1Room =
            opSetMUC1.createChatRoom(testRoomName, null);
        opSet1Room.join();

        MUCEventCollector opSet1RoomCollector =
            new MUCEventCollector(
            opSet1Room, MUCEventCollector.EVENT_PRESENCE);

        ChatRoom opSet2Room = opSetMUC2.findRoom(testRoomName);
        opSet2Room.join();

        opSet1RoomCollector.waitForEvent(10000);

        assertTrue("user2 not on member list after join"
            , nameIsOnMemberList(fixture.userID2, opSet1Room.getMembers()));

        List<ChatRoomMember> members = opSet1Room.getMembers();

        ChatRoomMember memberToKick = null;
        for (ChatRoomMember member : members)
            if (member.getContactAddress().equals(fixture.userID2))
            {
                memberToKick = member;
                break;
            }
        if (memberToKick == null)
            throw new IllegalStateException("member to kick not found");

        opSet1RoomCollector =
            new MUCEventCollector(
            opSet1Room, MUCEventCollector.EVENT_PRESENCE);

        assertTrue("user2 not in a room he joined "
            , opSet2Room.isJoined());

        MUCEventCollector opSet2RoomCollector =
            new MUCEventCollector(
            opSet2Room, MUCEventCollector.EVENT_PRESENCE);

        opSet1Room.kickParticipant(memberToKick, "testMemberKick");

        // when an user is kicked or banned, the smack lib deliver corresponding
        // events to remaining room members but nothing to the kicked or banned
        // user. altough, if we dont wait some amount of time, the banned user
        // could stils been reported as joined on his side.
        opSet2RoomCollector.waitForEvent(2000);

        assertFalse("user2 still in a room after been kicked"
            , opSet2Room.isJoined());

        opSet1RoomCollector.waitForEvent(10000);

        assertEquals("user1 didnt received an event for user2 kick"
            , 1, opSet1RoomCollector.collectedEvents.size());

        ChatRoomMemberPresenceChangeEvent changeEvent =
            (ChatRoomMemberPresenceChangeEvent)
            opSet1RoomCollector.collectedEvents.get(0);

        assertEquals("the event received by user1 is not "
            , ChatRoomMemberPresenceChangeEvent.MEMBER_KICKED
            , changeEvent.getEventType());

        assertEquals("the kicked member is not the one expected"
            , memberToKick.getContactAddress()
            , changeEvent.getChatRoomMember().getContactAddress());

        assertFalse("user2 still on member list after kick"
            , nameIsOnMemberList(fixture.userID2, opSet1Room.getMembers()));

        // contrary to a ban, an user can join after been kicked
        opSet2Room.join();

        assertTrue("user2 can't join after been kicked "
            , opSet2Room.isJoined());

        opSet1RoomCollector.waitForEvent(10000);

        assertTrue("user2 not on list when joining after a kick"
            , nameIsOnMemberList(fixture.userID2, opSet1Room.getMembers()));

        logger.info("--- End testMemberKick room:" + testRoomName);
    }

    /**
     * Tests participant roles when the room just has been created.
     * The member who has created the room should have OWNER role.
     * Others participants who are invited to the room should be MEMBER.
     *
     * @throws OperationNotSupportedException
     * @throws OperationFailedException
     */
    public void testInitialParticipantsRoles()
    throws OperationFailedException, OperationNotSupportedException
    {
        String roomName = testRoomBaseName + roomID;

        logger.info("--- Start testInitialParticipantsRoles room:" + roomName);

        // We create the test room
        ChatRoom roomUser1 = opSetMUC1.createChatRoom(roomName, null);
        roomUser1.join();

        MUCEventCollector roomUser1Collector =
            new MUCEventCollector(roomUser1, MUCEventCollector.EVENT_PRESENCE);
        roomUser1Collector.waitForEvent(10000); // wait for user1

        // User1 who just created the room is supposed to be the owner:
        assertEquals("Unexpected role for user1",
            roomUser1.getUserRole(), ChatRoomMemberRole.OWNER);
        assertEquals("Unexpected role for user1", roomUser1.getUserRole(),
            getRole(fixture.userID1, roomUser1.getMembers(),
                    roomUser1.getName(), true));

        // Both of our peers join the room:
        ChatRoom roomUser2 = opSetMUC2.findRoom(roomName);
        assertNotNull("Room can't be retrieved on user2's side", roomUser2);
        roomUser2.join();
        roomUser1Collector.waitForEvent(10000); // wait for user2

        ChatRoom roomUser3 = opSetMUC3.findRoom(roomName);
        assertNotNull("Room can't be retrieved on user3's side", roomUser2);
        roomUser3.join();
        roomUser1Collector.waitForEvent(10000); // wait for user3

        assertTrue("User2 not on member list after having join the room"
            , nameIsOnMemberList(fixture.userID2, roomUser1.getMembers()));
        assertTrue("User3 not on member list after having join the room"
            , nameIsOnMemberList(fixture.userID3, roomUser1.getMembers()));

        List<ChatRoomMember> members = roomUser1.getMembers();

        // Three members are supposed to be in the room:
        assertEquals("Unexpected members count", 3, members.size());

        // We are sure two of these members are our peers (checked before the
        // last assertion), now we make sure they are both members:
        // (Note that the first element of the list refers to the owner)
        assertEquals("The current implementation requires that room's new " +
            "comers must have MEMBER role",
            ChatRoomMemberRole.MEMBER, getRole(fixture.userID2, members,
                                        roomName, true));
        assertEquals("The current implementation requires that room's new " +
            "comers must have the MEMBER role",
            ChatRoomMemberRole.MEMBER, getRole(fixture.userID3, members,
                                        roomName, true));

        logger.info("--- End testInitialParticipantsRoles room:" + roomName);
    }

    /**
     * Search the user in the list by address and return its role.
     * @param userID
     * @param members
     * @param  roomName
     * @param printDebugInfo
     * @return
     */
    private ChatRoomMemberRole getRole(String userID,
                                       List<ChatRoomMember> members,
                                       String roomName,
                                       boolean printDebugInfo)
    {
        if(printDebugInfo)
            logger.info("Search in mems of " + roomName + " for " + userID);

        ChatRoomMemberRole roleFound = null;
        for(int i = 0; i < members.size(); i++)
        {
            ChatRoomMember mem = members.get(i);
            if(mem.getContactAddress().equals(userID))
                roleFound = mem.getRole();

            if(printDebugInfo)
                logger.info("m[" + i + "] "
                    + mem.getContactAddress() + " r:" + mem.getRole());
        }

        if(printDebugInfo)
            logger.info("found role: " + roleFound);

        return roleFound;
    }

    /**
     * The owner of the room (the user who created it, and which is an
     * administrator too, typically user1) will grant membership's role to one
     * of our peers (user2).
     * Membership means a long-life affiliation to the room, rather than being a
     * guest.
     *
     * @throws OperationNotSupportedException
     * @throws OperationFailedException
     */
    public void testGrantMembership()
    throws OperationFailedException, OperationNotSupportedException
    {
        try
        {
            Thread.sleep(2000);
        }
        catch (InterruptedException ex)
        {}

        String roomName = testRoomBaseName + roomID;

        logger.info("--- Start testGrantMembership room:" + roomName);

        ChatRoom roomUser1 = opSetMUC1.findRoom(roomName);
        assertNotNull("The room can't be retrieved on user1's side", roomUser1);

        ChatRoom roomUser3 = opSetMUC3.findRoom(roomName);
        assertNotNull("The room can't be retrieved on user3's side", roomUser3);

        MUCEventCollector roomUser1Col = new MUCEventCollector(
            roomUser1, MUCEventCollector.EVENT_ROLE);
        MUCEventCollector roomUser3Col = new MUCEventCollector(
            roomUser3, MUCEventCollector.EVENT_ROLE);

        // Owner of the room grants user2 member rights:
        roomUser1.grantMembership(fixture.userID2);

        roomUser1Col.waitForEvent(10000);
        roomUser3Col.waitForEvent(10000);

        assertEquals("Wrong count of collected events on user1's side",
            1, roomUser1Col.collectedEvents.size());
        ChatRoomMemberRoleChangeEvent roleEventUser1 =
            (ChatRoomMemberRoleChangeEvent) roomUser1Col.collectedEvents.get(0);

        assertEquals("Wrong count of collected events on user3's side",
            1, roomUser3Col.collectedEvents.size());
        ChatRoomMemberRoleChangeEvent roleEventUser3 =
            (ChatRoomMemberRoleChangeEvent) roomUser3Col.collectedEvents.get(0);

        assertEquals("Collected event does not belong to the right chatroom",
            roomName, XmppStringUtils.parseLocalpart(
                roleEventUser1.getSourceChatRoom().getName()));
        assertEquals("Collected event does not belong to the expected user",
            fixture.userID2,
            roleEventUser1.getSourceMember().getContactAddress());
        assertEquals("User2's previous role does not match",
            ChatRoomMemberRole.GUEST, roleEventUser1.getPreviousRole());
        assertEquals("Unexpected new role",
            ChatRoomMemberRole.MEMBER, roleEventUser1.getNewRole());

        assertEquals("Collected event does not belong to the right chatroom",
            roomName, XmppStringUtils.parseLocalpart(
                roleEventUser3.getSourceChatRoom().getName()));
        assertEquals("Collected event does not belong to the expected user",
            fixture.userID2,
            roleEventUser3.getSourceMember().getContactAddress());
        assertEquals("User2's previous role does not match",
            ChatRoomMemberRole.GUEST, roleEventUser3.getPreviousRole());
        assertEquals("Unexpected new role",
            ChatRoomMemberRole.MEMBER, roleEventUser3.getNewRole());

        logger.info("--- End testGrantMembership room:" + roomName);
    }

    /**
     * The owner of the room (the user who created it, and which is an
     * administrator too, typically user1) will grant moderator role to one
     * of our peers (user2).
     * A moderator is allowed to kick users, grant and revoke voice,
     * invite other users, modify room's subject plus all the participants
     * privileges.
     *
     * @throws OperationNotSupportedException
     * @throws OperationFailedException
     */
    public void testGrantModerator()
    throws OperationFailedException, OperationNotSupportedException
    {
        try
        {
            Thread.sleep(2000);
        }
        catch (InterruptedException ex)
        {}

        String roomName = testRoomBaseName + roomID;

        logger.info("--- Start testGrantModerator room:" + roomName);

        ChatRoom roomUser1 = opSetMUC1.findRoom(roomName);
        assertNotNull("The room can't be retrieved on user1's side", roomUser1);

        ChatRoom roomUser3 = opSetMUC3.findRoom(roomName);
        assertNotNull("The room can't be retrieved on user3's side", roomUser3);

        MUCEventCollector roomUser1Col = new MUCEventCollector(
            roomUser1, MUCEventCollector.EVENT_ROLE);
        MUCEventCollector roomUser3Col = new MUCEventCollector(
            roomUser3, MUCEventCollector.EVENT_ROLE);

        // Owner of the room grants user2 moderator rights:
        roomUser1.grantModerator(fixture.userID2);

        roomUser1Col.waitForEvent(10000);
        roomUser3Col.waitForEvent(10000);

        assertEquals("Wrong count of collected events on user1's side",
            1, roomUser1Col.collectedEvents.size());
        ChatRoomMemberRoleChangeEvent roleEventUser1 =
            (ChatRoomMemberRoleChangeEvent) roomUser1Col.collectedEvents.get(0);

        assertEquals("Wrong count of collected events on user3's side",
            1, roomUser3Col.collectedEvents.size());
        ChatRoomMemberRoleChangeEvent roleEventUser3 =
            (ChatRoomMemberRoleChangeEvent) roomUser3Col.collectedEvents.get(0);

        assertEquals("Collected event does not belong to the right chatroom",
            roomName, XmppStringUtils.parseLocalpart(
                roleEventUser1.getSourceChatRoom().getName()));
        assertEquals("Collected event does not belong to the expected user",
            fixture.userID2,
            roleEventUser1.getSourceMember().getContactAddress());
        assertEquals("User2's previous role does not match",
            ChatRoomMemberRole.MEMBER, roleEventUser1.getPreviousRole());
        assertEquals("Unexpected new role",
            ChatRoomMemberRole.MODERATOR, roleEventUser1.getNewRole());

        assertEquals("Collected event does not belong to the right chatroom",
            roomName, XmppStringUtils.parseLocalpart(
                roleEventUser3.getSourceChatRoom().getName()));
        assertEquals("Collected event does not belong to the expected user",
            fixture.userID2,
            roleEventUser3.getSourceMember().getContactAddress());
        assertEquals("User2's previous role does not match",
            ChatRoomMemberRole.MEMBER, roleEventUser3.getPreviousRole());
        assertEquals("Unexpected new role",
            ChatRoomMemberRole.MODERATOR, roleEventUser3.getNewRole());

        logger.info("--- End testGrantModerator room:" + roomName);
    }

    /**
     * User2 which is currently moderator in the room for our scenario will
     * revoke voice to user3, thanks to his moderator privileges.
     *
     * We will first check that user3 has become a silent member before making
     * sure that he can't send messages through the room.
     *
     * @throws OperationNotSupportedException
     * @throws OperationFailedException
     */
    public void testRevokeVoice()
    throws OperationFailedException, OperationNotSupportedException
    {
        try
        {
            Thread.sleep(2000);
        }
        catch (InterruptedException ex)
        {}

        String roomName = testRoomBaseName + roomID;

        logger.info("---= Start test for RevokeVoice =--- room: " + roomName);

        ChatRoom roomUser1 = opSetMUC1.findRoom(roomName);
        assertNotNull("The room can't be retrieved on user1's side", roomUser1);

        ChatRoom roomUser2 = opSetMUC2.findRoom(roomName);
        assertNotNull("The room can't be retrieved on user2's side", roomUser2);

        ChatRoom roomUser3 = opSetMUC3.findRoom(roomName);
        assertNotNull("The room can't be retrieved on user3's side", roomUser3);

        MUCEventCollector roomUser1Col = new MUCEventCollector(
            roomUser1, MUCEventCollector.EVENT_ROLE);
        MUCEventCollector roomUser2Col = new MUCEventCollector(
            roomUser2, MUCEventCollector.EVENT_ROLE);

        // User2 (moderator role) revokes voice right to user2 (guest):
        roomUser2.revokeVoice(fixture.userID3);

        roomUser1Col.waitForEvent(10000);
        roomUser2Col.waitForEvent(10000);

        assertEquals("Wrong count of collected events on user1's side",
            1, roomUser1Col.collectedEvents.size());
        ChatRoomMemberRoleChangeEvent roleEventUser1 =
            (ChatRoomMemberRoleChangeEvent) roomUser1Col.collectedEvents.get(0);

        assertEquals("Wrong count of collected events on user2's side",
            1, roomUser2Col.collectedEvents.size());
        ChatRoomMemberRoleChangeEvent roleEventUser2 =
            (ChatRoomMemberRoleChangeEvent) roomUser2Col.collectedEvents.get(0);

        assertEquals("Collected event does not belong to the right chatroom",
            roomName, XmppStringUtils.parseLocalpart(
                roleEventUser1.getSourceChatRoom().getName()));
        assertEquals("Collected event does not belong to the expected user",
            fixture.userID3,
            roleEventUser1.getSourceMember().getContactAddress());
        assertEquals("User3's previous role does not match",
            ChatRoomMemberRole.MEMBER, roleEventUser1.getPreviousRole());
        assertEquals("Unexpected new role",
            ChatRoomMemberRole.SILENT_MEMBER, roleEventUser1.getNewRole());

        assertEquals("Collected event does not belong to the right chatroom",
            roomName, XmppStringUtils.parseLocalpart(
                roleEventUser2.getSourceChatRoom().getName()));
        assertEquals("Collected event does not belong to the expected user",
            fixture.userID3,
            roleEventUser2.getSourceMember().getContactAddress());
        assertEquals("User3's previous role does not match",
            ChatRoomMemberRole.MEMBER, roleEventUser2.getPreviousRole());
        assertEquals("Unexpected new role",
            ChatRoomMemberRole.SILENT_MEMBER, roleEventUser2.getNewRole());

        logger.info("---= End test for RevokeVoice =--- room: " + roomName);
    }

    /**
     * User2 which is currently moderator in the room for our scenario will
     * grant voice to user3 (thanks to his moderator privileges) after having
     * revoked him it in the previous test of the suite.
     *
     * We will first check that user3 has become a guest before making
     * sure that he can again send messages through the room.
     *
     * @throws OperationNotSupportedException
     * @throws OperationFailedException
     */
    public void testGrantVoice()
    throws OperationFailedException, OperationNotSupportedException
    {
        try
        {
            Thread.sleep(2000);
        }
        catch (InterruptedException ex)
        {}

        String roomName = testRoomBaseName + roomID;

        logger.info("---= Start test for GrantVoice =--- room: " + roomName);

        ChatRoom roomUser1 = opSetMUC1.findRoom(roomName);
        assertNotNull("The room can't be retrieved on user1's side", roomUser1);

        ChatRoom roomUser2 = opSetMUC2.findRoom(roomName);
        assertNotNull("The room can't be retrieved on user2's side", roomUser2);

        MUCEventCollector roomUser1Col = new MUCEventCollector(
            roomUser1, MUCEventCollector.EVENT_ROLE);
        MUCEventCollector roomUser2Col = new MUCEventCollector(
            roomUser2, MUCEventCollector.EVENT_ROLE);

        // User2 of the room grants voice right to user2:
        roomUser2.grantVoice(fixture.userID3);

        roomUser1Col.waitForEvent(10000);
        roomUser2Col.waitForEvent(10000);

        assertEquals("Wrong count of collected events on user1's side",
            1, roomUser1Col.collectedEvents.size());
        ChatRoomMemberRoleChangeEvent roleEventUser1 =
            (ChatRoomMemberRoleChangeEvent) roomUser1Col.collectedEvents.get(0);

        assertEquals("Wrong count of collected events on user3's side",
            1, roomUser2Col.collectedEvents.size());
        ChatRoomMemberRoleChangeEvent roleEventUser2 =
            (ChatRoomMemberRoleChangeEvent) roomUser2Col.collectedEvents.get(0);

        assertEquals("Collected event does not belong to the right chatroom",
            roomName, XmppStringUtils.parseLocalpart(
                roleEventUser1.getSourceChatRoom().getName()));
        assertEquals("Collected event does not belong to the expected user",
            fixture.userID3,
            roleEventUser1.getSourceMember().getContactAddress());
        assertEquals("User3's previous role does not match",
            ChatRoomMemberRole.SILENT_MEMBER, roleEventUser1.getPreviousRole());
        assertEquals("Unexpected new role",
            ChatRoomMemberRole.MEMBER, roleEventUser1.getNewRole());

        assertEquals("Collected event does not belong to the right chatroom",
            roomName, XmppStringUtils.parseLocalpart(
                roleEventUser2.getSourceChatRoom().getName()));
        assertEquals("Collected event does not belong to the expected user",
            fixture.userID3,
            roleEventUser2.getSourceMember().getContactAddress());
        assertEquals("User3's previous role does not match",
            ChatRoomMemberRole.SILENT_MEMBER, roleEventUser2.getPreviousRole());
        assertEquals("Unexpected new role",
            ChatRoomMemberRole.MEMBER, roleEventUser2.getNewRole());

        logger.info("---= End test for GrantVoice =--- room: " + roomName);
    }

    /**
     * The owner of the room (the user who created it, and which is an
     * administrator too, typically user1) will grant administrator role to one
     * of our peers (user2).
     * An administrator is allowed to perform administrative functions such as
     * banning users and edit moderator list.
     *
     * @throws OperationNotSupportedException
     * @throws OperationFailedException
     */
    public void testGrantAdmin()
    throws OperationFailedException, OperationNotSupportedException
    {
        try
        {
            Thread.sleep(2000);
        }
        catch (InterruptedException ex)
        {}

        String roomName = testRoomBaseName + roomID;

        logger.info("--- Start testGrantAdmin room:" + roomName);

        ChatRoom roomUser1 = opSetMUC1.findRoom(roomName);
        assertNotNull("The room can't be retrieved on user1's side", roomUser1);

        ChatRoom roomUser2 = opSetMUC2.findRoom(roomName);
        assertNotNull("The room can't be retrieved on user3's side", roomUser2);

        ChatRoom roomUser3 = opSetMUC3.findRoom(roomName);
        assertNotNull("The room can't be retrieved on user3's side", roomUser3);

        MUCEventCollector roomUser1Col = new MUCEventCollector(
            roomUser1, MUCEventCollector.EVENT_ROLE);
        MUCEventCollector roomUser3Col = new MUCEventCollector(
            roomUser3, MUCEventCollector.EVENT_ROLE);

        // Owner of the room grants administrator rights to user2:
        roomUser1.grantAdmin(fixture.userID2);

        roomUser1Col.waitForEvent(10000);
        roomUser3Col.waitForEvent(10000);

        assertEquals("Wrong count of collected events on user1's side",
            1, roomUser1Col.collectedEvents.size());
        ChatRoomMemberRoleChangeEvent roleEventUser1 =
            (ChatRoomMemberRoleChangeEvent) roomUser1Col.collectedEvents.get(0);

        assertEquals("Wrong count of collected events on user3's side",
            1, roomUser3Col.collectedEvents.size());
        ChatRoomMemberRoleChangeEvent roleEventUser3 =
            (ChatRoomMemberRoleChangeEvent) roomUser3Col.collectedEvents.get(0);

        assertEquals("Collected event does not belong to the right chatroom",
            roomName, XmppStringUtils.parseLocalpart(
                roleEventUser1.getSourceChatRoom().getName()));
        assertEquals("Collected event does not belong to the expected user",
            fixture.userID2,
            roleEventUser1.getSourceMember().getContactAddress());
        assertEquals("User2's previous role does not match",
            ChatRoomMemberRole.MEMBER, roleEventUser1.getPreviousRole());
        assertEquals("Unexpected new role",
            ChatRoomMemberRole.ADMINISTRATOR, roleEventUser1.getNewRole());

        assertEquals("Collected event does not belong to the right chatroom",
            roomName, XmppStringUtils.parseLocalpart(
                roleEventUser3.getSourceChatRoom().getName()));
        assertEquals("Collected event does not belong to the expected user",
            fixture.userID2,
            roleEventUser3.getSourceMember().getContactAddress());
        assertEquals("User2's previous role does not match",
            ChatRoomMemberRole.MEMBER, roleEventUser3.getPreviousRole());
        assertEquals("Unexpected new role",
            ChatRoomMemberRole.ADMINISTRATOR, roleEventUser3.getNewRole());

        logger.info("--- End testGrantAdmin room:" + roomName);
    }

    /**
     * The owner of the room (the user who created it, and which is an
     * administrator too, typically user1) will grant ownership privileges to
     * one of our peers (user2).
     * An owner is allowed to change defining room features as well as perform
     * all administrative functions.
     *
     * @throws OperationNotSupportedException
     * @throws OperationFailedException
     */
    public void testGrantOwnership()
    throws OperationFailedException, OperationNotSupportedException
    {
        try
        {
            Thread.sleep(2000);
        }
        catch (InterruptedException ex)
        {}

        String roomName = testRoomBaseName + roomID;

        logger.info("--- Start testGrantOwnership room:" + roomName);

        ChatRoom roomUser1 = opSetMUC1.findRoom(roomName);
        assertNotNull("The room can't be retrieved on user1's side", roomUser1);

        ChatRoom roomUser3 = opSetMUC3.findRoom(roomName);
        assertNotNull("The room can't be retrieved on user3's side", roomUser3);

        MUCEventCollector roomUser1Col = new MUCEventCollector(
            roomUser1, MUCEventCollector.EVENT_ROLE);
        MUCEventCollector roomUser3Col = new MUCEventCollector(
            roomUser3, MUCEventCollector.EVENT_ROLE);

        // Owner of the room grants ownership privileges to user2:
        roomUser1.grantOwnership(fixture.userID2);

        roomUser1Col.waitForEvent(10000);
        roomUser3Col.waitForEvent(10000);

        assertEquals("Wrong count of collected events on user1's side",
            1, roomUser1Col.collectedEvents.size());
        ChatRoomMemberRoleChangeEvent roleEventUser1 =
            (ChatRoomMemberRoleChangeEvent) roomUser1Col.collectedEvents.get(0);

        assertEquals("Wrong count of collected events on user3's side",
            1, roomUser3Col.collectedEvents.size());
        ChatRoomMemberRoleChangeEvent roleEventUser3 =
            (ChatRoomMemberRoleChangeEvent) roomUser3Col.collectedEvents.get(0);

        assertEquals("Collected event does not belong to the right chatroom",
            roomName, XmppStringUtils.parseLocalpart(
                roleEventUser1.getSourceChatRoom().getName()));
        assertEquals("Collected event does not belong to the expected user",
            fixture.userID2,
            roleEventUser1.getSourceMember().getContactAddress());
        assertEquals("User2's previous role does not match",
            ChatRoomMemberRole.ADMINISTRATOR, roleEventUser1.getPreviousRole());
        assertEquals("Unexpected new role",
            ChatRoomMemberRole.OWNER, roleEventUser1.getNewRole());

        assertEquals("Collected event does not belong to the right chatroom",
            roomName, XmppStringUtils.parseLocalpart(
                roleEventUser3.getSourceChatRoom().getName()));
        assertEquals("Collected event does not belong to the expected user",
            fixture.userID2,
            roleEventUser3.getSourceMember().getContactAddress());
        assertEquals("User2's previous role does not match",
            ChatRoomMemberRole.ADMINISTRATOR, roleEventUser3.getPreviousRole());
        assertEquals("Unexpected new role",
            ChatRoomMemberRole.OWNER, roleEventUser3.getNewRole());

        logger.info("--- End testGrantOwnership room:" + roomName);
    }

    /**
     * The owner of the room (the user who created it, and which is an
     * administrator too, typically user1) will revoke membership's role to one
     * of our peers (user2).
     * The considered participant is then supposed to be a guest.
     *
     * @throws OperationNotSupportedException
     * @throws OperationFailedException
     */
    public void testRevokeMembership()
    throws OperationFailedException, OperationNotSupportedException
    {
        try
        {
            Thread.sleep(2000);
        }
        catch (InterruptedException ex)
        {}

        String roomName = testRoomBaseName + roomID;

        logger.info("--- Start testRevokeMembership room:" + roomName);

        ChatRoom roomUser1 = opSetMUC1.findRoom(roomName);
        assertNotNull("The room can't be retrieved on user1's side", roomUser1);

        ChatRoom roomUser3 = opSetMUC3.findRoom(roomName);
        assertNotNull("The room can't be retrieved on user3's side", roomUser3);

        MUCEventCollector roomUser3Col = new MUCEventCollector(
            roomUser3, MUCEventCollector.EVENT_ROLE);

        // Owner of the room revokes user2 member rights:
        roomUser1.revokeMembership(fixture.userID2);

        roomUser3Col.waitForEvent(10000);

        assertEquals("Wrong count of collected events on user3's side",
            1, roomUser3Col.collectedEvents.size());
        ChatRoomMemberRoleChangeEvent roleEventUser3 =
            (ChatRoomMemberRoleChangeEvent) roomUser3Col.collectedEvents.get(0);

        assertEquals("Collected event does not belong to the right chatroom",
            roomName, XmppStringUtils.parseLocalpart(
                roleEventUser3.getSourceChatRoom().getName()));
        assertEquals("Collected event does not belong to the expected user",
            fixture.userID2,
            roleEventUser3.getSourceMember().getContactAddress());
        assertEquals("User2's previous role does not match",
            ChatRoomMemberRole.MEMBER, roleEventUser3.getPreviousRole());
        assertEquals("Unexpected new role",
            ChatRoomMemberRole.GUEST, roleEventUser3.getNewRole());

        logger.info("--- End testRevokeMembership room:" + roomName);
    }

    /**
     * The owner of the room (the user who created it, and which is an
     * administrator too, typically user1) will revoke moderator role to user2.
     * The considered participant is then supposed to be a member.
     *
     * @throws OperationNotSupportedException
     * @throws OperationFailedException
     */
    public void testRevokeModerator()
    throws OperationFailedException, OperationNotSupportedException
    {
        try
        {
            Thread.sleep(2000);
        }
        catch (InterruptedException ex)
        {}

        String roomName = testRoomBaseName + roomID;

        logger.info("--- Start testRevokeModerator room:" + roomName);

        ChatRoom roomUser1 = opSetMUC1.findRoom(roomName);
        assertNotNull("The room can't be retrieved on user1's side", roomUser1);

        ChatRoom roomUser3 = opSetMUC3.findRoom(roomName);
        assertNotNull("The room can't be retrieved on user3's side", roomUser3);

        MUCEventCollector roomUser1Col = new MUCEventCollector(
            roomUser1, MUCEventCollector.EVENT_ROLE);
        MUCEventCollector roomUser3Col = new MUCEventCollector(
            roomUser3, MUCEventCollector.EVENT_ROLE);

        // Owner of the room revokes user2 member rights:
        roomUser1.revokeModerator(fixture.userID2);

        roomUser1Col.waitForEvent(10000);
        roomUser3Col.waitForEvent(10000);

        assertEquals("Wrong count of collected events on user1's side",
            1, roomUser1Col.collectedEvents.size());
        ChatRoomMemberRoleChangeEvent roleEventUser1 =
            (ChatRoomMemberRoleChangeEvent) roomUser1Col.collectedEvents.get(0);

        assertEquals("Wrong count of collected events on user3's side",
            1, roomUser3Col.collectedEvents.size());
        ChatRoomMemberRoleChangeEvent roleEventUser3 =
            (ChatRoomMemberRoleChangeEvent) roomUser3Col.collectedEvents.get(0);

        assertEquals("Collected event does not belong to the right chatroom",
            roomName, XmppStringUtils.parseLocalpart(
                roleEventUser1.getSourceChatRoom().getName()));
        assertEquals("Collected event does not belong to the expected user",
            fixture.userID2,
            roleEventUser1.getSourceMember().getContactAddress());
        assertEquals("User2's previous role does not match",
            ChatRoomMemberRole.MODERATOR, roleEventUser1.getPreviousRole());
        assertEquals("Unexpected new role",
            ChatRoomMemberRole.MEMBER, roleEventUser1.getNewRole());

        assertEquals("Collected event does not belong to the right chatroom",
            roomName, XmppStringUtils.parseLocalpart(
                roleEventUser3.getSourceChatRoom().getName()));
        assertEquals("Collected event does not belong to the expected user",
            fixture.userID2,
            roleEventUser3.getSourceMember().getContactAddress());
        assertEquals("User2's previous role does not match",
            ChatRoomMemberRole.MODERATOR, roleEventUser3.getPreviousRole());
        assertEquals("Unexpected new role",
            ChatRoomMemberRole.MEMBER, roleEventUser3.getNewRole());

        logger.info("--- End testRevokeModerator room:" + roomName);
    }

    /**
     * The owner of the room (the user who created it, and which is an
     * administrator too, typically user1) will revoke administrator role to
     * user2.
     * The considered participant is then supposed to be a moderator.
     *
     * @throws OperationNotSupportedException
     * @throws OperationFailedException
     */
    public void testRevokeAdmin()
    throws OperationFailedException, OperationNotSupportedException
    {
        try
        {
            Thread.sleep(2000);
        }
        catch (InterruptedException ex)
        {}

        String roomName = testRoomBaseName + roomID;

        logger.info("--- Start testRevokeAdmin room:" + roomName);

        ChatRoom roomUser1 = opSetMUC1.findRoom(roomName);
        assertNotNull("The room can't be retrieved on user1's side", roomUser1);

        ChatRoom roomUser3 = opSetMUC3.findRoom(roomName);
        assertNotNull("The room can't be retrieved on user3's side", roomUser3);

        MUCEventCollector roomUser1Col = new MUCEventCollector(
            roomUser1, MUCEventCollector.EVENT_ROLE);
        MUCEventCollector roomUser3Col = new MUCEventCollector(
            roomUser3, MUCEventCollector.EVENT_ROLE);

        // Owner of the room revokes user2 member rights:
        roomUser1.revokeAdmin(fixture.userID2);

        roomUser1Col.waitForEvent(10000);
        roomUser3Col.waitForEvent(10000);

        assertEquals("Wrong count of collected events on user1's side",
            1, roomUser1Col.collectedEvents.size());
        ChatRoomMemberRoleChangeEvent roleEventUser1 =
            (ChatRoomMemberRoleChangeEvent) roomUser1Col.collectedEvents.get(0);

        assertEquals("Wrong count of collected events on user3's side",
            1, roomUser3Col.collectedEvents.size());
        ChatRoomMemberRoleChangeEvent roleEventUser3 =
            (ChatRoomMemberRoleChangeEvent) roomUser3Col.collectedEvents.get(0);

        assertEquals("Collected event does not belong to the right chatroom",
            roomName, XmppStringUtils.parseLocalpart(
                roleEventUser1.getSourceChatRoom().getName()));
        assertEquals("Collected event does not belong to the expected user",
            fixture.userID2,
            roleEventUser1.getSourceMember().getContactAddress());
        assertEquals("User2's previous role does not match",
            ChatRoomMemberRole.ADMINISTRATOR, roleEventUser1.getPreviousRole());
        assertEquals("Unexpected new role",
            ChatRoomMemberRole.MODERATOR, roleEventUser1.getNewRole());

        assertEquals("Collected event does not belong to the right chatroom",
            roomName, XmppStringUtils.parseLocalpart(
                roleEventUser3.getSourceChatRoom().getName()));
        assertEquals("Collected event does not belong to the expected user",
            fixture.userID2,
            roleEventUser3.getSourceMember().getContactAddress());
        assertEquals("User2's previous role does not match",
            ChatRoomMemberRole.ADMINISTRATOR, roleEventUser3.getPreviousRole());
        assertEquals("Unexpected new role",
            ChatRoomMemberRole.MODERATOR, roleEventUser3.getNewRole());

        logger.info("--- End testRevokeAdmin room:" + roomName);
    }

    /**
     * The owner of the room (the user who created it, and which is an
     * administrator too, typically user1) will revoke ownership rights to
     * user2.
     * The considered participant is then supposed to be an admin.
     *
     * @throws OperationNotSupportedException
     * @throws OperationFailedException
     */
    public void testRevokeOwnership()
    throws OperationFailedException, OperationNotSupportedException
    {
        try
        {
            Thread.sleep(2000);
        }
        catch (InterruptedException ex)
        {}

        String roomName = testRoomBaseName + roomID;

        logger.info("--- Start testRevokeOwnership room:" + roomName);

        ChatRoom roomUser1 = opSetMUC1.findRoom(roomName);
        assertNotNull("The room can't be retrieved on user1's side", roomUser1);

        ChatRoom roomUser3 = opSetMUC3.findRoom(roomName);
        assertNotNull("The room can't be retrieved on user3's side", roomUser3);

        MUCEventCollector roomUser1Col = new MUCEventCollector(
            roomUser1, MUCEventCollector.EVENT_ROLE);
        MUCEventCollector roomUser3Col = new MUCEventCollector(
            roomUser3, MUCEventCollector.EVENT_ROLE);

        // Owner of the room revokes user2 member rights:
        roomUser1.revokeOwnership(fixture.userID2);

        roomUser1Col.waitForEvent(10000);
        roomUser3Col.waitForEvent(10000);

        assertEquals("Wrong count of collected events on user1's side",
            1, roomUser1Col.collectedEvents.size());
        ChatRoomMemberRoleChangeEvent roleEventUser1 =
            (ChatRoomMemberRoleChangeEvent) roomUser1Col.collectedEvents.get(0);

        assertEquals("Wrong count of collected events on user3's side",
            1, roomUser3Col.collectedEvents.size());
        ChatRoomMemberRoleChangeEvent roleEventUser3 =
            (ChatRoomMemberRoleChangeEvent) roomUser3Col.collectedEvents.get(0);

        assertEquals("Collected event does not belong to the right chatroom",
            roomName, XmppStringUtils.parseLocalpart(
                roleEventUser1.getSourceChatRoom().getName()));
        assertEquals("Collected event does not belong to the expected user",
            fixture.userID2,
            roleEventUser1.getSourceMember().getContactAddress());
        assertEquals("User2's previous role does not match",
            ChatRoomMemberRole.OWNER, roleEventUser1.getPreviousRole());
        assertEquals("Unexpected new role",
            ChatRoomMemberRole.ADMINISTRATOR, roleEventUser1.getNewRole());

        assertEquals("Collected event does not belong to the right chatroom",
            roomName, XmppStringUtils.parseLocalpart(
                roleEventUser3.getSourceChatRoom().getName()));
        assertEquals("Collected event does not belong to the expected user",
            fixture.userID2,
            roleEventUser3.getSourceMember().getContactAddress());
        assertEquals("User2's previous role does not match",
            ChatRoomMemberRole.OWNER, roleEventUser3.getPreviousRole());
        assertEquals("Unexpected new role",
            ChatRoomMemberRole.ADMINISTRATOR, roleEventUser3.getNewRole());

        logger.info("--- End testRevokeOwnership room:" + roomName);
    }

    /**
     * Utility class used to collect events received during tests.
     */
    class MUCEventCollector
        implements ChatRoomInvitationRejectionListener,
                   ChatRoomInvitationListener,
                   LocalUserChatRoomPresenceListener,
                   ChatRoomMemberPresenceListener,
                   ChatRoomMessageListener,
                   ChatRoomMemberPropertyChangeListener,
                   ChatRoomPropertyChangeListener,
                   ChatRoomMemberRoleListener,
                   ChatRoomLocalUserRoleListener
    {
        private final ArrayList<EventObject> collectedEvents
            = new ArrayList<EventObject>();

        private int waitCount = 0;

        @SuppressWarnings("unused")
        private final OperationSetMultiUserChat opSet;

        @SuppressWarnings("unused")
        private final ChatRoom room;

        private static final int EVENT_INVITE = 1;

        private static final int EVENT_PRESENCE = 2;

        private static final int EVENT_MESSAGE = 3;

        private static final int EVENT_PROPERTY = 4;

        private static final int EVENT_ROLE = 5;

        /**
         * Should we skip system messages in chat rooms.
         */
        private boolean skipSystemMsgs = false;

        /**
         * Creates an event collector to listen for specific events from
         * the given opSet
         *
         * @param opSet the <tt>OperationSetMultiUserChat> from which we will
         * receive events.
         * @param  eventType indicates the kind of events we are looking for
         */
        public MUCEventCollector(
            OperationSetMultiUserChat opSet, int eventType)
        {
            this.opSet = opSet;
            room = null;
            switch (eventType)
            {
                case EVENT_INVITE:
                    opSet.addInvitationListener(this);
                    opSet.addInvitationRejectionListener(this);
                    break;
                case EVENT_PRESENCE:
                    opSet.addPresenceListener(this);
                    break;
                default:
                    throw new IllegalArgumentException(
                        "invalid event category " + eventType);
            }
        }

        /**
         * @param room the room we are listening to.
         * @param eventType indicates the kind of events we are looking for
         */
        public MUCEventCollector(ChatRoom room, int eventType)
        {
            this(room, eventType, false);
        }
        /**
         * @param room the room we are listening to.
         * @param eventType indicates the kind of events we are looking for
         * @param skipSystemMsgs should we skip system messages in chat rooms.
         */
        public MUCEventCollector(
            ChatRoom room, int eventType, boolean skipSystemMsgs)
        {
            this.room = room;
            this.skipSystemMsgs = skipSystemMsgs;
            opSet = null;
            switch (eventType)
            {
                case EVENT_PRESENCE:
                    room.addMemberPresenceListener(this);
                    break;
                case EVENT_MESSAGE:
                    room.addMessageListener(this);
                    break;
                case EVENT_PROPERTY:
                    room.addMemberPropertyChangeListener(this);
                    room.addPropertyChangeListener(this);
                    break;
                case EVENT_ROLE:
                    room.addMemberRoleListener(this);
                    room.addLocalUserRoleListener(this);
                    break;
                default:
                    throw new IllegalArgumentException(
                        "invalid event category " + eventType);
            }
        }

        /**
         * Blocks until at least one event is received or until waitFor
         * miliseconds pass (whichever happens first).
         *
         * @param waitFor the number of miliseconds that we should be waiting
         * for an event before simply bailing out.
         */
        public void waitForEvent(long waitFor)
        {
            logger.trace("Waiting for a MUC Event");

            synchronized (this)
            {
                if (collectedEvents.size() > waitCount)
                {
                    waitCount++;
                    logger.trace("Event already received. " + collectedEvents);
                    return;
                }
                try
                {
                    wait(waitFor);
                    if (collectedEvents.size() > waitCount)
                    {
                        logger.trace("Received a MUC event.");
                    }
                    else
                    {
                        logger.trace(
                            "MUC event missed after " + waitFor + "ms.");
                    }
                    waitCount++;
                }
                catch (InterruptedException ex)
                {
                    logger.debug(
                        "Interrupted while waiting for a MUC event", ex);
                }
            }
        }

        /**
         * utility method to collect an event
         * @param evt the event to collect
         */
        private void collectEvent(EventObject evt)
        {
            synchronized (this)
            {
                logger.debug(
                    "Collected evt(" + collectedEvents.size() + ")= " + evt);
                collectedEvents.add(evt);
                notifyAll();
            }
        }

        public void invitationRejected(
            ChatRoomInvitationRejectedEvent evt)
        {
            collectEvent(evt);
        }

        public void invitationReceived(
            ChatRoomInvitationReceivedEvent evt)
        {
            collectEvent(evt);
        }

        public void localUserPresenceChanged(
            LocalUserChatRoomPresenceChangeEvent evt)
        {
            collectEvent(evt);
        }

        public void memberPresenceChanged(
            ChatRoomMemberPresenceChangeEvent evt)
        {
            collectEvent(evt);
        }

        public void messageReceived(
            ChatRoomMessageReceivedEvent evt)
        {
            if(skipSystemMsgs && evt.getEventType()
                == ChatRoomMessageReceivedEvent.SYSTEM_MESSAGE_RECEIVED)
                return;
            else
                collectEvent(evt);
        }

        public void messageDelivered(
            ChatRoomMessageDeliveredEvent evt)
        {
            collectEvent(evt);
        }

        public void messageDeliveryFailed(
            ChatRoomMessageDeliveryFailedEvent evt)
        {
            collectEvent(evt);
        }

        public void chatRoomPropertyChanged(
            ChatRoomMemberPropertyChangeEvent evt)
        {
            collectEvent(evt);
        }

        public void chatRoomPropertyChanged(
            ChatRoomPropertyChangeEvent evt)
        {
            collectEvent(evt);
        }

        public void chatRoomPropertyChangeFailed(
            ChatRoomPropertyChangeFailedEvent evt)
        {
            collectEvent(evt);
        }

        public void memberRoleChanged(
            ChatRoomMemberRoleChangeEvent evt)
        {
            collectEvent(evt);
        }

        public void localUserRoleChanged(
            ChatRoomLocalUserRoleChangeEvent evt)
        {
            collectEvent(evt);
        }
    }
}
