/**
 * Copyright (C) 2004-2011 Jive Software. All rights reserved.
 *
 * 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 org.jivesoftware.spark.ui.rooms;

import org.jivesoftware.resource.Default;
import org.jivesoftware.resource.Res;
import org.jivesoftware.resource.SparkRes;
import org.jivesoftware.smack.SmackException;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.XMPPException;
import org.jivesoftware.smack.filter.*;
import org.jivesoftware.smack.packet.*;
import org.jivesoftware.smack.roster.Roster;
import org.jivesoftware.smack.roster.RosterEntry;
import org.jivesoftware.smack.util.StringUtils;
import org.jivesoftware.smackx.carbons.packet.CarbonExtension;
import org.jivesoftware.smackx.chatstates.ChatState;
import org.jivesoftware.smackx.chatstates.packet.ChatStateExtension;
import org.jivesoftware.smackx.delay.packet.DelayInformation;
import org.jivesoftware.smackx.jiveproperties.packet.JivePropertiesExtension;
import org.jivesoftware.smackx.muc.packet.MUCUser;
import org.jivesoftware.smackx.xevent.MessageEventManager;
import org.jivesoftware.smackx.xevent.packet.MessageEvent;
import org.jivesoftware.spark.ChatManager;
import org.jivesoftware.spark.PresenceManager;
import org.jivesoftware.spark.SparkManager;
import org.jivesoftware.spark.ui.*;
import org.jivesoftware.spark.util.ModelUtil;
import org.jivesoftware.spark.util.log.Log;
import org.jivesoftware.sparkimpl.plugin.manager.Enterprise;
import org.jivesoftware.sparkimpl.plugin.transcripts.ChatTranscript;
import org.jivesoftware.sparkimpl.plugin.transcripts.ChatTranscripts;
import org.jivesoftware.sparkimpl.plugin.transcripts.HistoryMessage;
import org.jivesoftware.sparkimpl.profile.VCardManager;
import org.jivesoftware.sparkimpl.settings.local.LocalPreferences;
import org.jivesoftware.sparkimpl.settings.local.SettingsManager;
import org.jxmpp.jid.*;
import org.jxmpp.jid.parts.Localpart;
import org.jxmpp.jid.parts.Resourcepart;

import javax.swing.*;
import java.awt.*;
import java.awt.event.ActionEvent;
import java.text.DateFormat;
import java.util.Date;
import java.util.concurrent.CopyOnWriteArrayList;

import static org.apache.commons.lang3.Strings.CS;
import static org.jivesoftware.spark.util.StringUtils.replaceMe;

/**
 * This is the Person to Person implementation of <code>ChatRoom</code>
 * This room only allows for 1 to 1 conversations.
 */
public class ChatRoomImpl extends ChatRoom {
    private final CopyOnWriteArrayList<MessageEventListener> messageEventListeners = new CopyOnWriteArrayList<>();
    private EntityBareJid roomname;
    private Icon tabIcon;
    private String roomTitle;
    private String tabTitle;

    /**
     * The participants XMPP address, can be a full or bare JID.
     */
    private EntityJid participantJID;
    private final Resourcepart participantNickname;
    private Presence presence;
    private boolean offlineSent;

    /**
     * Thread ID of the last message sent. This is used to group messages together.
     * Use {@link #getThreadID()} to get the current thread ID. It will generate it if necessary.
     */
    private String threadID;

    private boolean active;

    // True if this is a one-on-one with a participant of a multi-user chatroom.
    private final boolean privateChat;

    // Information button
    private ChatRoomButton infoButton;

    private final ChatRoomButton addToRosterButton;
    private VCardPanel vcardPanel;
    
    private JComponent chatStatePanel;    

    /**
     * Constructs a 1-to-1 ChatRoom.
     *
     * Note that the participantJID value can be a bare JID, or a full JID. In regular one-on-one chats, a bare JID is
     * expected. This instance will then display relevant data sent by any of the (full) JIDs associated to the bare JID.
     * When this instance is created to reflect a private message in MUC context, a full JID is expected to be provided
     * as the participantJID value (room@service/nick). In such case, only data sent from that full JID is displayed.
     *
     * @param participantJID      the participants jid to chat with.
     * @param participantNickname the nickname of the participant.
     * @param title               the title of the room.
     */
    public ChatRoomImpl(final EntityJid participantJID, Resourcepart participantNickname, CharSequence title) {
        Log.debug("Loading chat room impl: " + title);
        this.active = true;
        setParticipantJID(participantJID);
        this.participantNickname = participantNickname;
        // Loads the current history for this user.
        SwingUtilities.invokeLater(this::loadHistory);
        // Register StanzaListeners
        final StanzaFilter directFilter = new AndFilter(
            FromMatchesFilter.create( participantJID ),
            new OrFilter( new StanzaTypeFilter( Presence.class ),
                          new StanzaTypeFilter( Message.class ) )
        );
        final StanzaFilter carbonFilter = new AndFilter(
            FromMatchesFilter.create( SparkManager.getSessionManager().getUserBareAddress() ), // Security Consideration, see https://xmpp.org/extensions/xep-0280.html#security
            new StanzaTypeFilter( Message.class ),
            new OrFilter(
                new StanzaExtensionFilter( "sent", CarbonExtension.NAMESPACE ),
                new StanzaExtensionFilter( "received", CarbonExtension.NAMESPACE )
            )
        );
        SparkManager.getConnection().addSyncStanzaListener( this, new OrFilter( directFilter, carbonFilter ) );
        // Use the agents username as the Tab Title
        this.tabTitle = title.toString();
        // The name of the room will be the node of the user jid + conversation.
        this.roomTitle = participantNickname.toString();
        // Add RoomInfo
        this.getSplitPane().setRightComponent(null);
        getSplitPane().setDividerSize(0);
        presence = PresenceManager.getPresence(participantJID.asBareJid());
        Roster roster = SparkManager.getRoster();
        RosterEntry entry = roster.getEntry(participantJID.asBareJid());
        tabIcon = PresenceManager.getIconFromPresence(presence);

        // Create toolbar buttons.
        infoButton = new ChatRoomButton("", SparkRes.getImageIcon(SparkRes.Icon.PROFILE_IMAGE_24x24));
        infoButton.setToolTipText(Res.getString("message.view.information.about.this.user"));
        // Create basic toolbar.
        addChatRoomButton(infoButton);
        // Show VCard.
        infoButton.addActionListener(this);
        // If this is a private chat from a group chat room, do not show toolbar.
        privateChat = participantNickname.equals(participantJID.getResourceOrNull());
        if ( privateChat ) {
            getToolBar().setVisible(false);
        }
        // If the user is not in the roster, then allow user to add them.
        addToRosterButton = new ChatRoomButton("", SparkRes.getImageIcon(SparkRes.Icon.ADD_IMAGE_24x24));
        if (entry == null && !privateChat) {
            addToRosterButton.setToolTipText(Res.getString("message.add.this.user.to.your.roster"));
            if (!Default.getBoolean(Default.ADD_CONTACT_DISABLED) && Enterprise.containsFeature(Enterprise.ADD_CONTACTS_FEATURE)) addChatRoomButton(addToRosterButton);
            addToRosterButton.addActionListener(this);
        }

        lastActivity = System.currentTimeMillis();
        Log.debug("Loaded chat room impl: " + title);
    }

    /**
     * Set the XMPP address of the participant.
     */
    private void setParticipantJID(Jid jid) {
        if (!jid.isEntityJid()) {
            return;
        }
        participantJID = jid.asEntityJidOrThrow();
        roomname = participantJID.asEntityBareJid();
    }

    /**
     * Returns true if this is a private chat (PM) based chat with one MUC participant.
     */
    public boolean isPrivateChat()
    {
        return privateChat;
    }

    @Override
	public void closeChatRoom() {
        // If already closed, don't bother.
        if (!active) {
            return;
        }
        super.closeChatRoom();
        removeListeners();
        SparkManager.getChatManager().removeChat(this);
        SparkManager.getConnection().removeAsyncStanzaListener(this);

        active = false;
        vcardPanel = null;
        this.removeAll();
    }

    protected void removeListeners() {
        // Remove info listener
        infoButton.removeActionListener(this);
        addToRosterButton.removeActionListener(this);
        SparkManager.getConnection().removeSyncStanzaListener(this);
    }

    @Override
	public void sendMessage() {
        String text = getChatInputEditor().getText();
        sendMessage(text);
    }

    @Override
	public void sendMessage(String text) {
        // Remove control characters
        text = text.replaceAll("[\\u0001-\\u0008\\u000B-\\u001F]", "");
        // If the body is empty, just return and do nothing
        if (!ModelUtil.hasLength(text)) {
            return;
        }
        MessageBuilder messageBuilder = StanzaBuilder.buildMessage()
            .setBody(text);

        if ( privateChat )
        {
            // XEP-0045: 7.5 Sending a Private Message
            messageBuilder.addExtension( new MUCUser() );
        }

        // Fire Message Filters
        SparkManager.getChatManager().filterOutgoingMessage(this, messageBuilder);

        // Fire Global Filters
        Message message = messageBuilder.build();
        SparkManager.getChatManager().fireGlobalMessageSentListeners(this, message);

        sendMessage(messageBuilder);
    }

    /**
     * Sends a message to the appropriate jid. The message is automatically added to the transcript.
     *
     * @param messageBuilder the message to send.
     */
    @Override
	public void sendMessage(MessageBuilder messageBuilder) {
        //Before sending message, let's add our full jid for full verification
        //Set message attributes before insertMessage is called - this is useful when transcript window is extended
        //more information will be available to be displayed for the chat area Document
        messageBuilder.ofType(Message.Type.chat);
        // Set chat state to 'active'
        messageBuilder.addExtension( new ChatStateExtension( ChatState.active ) );
        messageBuilder.to(participantJID);
        messageBuilder.from(SparkManager.getSessionManager().getJID());

        // If the message has the Thread then use it, otherwise continue in existing or generate a new Thread
        String msgThread = messageBuilder.getThread();
        if (msgThread != null) {
            threadID = msgThread;
        } else {
            messageBuilder.setThread(getThreadID());
        }

        Message message = messageBuilder.build();

        displaySendMessage( message );

        // No need to request displayed or delivered as we aren't doing anything with this
        // information.
        MessageEventManager.addNotificationsRequests(message, true, false, false, true);

        // Send the message that contains the notifications request
        try {
            fireOutgoingMessageSending(messageBuilder);
            // rebuild message after all alterations
            Message msgToSend = messageBuilder.build();
            SparkManager.getConnection().sendStanza(msgToSend);
        }
        catch (Exception ex) {
            Log.error("Error sending message", ex);
        }
    }

    /**
     * Adds a message that is to be sent to the transcript window.
     *
     * @param message The message to be displayed.
     */
    private void displaySendMessage( Message message )
    {
        lastActivity = System.currentTimeMillis();

        try {
            getTranscriptWindow().insertMessage( getNickname(), message, ChatManager.TO_COLOR);
            getChatInputEditor().selectAll();

            getTranscriptWindow().validate();
            getTranscriptWindow().repaint();
            getChatInputEditor().clear();
        }
        catch (Exception ex) {
            Log.error( "Error sending message", ex);
        }

        // Notify users that message has been sent
        fireMessageSent(message);

        addToTranscript(message, false);

        getChatInputEditor().setCaretPosition(0);
        getChatInputEditor().requestFocusInWindow();
        scrollToBottom();
    }

    @Override
    public EntityBareJid getBareJid() {
        return roomname;
    }


    @Override
	public Icon getTabIcon() {
        return tabIcon;
    }

    public void setTabIcon(Icon icon) {
        this.tabIcon = icon;
    }

    @Override
	public String getTabTitle() {
        return tabTitle;
    }

    public void setTabTitle(String tabTitle) {
        this.tabTitle = tabTitle;
    }

    public void setRoomTitle(String roomTitle) {
        this.roomTitle = roomTitle;
    }

    @Override
	public String getRoomTitle() {
        return roomTitle;
    }

    @Override
	public Message.Type getChatType() {
        return Message.Type.chat;
    }

    @Override
	public void leaveChatRoom() {
        // There really is no such thing in Agent to Agent
    }

    @Override
	public boolean isActive() {
        return true;
    }

    /**
     * Returns the Bare-Participant JID
     *
     * <b> user@server.com </b> <br>
     * for retrieving the full Jid use ChatRoomImpl.getJID()
     */
    public EntityBareJid getParticipantJID() {
        return participantJID.asEntityBareJid();
    }

    /**
     * Returns the users full jid (ex. macbeth@jivesoftware.com/spark) or null if user is offline.
     *
     * @return the users Full JID.
     */
    public EntityFullJid getJidOnline() {
        presence = PresenceManager.getPresence(getParticipantJID());
        return presence.getFrom().asEntityFullJidIfPossible();
    }

    @Override
    public EntityJid getJid() {
        return participantJID;
    }

    public String getThreadID() {
        if (threadID == null) {
            threadID = StringUtils.randomString(6);
        }
        return threadID;
    }

    /**
     * Process incoming packets.
     */
    @Override
    public void processStanza(final Stanza stanza) {
        final Runnable runnable = () -> {
            try
            {
                if ( stanza instanceof Presence )
                {
                    Presence.Type oldType = presence.getType();
                    presence = (Presence) stanza;
                    final Presence presence1 = (Presence) stanza;
                    ContactList list = SparkManager.getWorkspace().getContactList();
                    ContactItem contactItem = list.getContactItemByJID( getParticipantJID() );
                    String time = DateFormat.getTimeInstance( DateFormat.SHORT ).format( new Date() );
                    if ( presence1.getType() == Presence.Type.unavailable && contactItem != null )
                    {
                        getTranscriptWindow().insertNotificationMessage( "*** " + Res.getString( "message.went.offline", participantNickname, time ), ChatManager.NOTIFICATION_COLOR );
                    } else if (oldType == Presence.Type.unavailable && presence1.isAvailable()) {
                        getTranscriptWindow().insertNotificationMessage( "*** " + Res.getString( "message.came.online", participantNickname, time ), ChatManager.NOTIFICATION_COLOR );
                    }
                }
                else if ( stanza instanceof Message )
                {
                    lastActivity = System.currentTimeMillis();
                    // Do something with the incoming packet here.
                    MessageBuilder msgBuilder = MessageBuilder.buildMessageFrom((Message) stanza, stanza.getStanzaId());
                    fireReceivingIncomingMessage(msgBuilder);
                    Message message = msgBuilder.build();
                    if ( message.getError() != null )
                    {
                        if ( message.getError().getCondition() == StanzaError.Condition.item_not_found )
                        {
                            Roster roster = SparkManager.getRoster();
                            // Check to see if the user is online to recieve this message.
                            RosterEntry entry = roster.getEntry( participantJID.asBareJid() );
                            if ( !presence.isAvailable() && !offlineSent && entry != null )
                            {
                                getTranscriptWindow().insertNotificationMessage( Res.getString( "message.offline.error" ), ChatManager.ERROR_COLOR );
                                offlineSent = true;
                            }
                        }
                        else
                        {
                            // Show a generic error if something went wrong: SPARK-1895.
                            getTranscriptWindow().insertNotificationMessage( Res.getString( "message.default.error" ) + ": " + message.getError().getCondition(), ChatManager.ERROR_COLOR );
                        }
                        return;
                    }

                    // Check to see if the user is online to recieve this message.
                    Roster roster = SparkManager.getRoster();
                    RosterEntry entry = roster.getEntry( participantJID.asBareJid() );
                    if ( !presence.isAvailable() && !offlineSent && entry != null )
                    {
                        getTranscriptWindow().insertNotificationMessage( Res.getString( "message.offline" ), ChatManager.ERROR_COLOR );
                        offlineSent = true;
                    }
                    if ( threadID == null )
                    {
                        threadID = message.getThread();
                    }

                    final JivePropertiesExtension extension = message.getExtension(JivePropertiesExtension.class);
                    final boolean broadcast = extension != null && extension.getProperty( "broadcast" ) != null;
                    // If this is a group chat message, discard
                    if ( message.getType() == Message.Type.groupchat || broadcast || message.getType() == Message.Type.normal ||
                            message.getType() == Message.Type.headline )
                    {
                        return;
                    }

                    // Do not accept Administrative messages.
                    final DomainBareJid host = SparkManager.getSessionManager().getServerAddress();
                    if ( host.equals( message.getFrom() ) )
                    {
                        return;
                    }
                    // Do not Handle offline messages. Offline messages are handling by Workspace.
                    if (message.hasExtension(DelayInformation.class) &&
                        (message.getType() == Message.Type.chat ||
                        message.getType() == Message.Type.normal)) {
                        return;
                    }

                    final CarbonExtension carbon = CarbonExtension.from(message);
                    if ( carbon != null )
                    {
                        // Is this a carbon copy?
                        final Message forwardedStanza = carbon.getForwarded().getForwardedStanza();
                        if ( forwardedStanza.getBody() != null )
                        {
                            if ( carbon.getDirection() == CarbonExtension.Direction.received )
                            {
                                Jid forwardedStanzaJid = forwardedStanza.getFrom();
                                if ((privateChat && forwardedStanzaJid.equals(getJid())) || //private chat from MUC, match the full JID
                                    (!privateChat && forwardedStanzaJid.asBareJid().equals(getJid().asBareJid()))) // person-to-person chat, match bare jids
                                {
                                    // This is a stanza that we received from someone on one of our other clients.
                                    setParticipantJID(forwardedStanzaJid);
                                    insertMessage( forwardedStanza );
                                }
                            }
                            else
                            {
                                Jid forwardedStanzaJid = forwardedStanza.getTo();
                                if ((privateChat && forwardedStanzaJid.equals(getJid())) || //private chat from MUC, match full JID
                                    (!privateChat && forwardedStanzaJid.asBareJid().equals(getJid().asBareJid()))) // person-to-person chat, match bare jids
                                {
                                    // This is a stanza that one of our own clients sent.
                                    setParticipantJID(forwardedStanzaJid);
                                    displaySendMessage( forwardedStanza );
                                }
                            }
                            showTyping( false );
                        }
                    }
                    else if ( message.getBody() != null )
                    {
                        // If the message is not from the current agent. Append to chat.
                        setParticipantJID(message.getFrom());
                        insertMessage( message );
                        showTyping( false );
                    }
                }
            }
            catch ( Exception e )
            {
                Log.error( "An exception occurred while processing this incoming stanza: " + stanza, e );
            }
        };
        SwingUtilities.invokeLater(runnable);
    }

    /**
     * Returns the nickname of the user chatting with.
     */
    public Resourcepart getParticipantNickname() {
        return participantNickname;
    }

    @Override
	public Message insertMessage(Message message) {
        message = super.insertMessage(message);
        MessageEvent messageEvent = message.getExtension(MessageEvent.class);
        if (messageEvent != null) {
            checkEvents(message.getFrom(), message.getStanzaId(), messageEvent);
        }
        getTranscriptWindow().insertMessage(participantNickname, message, ChatManager.FROM_COLOR);
        // Set the participant jid to their full JID.
        setParticipantJID(message.getFrom());
        return message;
    }

    private void checkEvents(Jid from, String packetID, MessageEvent messageEvent) {
        if (messageEvent.isDelivered() || messageEvent.isDisplayed()) {
            // Create the message to send
            MessageBuilder messageBuilder = StanzaBuilder.buildMessage();
            // Create a MessageEvent Package and add it to the message
            MessageEvent event = new MessageEvent();
            if (messageEvent.isDelivered()) {
                event.setDelivered(true);
            }
            if (messageEvent.isDisplayed()) {
                event.setDisplayed(true);
            }
            event.setStanzaId(packetID);
            messageBuilder.addExtension(event);
            messageBuilder.from(from);
            Message msg = messageBuilder.build();
            // Send the packet
            try
            {
                SparkManager.getConnection().sendStanza(msg);
            }
            catch ( SmackException.NotConnectedException | InterruptedException e )
            {
                Log.warning( "Unable to send message to " + msg.getTo(), e );
            }
        }
    }

    public void addMessageEventListener(MessageEventListener listener) {
        messageEventListeners.addIfAbsent(listener);
    }

    public void removeMessageEventListener(MessageEventListener listener) {
        messageEventListeners.remove(listener);
    }

    public void fireOutgoingMessageSending(MessageBuilder message)
    {
        for ( final MessageEventListener listener : messageEventListeners )
        {
            try
            {
                listener.sendingMessage( message );
            }
            catch ( Exception e )
            {
                Log.error( "A MessageEventListener ('" + listener + "') threw an exception while processing an outgoing message (to '" + message.getTo() + "').", e );
            }
        }
    }

    public void fireReceivingIncomingMessage(MessageBuilder message)
    {
        for ( final MessageEventListener listener : messageEventListeners )
        {
            try
            {
                listener.receivingMessage( message );
            }
            catch ( Exception e )
            {
                Log.error( "A MessageEventListener ('" + listener + "') threw an exception while processing an incoming message (from '" + message.getFrom() + "').", e );
            }
        }
    }


    /**
     * Show the typing notification.
     *
     * @param typing true if the typing notification should show, otherwise hide it.
     */
    public void showTyping(boolean typing) {
        if (typing) {
            String isTypingText = Res.getString("message.is.typing.a.message", participantNickname);
            getNotificationLabel().setText(isTypingText);
            getNotificationLabel().setIcon(SparkRes.getImageIcon(SparkRes.Icon.SMALL_MESSAGE_EDIT_IMAGE));
        }
        else {
            // Remove is typing text.
            getNotificationLabel().setText("");
            getNotificationLabel().setIcon(SparkRes.getImageIcon(SparkRes.Icon.BLANK_IMAGE));
        }
    }

    /**
     * Returns the current presence of the client this room was created for.
     */
    public Presence getPresence() {
        return presence;
    }

    @Override
    public void authenticated( XMPPConnection xmppConnection, boolean b )
    {
        reconnectionSuccessful();
    }

    @Override
	public void connectionClosed() {
        handleDisconnect();
        String message = Res.getString("message.disconnected.error");
        getTranscriptWindow().insertNotificationMessage(message, ChatManager.ERROR_COLOR);
    }

    @Override
	public void connectionClosedOnError(Exception ex) {
        handleDisconnect();
        String message = Res.getString("message.disconnected.error");
        if (ex instanceof XMPPException.StreamErrorException && ((XMPPException.StreamErrorException) ex).getStreamError().getCondition() == StreamError.Condition.conflict )
        {
            message = Res.getString("message.disconnected.conflict.error");
        }
        getTranscriptWindow().insertNotificationMessage(message, ChatManager.ERROR_COLOR);
    }

    public void reconnectionSuccessful() {
        Presence usersPresence = PresenceManager.getPresence(getParticipantJID());
        if (usersPresence.isAvailable()) {
            presence = usersPresence;
        }
        setChatInputEnabled(true);
        SparkManager.getChatManager().getChatContainer().fireChatRoomStateUpdated(this);
    }

    private void handleDisconnect() {
        presence = StanzaBuilder.buildPresence()
            .ofType(Presence.Type.unavailable)
            .build();
        setChatInputEnabled(false);
        SparkManager.getChatManager().getChatContainer().fireChatRoomStateUpdated(this);
    }


    protected void loadHistory() {
    	// Add VCard Panel
    	vcardPanel = new VCardPanel(participantJID.asEntityBareJidIfPossible());
    	getToolBar().add(vcardPanel, new GridBagConstraints(0, 1, 1, 1, 1.0, 0.0, GridBagConstraints.NORTHWEST, GridBagConstraints.HORIZONTAL, new Insets(0, 2, 0, 2), 0, 0));

       	if (!Default.getBoolean(Default.HISTORY_DISABLED) && Enterprise.containsFeature(Enterprise.HISTORY_TRANSCRIPTS_FEATURE)) {
    		final LocalPreferences localPreferences = SettingsManager.getLocalPreferences();
    		if (!localPreferences.isChatHistoryEnabled()) {
    			return;
    		}
    		if (!localPreferences.isPrevChatHistoryEnabled()) {
    			return;
    		}

            final ChatTranscript chatTranscript = ChatTranscripts.getCurrentChatTranscript(getJid());
    		final String personalNickname = SparkManager.getUserManager().getNickname();
            final boolean privateChat = participantNickname.equals(participantJID.getResourceOrNull());

            EntityBareJid myJID = SparkManager.getSessionManager().getUserBareAddress();
    		for (HistoryMessage message : chatTranscript.getMessages()) {
    			String nickname = SparkManager.getUserManager().getUserNicknameFromJID(message.getFrom().asBareJid());
    			String messageBody = message.getBody();
    			if (nickname.equals(message.getFrom().toString()) || nickname.equals(message.getFrom().asBareJid().toString())) {
    				BareJid otherJID = message.getFrom().asBareJid();
    				if (otherJID.equals(myJID)) {
    					nickname = personalNickname;
    				}
    				else {
    				    Resourcepart resourcepart = message.getFrom().getResourceOrNull();
                        Localpart localpart = message.getFrom().getLocalpartOrNull();
    				    if (resourcepart == null) {
    				        if (localpart != null) {
    				            nickname = localpart.toString();
    				        }
    				    } else {
                            if(privateChat){
                                nickname = resourcepart.toString();
                            } else {
                                if(localpart != null){
                                    nickname = localpart.toString();
                                }
                            }
    				    }
    				}
    			}
                if (CS.startsWith(messageBody, "/me ")) {
                    messageBody = replaceMe(messageBody, nickname);
    			}
    			final Date messageDate = message.getDate();
    			getTranscriptWindow().insertHistoryMessage(nickname, messageBody, messageDate);
    		}
    		if (!chatTranscript.getMessages().isEmpty()) { // Check if we have history mesages
    			getTranscriptWindow().insertHorizontalLine();
    		}
    		chatTranscript.release();
    	}
    }

    @Override
	public void actionPerformed(ActionEvent e) {
        if (e.getSource() == infoButton) {
            VCardManager vcard = SparkManager.getVCardManager();
            vcard.viewProfile(participantJID.asBareJid(), SparkManager.getChatManager().getChatContainer());
        }
        else if (e.getSource() == addToRosterButton) {
            RosterDialog rosterDialog = new RosterDialog();
            rosterDialog.setDefaultJID(participantJID.asBareJid().toString());
            rosterDialog.setDefaultNickname(getParticipantNickname().toString());
            rosterDialog.showRosterDialog(SparkManager.getChatManager().getChatContainer().getChatFrame());
        } else {
            super.actionPerformed(e);
        }
    }

    public void notifyChatStateChange(ChatState state) {
    	if (chatStatePanel != null) {
    		getEditorWrapperBar().remove(chatStatePanel);
    	}
    	chatStatePanel = new ChatStatePanel(state, getParticipantNickname());
    	getEditorWrapperBar().add(chatStatePanel, BorderLayout.SOUTH);
    	getEditorWrapperBar().revalidate();
    	getEditorWrapperBar().repaint();
    }

    @Override
    protected void sendChatState(ChatState state) throws SmackException.NotConnectedException, InterruptedException
    {
        if ( !active )
        {
            return;
        }
        Message message = StanzaBuilder.buildMessage()
            .ofType(Message.Type.chat)
            .setThread(getThreadID())
            .addExtension(new ChatStateExtension(state))
            .to(participantJID)
            .from(SparkManager.getSessionManager().getJID())
            .build();
        SparkManager.getConnection().sendStanza( message );
    }
}
