/*
 * 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.impl.gui.main.chat.conference;

import java.util.*;
import java.util.concurrent.*;

import javax.swing.*;
import javax.swing.SwingWorker;

import net.java.sip.communicator.impl.gui.*;
import net.java.sip.communicator.impl.gui.main.chat.*;
import net.java.sip.communicator.impl.gui.main.chat.history.*;
import net.java.sip.communicator.impl.gui.main.chatroomslist.*;
import net.java.sip.communicator.plugin.desktoputil.*;
import net.java.sip.communicator.service.gui.*;
import net.java.sip.communicator.service.muc.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.service.protocol.globalstatus.*;
import net.java.sip.communicator.util.*;
import net.java.sip.communicator.util.Logger;

import org.apache.commons.lang3.StringUtils;
import org.jitsi.utils.*;
import org.osgi.framework.*;

/**
 * The <tt>ConferenceChatManager</tt> is the one that manages both chat room and
 * ad-hoc chat rooms invitations.
 *
 * @author Yana Stamcheva
 * @author Lubomir Marinov
 * @author Valentin Martinet
 * @author Hristo Terezov
 */
public class ConferenceChatManager
    implements  ChatRoomMessageListener,
                ChatRoomInvitationListener,
                ChatRoomInvitationRejectionListener,
                AdHocChatRoomMessageListener,
                AdHocChatRoomInvitationListener,
                AdHocChatRoomInvitationRejectionListener,
                LocalUserChatRoomPresenceListener,
                LocalUserAdHocChatRoomPresenceListener,
                ServiceListener, ChatRoomLocalUserRoleListener
{
    /**
     * The object used for logging.
     */
    private static final Logger logger
        = Logger.getLogger(ConferenceChatManager.class);

    /**
     * Maps each history window to a <tt>ChatRoomWrapper</tt>.
     */
    private final Hashtable<ChatRoomWrapper, HistoryWindow> chatRoomHistory =
        new Hashtable<ChatRoomWrapper, HistoryWindow>();

    /**
     * The list of ad-hoc chat rooms.
     */
    private final AdHocChatRoomList adHocChatRoomList = new AdHocChatRoomList();

    /**
     * A list of all <tt>AdHocChatRoomListChangeListener</tt>-s.
     */
    private final Vector<AdHocChatRoomListChangeListener>
        adHoclistChangeListeners = new Vector<AdHocChatRoomListChangeListener>();

    /**
     * Creates an instance of <tt>ConferenceChatManager</tt>.
     */
    public ConferenceChatManager()
    {
        // Loads the chat rooms list in a separate thread.
        new Thread()
        {
            @Override
            public void run()
            {
                adHocChatRoomList.loadList();
            }
        }.start();

        GuiActivator.bundleContext.addServiceListener(this);

    }

    /**
     * Returns all chat room providers currently contained in the ad-hoc chat
     * room list.
     *
     * @return  all chat room providers currently contained in the ad-hoc chat
     * room list.
     */
    public AdHocChatRoomList getAdHocChatRoomList()
    {
        return adHocChatRoomList;
    }

    /**
     * Handles <tt>ChatRoomInvitationReceivedEvent</tt>-s.
     */
    public void invitationReceived(ChatRoomInvitationReceivedEvent evt)
    {
        InvitationReceivedDialog dialog
            = new InvitationReceivedDialog(
                    this,
                    evt.getSourceOperationSet(),
                    evt.getInvitation());

        dialog.setVisible(true);
    }

    public void invitationRejected(ChatRoomInvitationRejectedEvent evt) {}

    /**
     * Implements the <tt>ChatRoomMessageListener.messageDelivered</tt> method.
     * <br>
     * Shows the message in the conversation area and clears the write message
     * area.
     * @param evt the <tt>ChatRoomMessageDeliveredEvent</tt> that notified us
     * that the message was delivered to its destination
     */
    public void messageDelivered(ChatRoomMessageDeliveredEvent evt)
    {
        ChatRoom sourceChatRoom = (ChatRoom) evt.getSource();

        if (logger.isTraceEnabled())
            logger.trace(
                "MESSAGE DELIVERED to chat room: " + sourceChatRoom.getName());

        ChatPanel chatPanel = GuiActivator.getUIService().getChatWindowManager()
            .getMultiChat(sourceChatRoom, false);

        if(chatPanel != null)
        {
            String messageType;

            switch (evt.getEventType())
            {
            case ChatRoomMessageDeliveredEvent.CONVERSATION_MESSAGE_DELIVERED:
                messageType = Chat.OUTGOING_MESSAGE;
                break;
            case ChatRoomMessageDeliveredEvent.ACTION_MESSAGE_DELIVERED:
                messageType = Chat.ACTION_MESSAGE;
                break;
            default:
                messageType = null;
                break;
            }

            Message msg = evt.getMessage();

            chatPanel.addMessage(
                sourceChatRoom.getUserNickname(),
                null,
                evt.getTimestamp(),
                messageType,
                msg.getContent(),
                msg.getContentType(),
                msg.getMessageUID(),
                null);
        }
    }

    /**
     * Implements the <tt>ChatRoomMessageListener.messageReceived</tt> method.
     * <br>
     * Obtains the corresponding <tt>ChatPanel</tt> and process the message
     * there.
     * @param evt the <tt>ChatRoomMessageReceivedEvent</tt> that notified us
     * that a message has been received
     */
    public void messageReceived(ChatRoomMessageReceivedEvent evt)
    {
        ChatRoom sourceChatRoom = evt.getSourceChatRoom();
        ChatRoomMember sourceMember = evt.getSourceChatRoomMember();

        String messageType = null;

        switch (evt.getEventType())
        {
        case ChatRoomMessageReceivedEvent.CONVERSATION_MESSAGE_RECEIVED:
            messageType = Chat.INCOMING_MESSAGE;
            break;
        case ChatRoomMessageReceivedEvent.SYSTEM_MESSAGE_RECEIVED:
            messageType = Chat.SYSTEM_MESSAGE;
            break;
        case ChatRoomMessageReceivedEvent.ACTION_MESSAGE_RECEIVED:
            messageType = Chat.ACTION_MESSAGE;
            break;
        }

        if (logger.isTraceEnabled())
            logger.trace("MESSAGE RECEIVED from contact: "
            + sourceMember.getContactAddress());

        Message message = evt.getMessage();

        ChatPanel chatPanel = null;

        ChatWindowManager chatWindowManager
            = GuiActivator.getUIService().getChatWindowManager();

        boolean createWindow = false;
        String autoOpenConfig
            = MUCService.getChatRoomAutoOpenOption(
                sourceChatRoom.getParentProvider(),
                sourceChatRoom.getIdentifier());
        if(autoOpenConfig == null)
            autoOpenConfig = MUCService.DEFAULT_AUTO_OPEN_BEHAVIOUR;

        if(autoOpenConfig.equals(MUCService.OPEN_ON_ACTIVITY)
            || (autoOpenConfig.equals(MUCService.OPEN_ON_MESSAGE)
                && !evt.isHistoryMessage())
            || evt.isImportantMessage())
            createWindow = true;

        if(sourceChatRoom.isSystem())
        {
            ChatRoomProviderWrapper serverWrapper
                = GuiActivator.getMUCService().findServerWrapperFromProvider(
                    sourceChatRoom.getParentProvider());

            chatPanel = chatWindowManager.getMultiChat(
                serverWrapper.getSystemRoomWrapper(), createWindow);
        }
        else
        {
            chatPanel = chatWindowManager.getMultiChat(
                sourceChatRoom, createWindow, message.getMessageUID());
        }

        if(chatPanel == null)
            return;

        String messageContent = message.getContent();

        if (evt.isHistoryMessage())
        {
            Date timeStamp = chatPanel.getChatConversationPanel()
                .getLastIncomingMsgTimestamp();
            Collection<Object> c =
                chatPanel.getChatSession().getHistoryBeforeDate(
                    new Date(
                        timeStamp.equals(new Date(0))
                        ? System.currentTimeMillis() - 10000
                        : timeStamp.getTime()
                    ), 20);
            if (c.size() > 0)
            {
                boolean isPresent = false;
                for (Object o : c)
                {
                    if (o instanceof ChatRoomMessageDeliveredEvent)
                    {
                        ChatRoomMessageDeliveredEvent ev =
                            (ChatRoomMessageDeliveredEvent) o;
                        if (evt.getTimestamp() != null
                            && evt.getTimestamp().equals(ev.getTimestamp()))
                        {
                            isPresent = true;
                            break;
                        }
                    }
                    else if(o instanceof ChatRoomMessageReceivedEvent)
                    {
                        ChatRoomMessageReceivedEvent ev =
                            (ChatRoomMessageReceivedEvent) o;
                        if (evt.getTimestamp() != null
                            && evt.getTimestamp().equals(ev.getTimestamp()))
                        {
                            isPresent = true;
                            break;
                        }
                    }

                    Message m2 = evt.getMessage();

                    if(m2 != null
                        && m2.getContent().equals(messageContent))
                    {
                        isPresent = true;
                        break;
                    }
                }

                if (isPresent)
                    return;
            }
        }

        chatPanel.addMessage(
            sourceMember.getName(),
            null,
            evt.getTimestamp(),
            messageType,
            messageContent,
            message.getContentType(),
            message.getMessageUID(),
            null);

        if(createWindow)
            chatWindowManager.openChat(chatPanel, false);
    }

    /**
     * Implements the <tt>ChatRoomMessageListener.messageDeliveryFailed</tt>
     * method.
     * <br>
     * In the conversation area shows an error message, explaining the problem.
     * @param evt the <tt>ChatRoomMessageDeliveryFailedEvent</tt> that notified
     * us of a delivery failure
     */
    public void messageDeliveryFailed(ChatRoomMessageDeliveryFailedEvent evt)
    {
        ChatRoom sourceChatRoom = evt.getSourceChatRoom();

        String errorMsg = null;

        /*
         * FIXME ChatRoomMessageDeliveryFailedEvent#getSource() is not a Message
         * instance at the time of this writing and the attempt "(Message)
         * evt.getSource()" seems to be to get the message which failed to be
         * delivered. I'm not sure it's
         * ChatRoomMessageDeliveryFailedEvent#getMessage() but since it's the
         * only message I can get out of ChatRoomMessageDeliveryFailedEvent, I'm
         * using it.
         */
        Message sourceMessage = evt.getMessage();

        ChatRoomMember destMember = evt.getDestinationChatRoomMember();

        if (evt.getErrorCode()
            == MessageDeliveryFailedEvent.OFFLINE_MESSAGES_NOT_SUPPORTED)
        {
            errorMsg = GuiActivator.getResources().getI18NString(
                "service.gui.MSG_DELIVERY_NOT_SUPPORTED",
                new String[]{destMember.getName()});
        }
        else if (evt.getErrorCode()
            == MessageDeliveryFailedEvent.NETWORK_FAILURE)
        {
            errorMsg = GuiActivator.getResources()
                .getI18NString("service.gui.MSG_NOT_DELIVERED");
        }
        else if (evt.getErrorCode()
            == MessageDeliveryFailedEvent.PROVIDER_NOT_REGISTERED)
        {
            errorMsg = GuiActivator.getResources().getI18NString(
                "service.gui.MSG_SEND_CONNECTION_PROBLEM");
        }
        else if (evt.getErrorCode()
            == MessageDeliveryFailedEvent.INTERNAL_ERROR)
        {
            errorMsg = GuiActivator.getResources().getI18NString(
                "service.gui.MSG_DELIVERY_INTERNAL_ERROR");
        }
        else if (evt.getErrorCode()
            == ChatRoomMessageDeliveryFailedEvent.FORBIDDEN)
        {
            errorMsg = GuiActivator.getResources().getI18NString(
                "service.gui.CHAT_ROOM_SEND_MSG_FORBIDDEN");
        }
        else if (evt.getErrorCode()
            == ChatRoomMessageDeliveryFailedEvent.UNSUPPORTED_OPERATION)
        {
            errorMsg =
                GuiActivator.getResources().getI18NString(
                    "service.gui.MSG_DELIVERY_UNSUPPORTED_OPERATION");
        }
        else
        {
            errorMsg = GuiActivator.getResources().getI18NString(
                 "service.gui.MSG_DELIVERY_UNKNOWN_ERROR");
        }

        String reason = evt.getReason();
        if (reason != null)
            errorMsg += " " + GuiActivator.getResources().getI18NString(
                "service.gui.ERROR_WAS",
                new String[]{reason});

        ChatWindowManager chatWindowManager
            = GuiActivator.getUIService().getChatWindowManager();
        ChatPanel chatPanel
            = chatWindowManager.getMultiChat(sourceChatRoom, true);

        chatPanel.addMessage(
            destMember != null ? destMember.getName()
                : sourceChatRoom.getName(),
            new Date(),
            Chat.OUTGOING_MESSAGE,
            sourceMessage.getContent(),
            sourceMessage.getContentType());

        chatPanel.addErrorMessage(
            destMember != null ? destMember.getName()
                : sourceChatRoom.getName(),
            errorMsg);

        chatWindowManager.openChat(chatPanel, false);
    }

    /**
     * Implements the
     * <tt>LocalUserAdHocChatRoomPresenceListener.localUserPresenceChanged</tt>
     * method
     *
     * @param evt the <tt>LocalUserAdHocChatRoomPresenceChangeEvent</tt> that
     * notified us of a presence change
     */
    public void localUserAdHocPresenceChanged(
            LocalUserAdHocChatRoomPresenceChangeEvent evt)
    {
        AdHocChatRoom sourceAdHocChatRoom = evt.getAdHocChatRoom();
        AdHocChatRoomWrapper adHocChatRoomWrapper
            = adHocChatRoomList
                .findChatRoomWrapperFromAdHocChatRoom(sourceAdHocChatRoom);

        String eventType = evt.getEventType();

        if (LocalUserAdHocChatRoomPresenceChangeEvent
                .LOCAL_USER_JOINED.equals(eventType))
        {
            if(adHocChatRoomWrapper != null)
            {
                this.fireAdHocChatRoomListChangedEvent(
                        adHocChatRoomWrapper,
                        AdHocChatRoomListChangeEvent.AD_HOC_CHAT_ROOM_CHANGED);

                ChatWindowManager chatWindowManager
                    = GuiActivator.getUIService().getChatWindowManager();
                ChatPanel chatPanel
                    = chatWindowManager
                        .getMultiChat(adHocChatRoomWrapper, true);

                // Check if we have already opened a chat window for this chat
                // wrapper and load the real chat room corresponding to the
                // wrapper.
                if(chatPanel.isShown())
                    ((AdHocConferenceChatSession) chatPanel.getChatSession())
                        .loadChatRoom(sourceAdHocChatRoom);
                else
                    chatWindowManager.openChat(chatPanel, true);
            }

            sourceAdHocChatRoom.addMessageListener(this);
        }
        else if (evt.getEventType().equals(
            LocalUserAdHocChatRoomPresenceChangeEvent.LOCAL_USER_JOIN_FAILED))
        {
            GuiActivator.getAlertUIService().showAlertPopup(
                GuiActivator.getResources().getI18NString("service.gui.ERROR"),
                GuiActivator.getResources().getI18NString(
                    "service.gui.FAILED_TO_JOIN_CHAT_ROOM",
                    new String[]{sourceAdHocChatRoom.getName()})
                    + evt.getReason());
        }
        else if (LocalUserAdHocChatRoomPresenceChangeEvent
                        .LOCAL_USER_LEFT.equals(eventType)
                    || LocalUserAdHocChatRoomPresenceChangeEvent
                            .LOCAL_USER_DROPPED.equals(eventType))
        {
            this.closeAdHocChatRoom(adHocChatRoomWrapper);

            // Need to refresh the chat room's list in order to change
            // the state of the chat room to offline.
            fireAdHocChatRoomListChangedEvent(
                    adHocChatRoomWrapper,
                    AdHocChatRoomListChangeEvent.AD_HOC_CHAT_ROOM_CHANGED);

            sourceAdHocChatRoom.removeMessageListener(this);
        }
    }

    /**
     * Implements the
     * <tt>LocalUserChatRoomPresenceListener.localUserPresenceChanged</tt>
     * method.
     * @param evt the <tt>LocalUserChatRoomPresenceChangeEvent</tt> that
     * notified us
     */
    public void localUserPresenceChanged(
        final LocalUserChatRoomPresenceChangeEvent evt)
    {
        if(!SwingUtilities.isEventDispatchThread())
        {
            SwingUtilities.invokeLater(new Runnable()
            {
                @Override
                public void run()
                {
                    localUserPresenceChanged(evt);
                }
            });
            return;
        }

        ChatRoom sourceChatRoom = evt.getChatRoom();
        ChatRoomWrapper chatRoomWrapper
            = GuiActivator.getMUCService().findChatRoomWrapperFromChatRoom(
                sourceChatRoom);

        String eventType = evt.getEventType();

        if (LocalUserChatRoomPresenceChangeEvent
                .LOCAL_USER_JOINED.equals(eventType))
        {
            if(chatRoomWrapper != null)
            {
                GuiActivator.getMUCService().fireChatRoomListChangedEvent(
                    chatRoomWrapper,
                    ChatRoomListChangeEvent.CHAT_ROOM_CHANGED);

                boolean createWindow = false;

                String autoOpenConfig
                = MUCService.getChatRoomAutoOpenOption(
                    sourceChatRoom.getParentProvider(),
                    sourceChatRoom.getIdentifier());

                if(autoOpenConfig != null
                    && autoOpenConfig.equals(MUCService.OPEN_ON_ACTIVITY))
                    createWindow = true;

                ChatWindowManager chatWindowManager
                    = GuiActivator.getUIService().getChatWindowManager();
                ChatPanel chatPanel
                    = chatWindowManager.getMultiChat(
                        chatRoomWrapper, createWindow);

                if(chatPanel != null)
                {
                    chatPanel.setChatIcon(
                        chatPanel.getChatSession().getChatStatusIcon());

                    // Check if we have already opened a chat window for this chat
                    // wrapper and load the real chat room corresponding to the
                    // wrapper.
                    if(chatPanel.isShown())
                    {
                        ((ConferenceChatSession) chatPanel.getChatSession())
                            .loadChatRoom(sourceChatRoom);
                    }
                    else
                    {
                        chatWindowManager.openChat(chatPanel, true);
                    }
                }
            }

            if (sourceChatRoom.isSystem())
            {
                ChatRoomProviderWrapper serverWrapper
                    = GuiActivator.getMUCService()
                        .findServerWrapperFromProvider(
                            sourceChatRoom.getParentProvider());

                serverWrapper.setSystemRoom(sourceChatRoom);
            }

            sourceChatRoom.addMessageListener(this);
            sourceChatRoom.addLocalUserRoleListener(this);
        }
        else if (LocalUserChatRoomPresenceChangeEvent
                    .LOCAL_USER_JOIN_FAILED.equals(eventType))
        {
            GuiActivator.getAlertUIService().showAlertPopup(
                GuiActivator.getResources().getI18NString("service.gui.ERROR"),
                GuiActivator.getResources().getI18NString(
                    "service.gui.FAILED_TO_JOIN_CHAT_ROOM",
                    new String[]{sourceChatRoom.getName()})
                    + evt.getReason());
        }
        else if (LocalUserChatRoomPresenceChangeEvent
                        .LOCAL_USER_LEFT.equals(eventType)
                    || LocalUserChatRoomPresenceChangeEvent
                            .LOCAL_USER_KICKED.equals(eventType)
                    || LocalUserChatRoomPresenceChangeEvent
                            .LOCAL_USER_DROPPED.equals(eventType))
        {
            if(chatRoomWrapper != null)
            {
                if(StringUtils.isEmpty(evt.getReason()))
                {
                    GuiActivator.getUIService()
                        .closeChatRoomWindow(chatRoomWrapper);
                }
                else
                {
                    // send some system messages informing for the
                    // reason of leaving
                    ChatWindowManager chatWindowManager
                        = GuiActivator.getUIService().getChatWindowManager();

                    ChatPanel chatPanel = chatWindowManager.getMultiChat(
                        sourceChatRoom, false);

                    if(chatPanel != null)
                    {
                        chatPanel.addMessage(
                            sourceChatRoom.getName(),
                            null,
                            new Date(),
                            Chat.SYSTEM_MESSAGE,
                            evt.getReason(),
                            OperationSetBasicInstantMessaging.DEFAULT_MIME_TYPE,
                            null,
                            null);

                        // print and the alternate address
                        if(StringUtils.isNotEmpty(
                                evt.getAlternateAddress()))
                        {
                            chatPanel.addMessage(
                                sourceChatRoom.getName(),
                                null,
                                new Date(),
                                Chat.SYSTEM_MESSAGE,
                                GuiActivator.getResources().getI18NString(
                                    "service.gui.CHAT_ROOM_ALTERNATE_ADDRESS",
                                    new String[]{evt.getAlternateAddress()}),
                                OperationSetBasicInstantMessaging
                                    .DEFAULT_MIME_TYPE,
                                null,
                                null);
                        }
                    }
                }

                // Need to refresh the chat room's list in order to change
                // the state of the chat room to offline.

                GuiActivator.getMUCService().fireChatRoomListChangedEvent(
                    chatRoomWrapper,
                    ChatRoomListChangeEvent.CHAT_ROOM_CHANGED);
            }

            sourceChatRoom.removeMessageListener(this);
            sourceChatRoom.removelocalUserRoleListener(this);
        }
    }


    /**
     * Called to accept an incoming invitation. Adds the invitation chat room
     * to the list of chat rooms and joins it.
     *
     * @param invitation the invitation to accept
     * @param multiUserChatOpSet the operation set for chat conferencing
     * @throws OperationFailedException if the accept fails
     */
    public void acceptInvitation(
        AdHocChatRoomInvitation invitation,
        OperationSetAdHocMultiUserChat multiUserChatOpSet)
        throws OperationFailedException
    {
        AdHocChatRoom chatRoom = invitation.getTargetAdHocChatRoom();

        chatRoom.join();
    }

    /**
     * Rejects the given invitation with the specified reason.
     *
     * @param multiUserChatAdHocOpSet the operation set to use for rejecting the
     * invitation
     * @param invitation the invitation to reject
     * @param reason the reason for the rejection
     */
    public void rejectInvitation(
            OperationSetAdHocMultiUserChat     multiUserChatAdHocOpSet,
            AdHocChatRoomInvitation         invitation,
            String                             reason)
    {
         multiUserChatAdHocOpSet.rejectInvitation(invitation, reason);
    }

    /**
     * Creates an ad-hoc chat room, by specifying the ad-hoc chat room name, the
     * parent protocol provider and eventually, the contacts invited to
     * participate in this ad-hoc chat room.
     *
     * @param protocolProvider the parent protocol provider.
     * @param contacts the contacts invited when creating the chat room.
     * @param reason the reason for this invitation
     * @return the <tt>AdHocChatRoomWrapper</tt> corresponding to the created
     * ad hoc chat room
     */
    public AdHocChatRoomWrapper createAdHocChatRoom(
        ProtocolProviderService protocolProvider,
        Collection<String> contacts,
        String reason)
    {
        AdHocChatRoomWrapper chatRoomWrapper = null;

        OperationSetAdHocMultiUserChat groupChatOpSet
            = protocolProvider
                .getOperationSet(OperationSetAdHocMultiUserChat.class);

        // If there's no group chat operation set we have nothing to do here.
        if (groupChatOpSet == null)
            return null;

        AdHocChatRoom chatRoom = null;

        try
        {
            java.util.List<String> members = new LinkedList<String>();

            for(String address : contacts)
                members.add(address);

            chatRoom = groupChatOpSet.createAdHocChatRoom(
                "chatroom-" + new Date().getTime(), members, reason);
        }
        catch (OperationFailedException ex)
        {
            new ErrorDialog(
                GuiActivator.getUIService().getMainFrame(),
                GuiActivator.getResources().getI18NString("service.gui.ERROR"),
                GuiActivator.getResources().getI18NString(
                    "service.gui.CREATE_CHAT_ROOM_ERROR",
                    new String[]{protocolProvider.getProtocolDisplayName()}),
                    ex)
            .showDialog();
        }
        catch (OperationNotSupportedException ex)
        {
            new ErrorDialog(
                GuiActivator.getUIService().getMainFrame(),
                GuiActivator.getResources().getI18NString("service.gui.ERROR"),
                GuiActivator.getResources().getI18NString(
                    "service.gui.CREATE_CHAT_ROOM_ERROR",
                    new String[]{protocolProvider.getProtocolDisplayName()}),
                    ex)
            .showDialog();
        }

        if(chatRoom != null)
        {
            AdHocChatRoomProviderWrapper parentProvider
                = adHocChatRoomList.findServerWrapperFromProvider(
                        protocolProvider);

            chatRoomWrapper = new AdHocChatRoomWrapper(
                    parentProvider, chatRoom);
            parentProvider.addAdHocChatRoom(chatRoomWrapper);
            adHocChatRoomList.addAdHocChatRoom(chatRoomWrapper);

            fireAdHocChatRoomListChangedEvent(
                chatRoomWrapper,
                AdHocChatRoomListChangeEvent.AD_HOC_CHAT_ROOM_ADDED);
        }

        return chatRoomWrapper;
    }

    /**
     * Joins the given ad-hoc chat room
     *
     * @param chatRoomWrapper
     */
    public void joinChatRoom(AdHocChatRoomWrapper chatRoomWrapper)
    {
        AdHocChatRoom chatRoom = chatRoomWrapper.getAdHocChatRoom();

        if(chatRoom == null)
        {
            new ErrorDialog(
               GuiActivator.getUIService().getMainFrame(),
               GuiActivator.getResources().getI18NString("service.gui.WARNING"),
               GuiActivator.getResources().getI18NString(
                        "service.gui.CHAT_ROOM_NOT_CONNECTED",
                        new String[]{chatRoomWrapper.getAdHocChatRoomName()}))
                    .showDialog();

            return;
        }

        new JoinAdHocChatRoomTask(chatRoomWrapper).execute();
    }

    /**
     * Removes the given chat room from the UI.
     *
     * @param chatRoomWrapper the chat room to remove.
     */
    public void removeChatRoom(ChatRoomWrapper chatRoomWrapper)
    {
        ChatRoom chatRoom = chatRoomWrapper.getChatRoom();

        if (chatRoom != null)
            leaveChatRoom(chatRoomWrapper);

        GuiActivator.getUIService().closeChatRoomWindow(chatRoomWrapper);

        GuiActivator.getMUCService().removeChatRoom(chatRoomWrapper);

    }

    /**
     * Joins the given chat room and manages all the exceptions that could
     * occur during the join process.
     *
     * @param chatRoom the chat room to join
     */
    public void joinChatRoom(AdHocChatRoom chatRoom)
    {
        AdHocChatRoomWrapper chatRoomWrapper
            = adHocChatRoomList.findChatRoomWrapperFromAdHocChatRoom(chatRoom);

        if(chatRoomWrapper == null)
        {
            AdHocChatRoomProviderWrapper parentProvider
            = adHocChatRoomList.findServerWrapperFromProvider(
                chatRoom.getParentProvider());

            chatRoomWrapper =
                new AdHocChatRoomWrapper(parentProvider, chatRoom);

            adHocChatRoomList.addAdHocChatRoom(chatRoomWrapper);

            fireAdHocChatRoomListChangedEvent(
                chatRoomWrapper,
                AdHocChatRoomListChangeEvent.AD_HOC_CHAT_ROOM_ADDED);
        }

        this.joinChatRoom(chatRoomWrapper);

        ChatWindowManager chatWindowManager
            = GuiActivator.getUIService().getChatWindowManager();

        chatWindowManager
            .openChat(
                chatWindowManager.getMultiChat(chatRoomWrapper, true),
                true);
    }

    /**
     * Leaves the given <tt>ChatRoom</tt>.
     *
     * @param chatRoomWrapper the <tt>ChatRoom</tt> to leave.
     */
    public void leaveChatRoom(ChatRoomWrapper chatRoomWrapper)
    {
        ChatRoomWrapper leavedRoomWrapped
            = GuiActivator.getMUCService().leaveChatRoom(chatRoomWrapper);
        if(leavedRoomWrapped != null)
            GuiActivator.getUIService().closeChatRoomWindow(leavedRoomWrapped);
    }

    /**
     * Leaves the given <tt>ChatRoom</tt>.
     *
     * @param chatRoomWrapper the <tt>ChatRoom</tt> to leave.
     */
    public void leaveChatRoom(AdHocChatRoomWrapper chatRoomWrapper)
    {
        AdHocChatRoom chatRoom = chatRoomWrapper.getAdHocChatRoom();

        if (chatRoom != null)
        {
            chatRoom.leave();
        }
        else
        {
            new ErrorDialog(
               GuiActivator.getUIService().getMainFrame(),
               GuiActivator.getResources().getI18NString("service.gui.WARNING"),
               GuiActivator.getResources().getI18NString(
                   "service.gui.CHAT_ROOM_LEAVE_NOT_CONNECTED"))
                   .showDialog();
        }
    }

    /**
     * Checks if there's an open history window for the given chat room.
     *
     * @param chatRoomWrapper the chat room wrapper to check for
     * @return TRUE if there's an opened history window for the given chat room,
     *         FALSE otherwise.
     */
    public boolean containsHistoryWindowForChatRoom(
        ChatRoomWrapper chatRoomWrapper)
    {
        return chatRoomHistory.containsKey(chatRoomWrapper);
    }

    /**
     * Returns the history window for the given chat room.
     *
     * @param chatRoomWrapper the chat room wrapper to search for
     * @return the history window for the given chat room
     */
    public HistoryWindow getHistoryWindowForChatRoom(
        ChatRoomWrapper chatRoomWrapper)
    {
        return chatRoomHistory.get(chatRoomWrapper);
    }

    /**
     * Adds a history window for a given chat room in the table of opened
     * history windows.
     *
     * @param chatRoomWrapper the chat room wrapper to add
     * @param historyWindow the history window to add
     */
    public void addHistoryWindowForChatRoom(ChatRoomWrapper chatRoomWrapper,
        HistoryWindow historyWindow)
    {
        chatRoomHistory.put(chatRoomWrapper, historyWindow);
    }

    /**
     * Removes the history window for the given chat room.
     *
     * @param chatRoomWrapper the chat room wrapper to remove the history window
     */
    public void removeHistoryWindowForChatRoom(ChatRoomWrapper chatRoomWrapper)
    {
        chatRoomHistory.remove(chatRoomWrapper);
    }

    /**
     * Adds the given <tt>AdHocChatRoomListChangeListener</tt> that will listen
     * for all changes of the chat room list data model.
     *
     * @param l the listener to add.
     */
    public void addAdHocChatRoomListChangeListener(
            AdHocChatRoomListChangeListener l)
    {
        synchronized (adHoclistChangeListeners)
        {
            adHoclistChangeListeners.add(l);
        }
    }

    /**
     * Removes the given <tt>AdHocChatRoomListChangeListener</tt>.
     *
     * @param l the listener to remove.
     */
    public void removeAdHocChatRoomListChangeListener(
        AdHocChatRoomListChangeListener l)
    {
        synchronized (adHoclistChangeListeners)
        {
            adHoclistChangeListeners.remove(l);
        }
    }

    /**
     * Notifies all interested listeners that a change in the chat room list
     * model has occurred.
     * @param adHocChatRoomWrapper the chat room wrapper that identifies the
     * chat room
     * @param eventID the identifier of the event
     */
    private void fireAdHocChatRoomListChangedEvent(
                                    AdHocChatRoomWrapper adHocChatRoomWrapper,
                                    int                  eventID)
    {
        AdHocChatRoomListChangeEvent evt
            = new AdHocChatRoomListChangeEvent(adHocChatRoomWrapper, eventID);

        for (AdHocChatRoomListChangeListener l : adHoclistChangeListeners)
        {
            l.contentChanged(evt);
        }
    }


    /**
     * Closes the chat corresponding to the given ad-hoc chat room wrapper, if
     * such exists.
     *
     * @param chatRoomWrapper the ad-hoc chat room wrapper for which we search a
     * chat to close.
     */
    private void closeAdHocChatRoom(AdHocChatRoomWrapper chatRoomWrapper)
    {
        ChatWindowManager chatWindowManager
            = GuiActivator.getUIService().getChatWindowManager();
        ChatPanel chatPanel
            = chatWindowManager.getMultiChat(chatRoomWrapper, false);

        if (chatPanel != null)
            chatWindowManager.closeChat(chatPanel);
    }

    /**
     * Handles <tt>ServiceEvent</tt>s triggered by adding or removing a
     * ProtocolProviderService. Updates the list of available chat rooms and
     * chat room servers.
     *
     * @param event The event to handle.
     */
    public void serviceChanged(ServiceEvent event)
    {
        // if the event is caused by a bundle being stopped, we don't want to
        // know
        if (event.getServiceReference().getBundle().getState()
                == Bundle.STOPPING)
            return;

        Object service = GuiActivator.bundleContext.getService(event
            .getServiceReference());

        // we don't care if the source service is not a protocol provider
        if (!(service instanceof ProtocolProviderService))
            return;

        ProtocolProviderService protocolProvider
            = (ProtocolProviderService) service;


        Object multiUserChatAdHocOpSet
            = protocolProvider
            .getOperationSet(OperationSetAdHocMultiUserChat.class);

        if (multiUserChatAdHocOpSet != null)
        {
             if (event.getType() == ServiceEvent.REGISTERED)
             {
                 adHocChatRoomList.addChatProvider(protocolProvider);
             }
             else if (event.getType() == ServiceEvent.UNREGISTERING)
             {
                 adHocChatRoomList.removeChatProvider(protocolProvider);
             }
        }
    }

    /**
     * Joins an ad-hoc chat room in an asynchronous way.
     */
    private static class JoinAdHocChatRoomTask
        extends SwingWorker<String, Object>
    {
        private static final String SUCCESS = "Success";

        private static final String AUTHENTICATION_FAILED
            = "AuthenticationFailed";

        private static final String REGISTRATION_REQUIRED
            = "RegistrationRequired";

        private static final String PROVIDER_NOT_REGISTERED
            = "ProviderNotRegistered";

        private static final String SUBSCRIPTION_ALREADY_EXISTS
            = "SubscriptionAlreadyExists";

        private static final String UNKNOWN_ERROR
            = "UnknownError";

        private final AdHocChatRoomWrapper adHocChatRoomWrapper;

        JoinAdHocChatRoomTask(AdHocChatRoomWrapper chatRoomWrapper)
        {
            this.adHocChatRoomWrapper = chatRoomWrapper;
        }

        /**
         * @override {@link SwingWorker}{@link #doInBackground()} to perform
         * all asynchronous tasks.
         * @return SUCCESS if success, otherwise the error code
         */
        @Override
        public String doInBackground()
        {
            AdHocChatRoom chatRoom = adHocChatRoomWrapper.getAdHocChatRoom();

            try
            {
                chatRoom.join();

                return SUCCESS;
            }
            catch (OperationFailedException e)
            {
                if (logger.isTraceEnabled())
                    logger.trace("Failed to join ad-hoc chat room: "
                    + chatRoom.getName(), e);

                switch (e.getErrorCode())
                {
                case OperationFailedException.AUTHENTICATION_FAILED:
                    return AUTHENTICATION_FAILED;
                case OperationFailedException.REGISTRATION_REQUIRED:
                    return REGISTRATION_REQUIRED;
                case OperationFailedException.PROVIDER_NOT_REGISTERED:
                    return PROVIDER_NOT_REGISTERED;
                case OperationFailedException.SUBSCRIPTION_ALREADY_EXISTS:
                    return SUBSCRIPTION_ALREADY_EXISTS;
                default:
                    return UNKNOWN_ERROR;
                }
            }
        }

        /**
         * @override {@link SwingWorker}{@link #done()} to perform UI changes
         * after the ad-hoc chat room join task has finished.
         */
        @Override
        protected void done()
        {
            String returnCode = null;
            try
            {
                returnCode = get();
            }
            catch (InterruptedException ignore)
            {}
            catch (ExecutionException ignore)
            {}

            ConfigurationUtils.updateChatRoomStatus(
                adHocChatRoomWrapper.getParentProvider().getProtocolProvider(),
                adHocChatRoomWrapper.getAdHocChatRoomID(),
                GlobalStatusEnum.ONLINE_STATUS);

            String errorMessage = null;
            if(PROVIDER_NOT_REGISTERED.equals(returnCode))
            {
                errorMessage
                    = GuiActivator.getResources()
                        .getI18NString("service.gui.CHAT_ROOM_NOT_CONNECTED",
                        new String[]{
                            adHocChatRoomWrapper.getAdHocChatRoomName()});
            }
            else if(SUBSCRIPTION_ALREADY_EXISTS.equals(returnCode))
            {
                errorMessage
                    = GuiActivator.getResources()
                        .getI18NString("service.gui.CHAT_ROOM_ALREADY_JOINED",
                            new String[]{
                            adHocChatRoomWrapper.getAdHocChatRoomName()});
            }
            else
            {
                errorMessage
                    = GuiActivator.getResources()
                        .getI18NString("service.gui.FAILED_TO_JOIN_CHAT_ROOM",
                            new String[]{
                            adHocChatRoomWrapper.getAdHocChatRoomName()});
            }

            if (!SUCCESS.equals(returnCode)
                    && !AUTHENTICATION_FAILED.equals(returnCode))
            {
                GuiActivator.getAlertUIService().showAlertPopup(
                    GuiActivator.getResources().getI18NString(
                        "service.gui.ERROR"), errorMessage);
            }
        }
    }


    /**
     * Indicates that an invitation has been received and opens the invitation
     * dialog to notify the user.
     * @param evt the <tt>AdHocChatRoomInvitationReceivedEvent</tt> that
     * notified us
     */
    public void invitationReceived(AdHocChatRoomInvitationReceivedEvent evt)
    {
        if (logger.isInfoEnabled())
            logger.info("Invitation received: "+evt.toString());
        OperationSetAdHocMultiUserChat multiUserChatOpSet
            = evt.getSourceOperationSet();

        InvitationReceivedDialog dialog = new InvitationReceivedDialog(
                this, multiUserChatOpSet, evt.getInvitation());

        dialog.setVisible(true);
    }

     /**
     * Implements the <tt>AdHocChatRoomMessageListener.messageDelivered</tt>
     * method.
     * <br>
     * Shows the message in the conversation area and clears the write message
     * area.
     * @param evt the <tt>AdHocChatRoomMessageDeliveredEvent</tt> that notified
     * us
     */
    public void messageDelivered(AdHocChatRoomMessageDeliveredEvent evt)
    {
        AdHocChatRoom sourceChatRoom = (AdHocChatRoom) evt.getSource();

        if (logger.isInfoEnabled())
            logger.info("MESSAGE DELIVERED to ad-hoc chat room: "
            + sourceChatRoom.getName());

        ChatPanel chatPanel
            = GuiActivator
                .getUIService()
                    .getChatWindowManager()
                        .getMultiChat(sourceChatRoom, false);

        if(chatPanel != null)
        {
            String messageType;
            switch (evt.getEventType())
            {
            case AdHocChatRoomMessageDeliveredEvent
                    .CONVERSATION_MESSAGE_DELIVERED:
                messageType = Chat.OUTGOING_MESSAGE;
                break;
            case AdHocChatRoomMessageDeliveredEvent.ACTION_MESSAGE_DELIVERED:
                messageType = Chat.ACTION_MESSAGE;
                break;
            default:
                messageType = null;
            }

            Message msg = evt.getMessage();

            chatPanel
                .addMessage(
                    sourceChatRoom
                        .getParentProvider().getAccountID().getUserID(),
                    null,
                    evt.getTimestamp(),
                    messageType,
                    msg.getContent(),
                    msg.getContentType(),
                    msg.getMessageUID(),
                    null);
        }
        else
        {
            logger.error("chat panel is null, message NOT DELIVERED !");
        }
    }

    /**
     * Implements <tt>AdHocChatRoomMessageListener.messageDeliveryFailed</tt>
     * method.
     * <br>
     * In the conversation area shows an error message, explaining the problem.
     * @param evt the <tt>AdHocChatRoomMessageDeliveryFailedEvent</tt> that
     * notified us
     */
    public void messageDeliveryFailed(
            AdHocChatRoomMessageDeliveryFailedEvent evt)
    {
        AdHocChatRoom sourceChatRoom = evt.getSourceChatRoom();
        Message sourceMessage = evt.getMessage();
        Contact destParticipant = evt.getDestinationParticipant();

        String errorMsg = null;
        if (evt.getErrorCode()
                == MessageDeliveryFailedEvent.OFFLINE_MESSAGES_NOT_SUPPORTED)
        {
            errorMsg = GuiActivator.getResources().getI18NString(
                    "service.gui.MSG_DELIVERY_NOT_SUPPORTED",
                    new String[]{destParticipant.getDisplayName()});
        }
        else if (evt.getErrorCode()
                == MessageDeliveryFailedEvent.NETWORK_FAILURE)
        {
            errorMsg = GuiActivator.getResources()
                .getI18NString("service.gui.MSG_NOT_DELIVERED");
        }
        else if (evt.getErrorCode()
                == MessageDeliveryFailedEvent.PROVIDER_NOT_REGISTERED)
        {
            errorMsg = GuiActivator.getResources().getI18NString(
                    "service.gui.MSG_SEND_CONNECTION_PROBLEM");
        }
        else if (evt.getErrorCode()
                == MessageDeliveryFailedEvent.INTERNAL_ERROR)
        {
            errorMsg = GuiActivator.getResources().getI18NString(
                    "service.gui.MSG_DELIVERY_INTERNAL_ERROR");
        }
        else if (evt.getErrorCode()
                == MessageDeliveryFailedEvent.UNSUPPORTED_OPERATION)
        {
            errorMsg = GuiActivator.getResources().getI18NString(
                "service.gui.MSG_DELIVERY_UNSUPPORTED_OPERATION");
        }
        else
        {
            errorMsg = GuiActivator.getResources().getI18NString(
                    "service.gui.MSG_DELIVERY_UNKNOWN_ERROR");
        }

        ChatWindowManager chatWindowManager
            = GuiActivator.getUIService().getChatWindowManager();
        ChatPanel chatPanel
            = chatWindowManager.getMultiChat(sourceChatRoom, true);

        chatPanel.addMessage(
                destParticipant.getDisplayName(),
                new Date(),
                Chat.OUTGOING_MESSAGE,
                sourceMessage.getContent(),
                sourceMessage.getContentType());

        chatPanel.addErrorMessage(
                destParticipant.getDisplayName(),
                errorMsg);

        chatWindowManager.openChat(chatPanel, false);
    }

    /**
     * Implements the <tt>AdHocChatRoomMessageListener.messageReceived</tt>
     * method.
     * <br>
     * Obtains the corresponding <tt>ChatPanel</tt> and process the message
     * there.
     * @param evt the <tt>AdHocChatRoomMessageReceivedEvent</tt> that notified
     * us
     */
    public void messageReceived(AdHocChatRoomMessageReceivedEvent evt)
    {
        AdHocChatRoom sourceChatRoom = evt.getSourceChatRoom();
        Contact sourceParticipant = evt.getSourceChatRoomParticipant();

        String messageType = null;

        switch (evt.getEventType())
        {
        case AdHocChatRoomMessageReceivedEvent.CONVERSATION_MESSAGE_RECEIVED:
            messageType = Chat.INCOMING_MESSAGE;
            break;
        case AdHocChatRoomMessageReceivedEvent.SYSTEM_MESSAGE_RECEIVED:
            messageType = Chat.SYSTEM_MESSAGE;
            break;
        case AdHocChatRoomMessageReceivedEvent.ACTION_MESSAGE_RECEIVED:
            messageType = Chat.ACTION_MESSAGE;
            break;
        }

        if (logger.isInfoEnabled())
            logger.info("MESSAGE RECEIVED from contact: "
            + sourceParticipant.getAddress());

        Message message = evt.getMessage();

        ChatWindowManager chatWindowManager
            = GuiActivator.getUIService().getChatWindowManager();
        ChatPanel chatPanel
            = chatWindowManager
                .getMultiChat(sourceChatRoom, true, message.getMessageUID());

        String messageContent = message.getContent();

        chatPanel.addMessage(
            sourceParticipant.getDisplayName(),
            null,
            evt.getTimestamp(),
            messageType,
            messageContent,
            message.getContentType(),
            message.getMessageUID(),
            null);

        chatWindowManager.openChat(chatPanel, false);
    }

    public void invitationRejected(AdHocChatRoomInvitationRejectedEvent evt) {}

    @Override
    public void localUserRoleChanged(ChatRoomLocalUserRoleChangeEvent evt)
    {
        if(evt.isInitial())
            return;
        ChatRoom sourceChatRoom = evt.getSourceChatRoom();
        ChatRoomWrapper chatRoomWrapper
            = GuiActivator.getMUCService().findChatRoomWrapperFromChatRoom(
                sourceChatRoom);
        ChatWindowManager chatWindowManager
            = GuiActivator.getUIService().getChatWindowManager();
        ChatPanel chatPanel
            = chatWindowManager.getMultiChat(chatRoomWrapper, true);
        chatWindowManager.openChat(chatPanel, true);
    }

}
