/*
 * 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.plugin.msofficecomm;

import java.beans.*;
import java.util.*;

import javax.swing.*;

import net.java.sip.communicator.service.contactlist.*;
import net.java.sip.communicator.service.gui.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.ServerStoredDetails.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.service.protocol.jabberconstants.*;
import net.java.sip.communicator.util.*;

import org.jitsi.service.configuration.*;
import org.jitsi.util.xml.*;
import org.osgi.framework.*;
import org.w3c.dom.*;

/**
 * Represents the Java counterpart of a native <tt>IMessenger</tt>
 * implementation.
 *
 * @author Lyubomir Marinov
 */
public class Messenger
{
    /**
     * The <tt>Logger</tt> used by the <tt>Messenger</tt> class and its
     * instances for logging output.
     */
    private static final Logger logger = Logger.getLogger(Messenger.class);

    static final int CONVERSATION_TYPE_AUDIO = 8;

    static final int CONVERSATION_TYPE_IM = 1;

    static final int CONVERSATION_TYPE_LIVEMEETING = 4;

    static final int CONVERSATION_TYPE_PHONE = 2;

    static final int CONVERSATION_TYPE_PSTN = 32;

    static final int CONVERSATION_TYPE_VIDEO = 16;

    static final int MISTATUS_AWAY = 0x0022;

    static final int MISTATUS_MAY_BE_AVAILABLE = 0x00A2;

    static final int MISTATUS_OFFLINE = 0x0001;

    /**
     * The <tt>MISTATUS</tt> value which indicates that the local or remote
     * client user is on the phone.
     */
    static final int MISTATUS_ON_THE_PHONE = 0x0032;
    
    static final int MISTATUS_IN_A_MEETING = 0x0052;

    static final int MISTATUS_ONLINE = 0x0002;

    static final int MISTATUS_UNKNOWN = 0x0000;

    static final int MPHONE_TYPE_CUSTOM = 3;

    /**
     * The <tt>MPHONE_TYPE</tt> value which indicates a home phone number.
     */
    static final int MPHONE_TYPE_HOME = 0;

    /**
     * The <tt>MPHONE_TYPE</tt> value which indicates a mobile phone number.
     */
    static final int MPHONE_TYPE_MOBILE = 2;

    /**
     * The <tt>MPHONE_TYPE</tt> value which indicates a work phone number.
     */
    static final int MPHONE_TYPE_WORK = 1;

    /**
     * The name of the boolean <tt>ConfigurationService</tt> property which
     * indicates whether {@link #startConversation(int, String[], String)} is
     * to invoke {@link UIService#createCall(String[])} with a phone number
     * associated with a specific <tt>IMessengerContact</tt> instead of the
     * <tt>Contact</tt> address.
     */
    private static final String PNAME_CREATE_CALL_BY_PHONE_NUMBER
        = "net.java.sip.communicator.plugin.msofficecomm."
            + "CREATE_CALL_BY_PHONE_NUMBER";

    /**
     * The name of the <tt>String</tt> <tt>ConfigurationService</tt> property
     * which specifies the sort order of the <tt>MPHONE_TYPE_*</tt> enumerated
     * type values. The default value orders them as follows:
     * {@link #MPHONE_TYPE_WORK}, {@link #MPHONE_TYPE_HOME},
     * {@link #MPHONE_TYPE_MOBILE}, {@link #MPHONE_TYPE_CUSTOM}.
     */
    private static final String PNAME_MPHONE_TYPE_SORT_ORDER
        = "net.java.sip.communicator.plugin.msofficecomm."
            + "MPHONE_TYPE_SORT_ORDER";

    /**
     * The <tt>BundleContext</tt> in which the <tt>msofficecomm</tt> bundle has
     * been started.
     */
    private static BundleContext bundleContext;

    /**
     * The <tt>MetaContactListService</tt> which the <tt>Messenger</tt> class
     * looks through in order to locate <tt>Contact</tt>s associated with a
     * specific sign-in name.
     */
    private static MetaContactListService metaContactListService;

    /**
     * The <tt>MPHONE_TYPE_*</tt> enumerated type values indexed by their sort
     * order position.
     */
    private static final int[] MPHONE_TYPE_SORT_ORDER
        = new int[]
                {
                    MPHONE_TYPE_WORK,
                    MPHONE_TYPE_HOME,
                    MPHONE_TYPE_MOBILE,
                    MPHONE_TYPE_CUSTOM
                };

    /**
     * The list of (local) accounts by sign-in name which correspond to
     * <tt>IMessengerContact</tt> implementations having <tt>true</tt> as the
     * value of their <tt>self</tt> boolean property.
     */
    private static final Map<String, Self> selves = new HashMap<String, Self>();

    /**
     * The <tt>ServiceListener</tt> which listens to the <tt>BundleContext</tt>
     * in which the <tt>msofficecomm</tt> bundle has been started for service
     * changes.
     */
    private static final ServiceListener serviceListener
        = new ServiceListener()
        {
            public void serviceChanged(ServiceEvent event)
            {
                Messenger.serviceChanged(event);
            }
        };

    static
    {
        try
        {
            System.loadLibrary("jmsofficecomm");
        }
        catch (Throwable t)
        {
            logger.error("Couldn't load jmsofficecomm. If you are debugging,"
                + " make sure that HKLM\\SOFTWARE[\\Wow6432Node]\\Microsoft\\"
                + "Office\\Outlook\\Call Integration\\IMApplication is set to"
                + " javaw.exe; " + t.getMessage());
            throw new RuntimeException(t);
        }
    }

    private static synchronized void addSelf(
            String signinName,
            ProtocolProviderService pps,
            OperationSetPresence presenceOpSet)
    {
        Self self = selves.get(signinName);

        if (self == null)
        {
            self = new Self(signinName);
            selves.put(signinName, self);
        }
        self.addProtocolProviderService(pps, presenceOpSet);
    }

    /**
     * Finds the <tt>Contact</tt> instances which are associated with a specific
     * <tt>IMessengerContact</tt> sign-in name and which originate from a
     * specific <tt>ProtocolProviderService</tt> instance.
     *
     * @param pps the <tt>ProtocolProviderService</tt> from which possibly
     * found <tt>Contact</tt> instances are to originate
     * @param presenceOpSet the <tt>OperationSetPresence</tt> associated with
     * the specified <tt>pps</tt>
     * @param signinName the <tt>IMessengerContact</tt> sign-in name for which
     * the associated <tt>Contact</tt> instances are to be found
     * @return a list of <tt>Contact</tt> instances which are associated with
     * the specified <tt>signinName</tt> and which originate from the specified
     * <tt>pps</tt> if such <tt>Contact</tt> instances have been found;
     * otherwise, an empty list
     */
    private static List<Contact> findContactsBySigninName(
            ProtocolProviderService pps,
            OperationSetPresence presenceOpSet,
            String signinName)
    {
        List<Contact> contacts = new ArrayList<Contact>();

        for (Iterator<MetaContact> metaContactIt
                    = metaContactListService.findAllMetaContactsForProvider(
                            pps);
                metaContactIt.hasNext();)
        {
            MetaContact metaContact = metaContactIt.next();

            for (Iterator<Contact> contactIt = metaContact.getContacts();
                    contactIt.hasNext();)
            {
                Contact contact = contactIt.next();

                if (signinName.equalsIgnoreCase(getSigninName(contact, pps)))
                {
                    /*
                     * Prefer matches by Contact address over
                     * EmailAddressDetail.
                     */
                    contacts.add(0, contact);
                    continue;
                }

                OperationSetServerStoredContactInfo serverStoredContactInfoOpSet
                    = pps.getOperationSet(
                            OperationSetServerStoredContactInfo.class);

                if (serverStoredContactInfoOpSet != null)
                {
                    for (Iterator<EmailAddressDetail> emailAddressDetailIt
                                =  serverStoredContactInfoOpSet
                                    .getDetailsAndDescendants(
                                            contact,
                                            EmailAddressDetail.class);
                            emailAddressDetailIt.hasNext();)
                    {
                        EmailAddressDetail emailAddressDetail
                            = emailAddressDetailIt.next();

                        if (signinName.equalsIgnoreCase(
                                emailAddressDetail.getEMailAddress()))
                        {
                            contacts.add(contact);
                            break;
                        }
                    }
                }
            }
        }
        return contacts;
    }

    /**
     * Gets the <tt>Contact</tt> instances which are associated with a specific
     * <tt>IMessengerContact</tt> sign-in name and which support a specific
     * <tt>OperationSet</tt>.
     *
     * @param signinName the <tt>IMessengerContact</tt> sign-in name for which
     * the associated <tt>Contact</tt> instances are to be found
     * @param opSetClass the <tt>OperationSet</tt> class to be supported by the
     * possibly found <tt>Contact</tt> instances
     * @param limit the maximum number of found <tt>Contact</tt>s at which the
     * search should stop or {@link Integer#MAX_VALUE} if the search is to be
     * unbound with respect to the number of found <tt>Contact</tt>s
     * @return a list of <tt>Contact</tt> instances which are associated with
     * the specified <tt>signinName</tt> and which support the specified
     * <tt>opSetClass</tt> if such <tt>Contact</tt> instances have been found;
     * otherwise, an empty list
     */
    private static List<Contact> findContactsBySigninName(
            String signinName,
            Class<? extends OperationSet> opSetClass,
            int limit)
    {
        List<Contact> contacts = new ArrayList<Contact>();

        for (Self self : selves.values())
        {
            self.findContactsBySigninName(
                    signinName, opSetClass, limit,
                    contacts);
            /* Obey the specified limit of the number of found Contacts. */
            if (contacts.size() >= limit)
                break;
        }
        return contacts;
    }

    /**
     * Gets the <tt>PhoneNumberDetail</tt> instances which are associated with a
     * specific <tt>IMessengerContact</tt> sign-in name.
     *
     * @param signinName the <tt>IMessengerContact</tt> sign-in name for which
     * the associated <tt>PhoneNumberDetail</tt> instances are to be found
     * @param limit the maximum number of found <tt>PhoneNumberDetail</tt>s at
     * which the search should stop or {@link Integer#MAX_VALUE} if the search
     * is to be unbound with respect to the number of found
     * <tt>PhoneNumberDetail</tt>s
     * @return a list of <tt>PhoneNumberDetail</tt> instances which are
     * associated with the specified <tt>signinName</tt> if such
     * <tt>PhoneNumberDetail</tt> instances have been found; otherwise, an empty
     * list
     */
    private static Set<PhoneNumberDetail> findPhoneNumbersBySigninName(
            String signinName,
            int limit)
    {
        /*
         * XXX The limit is not being obeyed at this time because the
         * PhoneNumberDetails are ordered by MPHONE_TYPE.
         */

        Set<PhoneNumberDetail> phoneNumbers
            = new TreeSet<PhoneNumberDetail>(
                    new Comparator<PhoneNumberDetail>()
                    {
                        public int compare(
                                PhoneNumberDetail pn1,
                                PhoneNumberDetail pn2)
                        {
                            int so1
                                = getMPHONE_TYPESortOrder(getMPHONE_TYPE(pn1));
                            int so2
                                = getMPHONE_TYPESortOrder(getMPHONE_TYPE(pn2));

                            return
                                (so1 == so2)
                                    ? pn1.getNumber().compareTo(
                                            pn2.getNumber())
                                    : (so1 - so2);
                        }
                    });

        for (Self self : selves.values())
        {
            self.findPhoneNumbersBySigninName(
                    signinName,
                    Integer.MAX_VALUE,
                    phoneNumbers);
        }

        return phoneNumbers;
    }

    /**
     * Gets an <tt>MPHONE_TYPE</tt> enumerated type value which indicates the
     * phone number type of a specific <tt>PhoneNumberDetail</tt>.
     *
     * @param phoneNumber the <tt>PhoneNumberDetail</tt> for which a matching
     * <tt>MPHONE_TYPE</tt> enumerated type value is to be retrieved
     * @return an <tt>MPHONE_TYPE</tt> enumerated type value which indicates the
     * phone number type of the speciifed <tt>phoneNumber</tt>
     */
    private static int getMPHONE_TYPE(PhoneNumberDetail phoneNumber)
    {
        if (phoneNumber.getClass().equals(PhoneNumberDetail.class))
            return MPHONE_TYPE_HOME;
        else if (phoneNumber instanceof MobilePhoneDetail)
            return MPHONE_TYPE_MOBILE;
        else if (phoneNumber instanceof WorkPhoneDetail)
            return MPHONE_TYPE_WORK;
        else
            return MPHONE_TYPE_CUSTOM;
    }

    /**
     * Gets an <tt>int</tt> value which specifies the sort order position of a
     * specific <tt>MPHONE_TYPE</tt> enumerated type value which can be used
     * with a <tt>Comparator</tt> implementation.
     *
     * @param mphonetype the <tt>MPHONE_TYPE</tt> enumerated type value for
     * which the sort order position is to be retrieved
     * @return an <tt>int</tt> value which specifies the sort order position of
     * the specified <tt>MPHONE_TYPE</tt> enumerated type value which can be
     * used with a <tt>Comparator</tt> implementation
     */
    private static int getMPHONE_TYPESortOrder(int mphonetype)
    {
        for (int i = 0; i < MPHONE_TYPE_SORT_ORDER.length; i++)
            if (MPHONE_TYPE_SORT_ORDER[i] == mphonetype)
                return i;
        return MPHONE_TYPE_SORT_ORDER.length;
    }

    /**
     * Gets the phone number information of the contact associated with a
     * specific <tt>MessengerContact</tt> instance.
     *
     * @param messengerContact a <tt>MessengerContact</tt> instance which
     * specifies the contact for which the phone number information is to be
     * retrieved
     * @param type member of the <tt>MPHONE_TYPE</tt> enumerated type which
     * specifies the type of the phone number information to be retrieved
     * @return the phone number information of the contact associated with the
     * specified <tt>messengerContact</tt>
     */
    static String getPhoneNumber(MessengerContact messengerContact, int type)
    {
        Set<PhoneNumberDetail> phoneNumbers
            = findPhoneNumbersBySigninName(
                    messengerContact.signinName,
                    Integer.MAX_VALUE);

        for (PhoneNumberDetail phoneNumber : phoneNumbers)
            if (getMPHONE_TYPE(phoneNumber) == type)
                return phoneNumber.getNumber();
        return null;
    }

    /**
     * Gets the (local) account associated with a specific sign-in name in the
     * form of a <tt>Self</tt> instance if the specified sign-in name is
     * associated with such a (local) account.
     *
     * @param signinName the sign-in name associated with the (local) account to
     * be retrieved
     * @return a <tt>Self</tt> instance describing a (local) account associated
     * with the specified <tt>signinName</tt> if such a <tt>Self</tt> instance
     * exists; otherwise, <tt>null</tt>
     */
    private static Self getSelf(String signinName)
    {
        Self self = selves.get(signinName);

        if (self == null)
        {
            for (Self aSelf : selves.values())
            {
                if (aSelf.isSelf(signinName))
                {
                    self = aSelf;
                    break;
                }
            }
        }
        return self;
    }

    /**
     * Gets the <tt>IMessengerContact</tt> sign-in name associated with a
     * specific <tt>Contact</tt> from a specific
     * <tt>ProtocolProviderService</tt>. If no <tt>Contact</tt> is specified,
     * gets the sign-in name associated with the <tt>AccountID</tt> of the
     * specified <tt>ProtocolProviderService</tt>.
     *
     * @param contact the <tt>Contact</tt> to retrieve the sign-in name of or
     * <tt>null</tt> to retrieve the sign-in name associated with the
     * <tt>AccountID</tt> of the specified <tt>pps</tt>
     * @param pps the <tt>ProtocolProviderService</tt> of <tt>contact</tt> if
     * <tt>contact</tt> is other than <tt>null</tt> or of the <tt>AccountID</tt>
     * to get the sign-in name of if <tt>contact</tt> is <tt>null</tt>
     * @return the sign-in name associated with the specified <tt>contact</tt>
     * from the specified <tt>pps</tt> if <tt>contact</tt> is other than
     * <tt>null</tt> or with the <tt>AccountID</tt> of the specified
     * <tt>pps</tt> if <tt>contact</tt> is <tt>null</tt>
     */
    private static String getSigninName(
            Contact contact,
            ProtocolProviderService pps)
    {
        String address
            = (contact == null)
                ? pps.getAccountID().getAccountAddress()
                : contact.getAddress();
        String signinName;

        if (address.contains("@"))
        {
            String protocol
                = ((pps == null) ? contact.getProtocolProvider() : pps)
                        .getProtocolName()
                    + ":";

            if (address.toLowerCase().startsWith(protocol.toLowerCase()))
                signinName = address.substring(protocol.length());
            else
                signinName = address;
        }
        else
            signinName = null;
        return signinName;
    }

    /**
     * Gets the connection/presence status of the contact associated with a
     * specific <tt>MessengerContact</tt> instance in the form of a
     * <tt>MISTATUS</tt> value.
     *
     * @param messengerContact a <tt>MessengerContact</tt> instance which
     * specifies the contact for which the connection/presence status is to be
     * retrieved
     * @return a <tt>MISTATUS</tt> value which represents the
     * connection/presence status of the contact associated with the specified
     * <tt>messengerContact</tt>
     */
    static int getStatus(MessengerContact messengerContact)
    {
        String signinName = messengerContact.signinName;
        ProtocolPresenceStatus presenceStatus;

        if(logger.isTraceEnabled())
            logger.trace("Got getStatus for " + signinName);

        if (signinName == null)
            presenceStatus = null;
        else
        {
            Self self = getSelf(signinName);

            if (self == null)
            {
                presenceStatus = null;
                for (Self aSelf : selves.values())
                {
                    ProtocolPresenceStatus aPresenceStatus
                        = aSelf.getPresenceStatus(signinName);

                    if (aPresenceStatus != null)
                    {
                        if (presenceStatus == null)
                            presenceStatus = aPresenceStatus;
                        else if (presenceStatus.compareTo(aPresenceStatus) < 0)
                            presenceStatus = aPresenceStatus;
                        if (presenceStatus.toInt()
                                >= PresenceStatus.MAX_STATUS_VALUE)
                            break;
                    }
                }
            }
            else
                presenceStatus = self.getPresenceStatus();
        }

        return ProtocolPresenceStatus.toMISTATUS(presenceStatus);
    }

    /**
     * Gets the indicator which determines whether a specific
     * <tt>MessengerContact</tt> is the same user as the current client user.
     *
     * @param messengerContact the <tt>MessengerContact</tt> which is to be
     * determined whether it is the same user as the current client user
     * @return <tt>true</tt> if the specified <tt>messengerContact</tt> is the
     * same user as the current client user; otherwise, <tt>false</tt>
     */
    static boolean isSelf(MessengerContact messengerContact)
    {
        String signinName = messengerContact.signinName;

        return (signinName == null) ? false : (getSelf(signinName) != null);
    }

    private static native void onContactStatusChange(
            String signinName,
            int status);

    private static synchronized void removeSelf(
            String signinName,
            ProtocolProviderService pps)
    {
        Self self = selves.get(signinName);

        if ((self != null) && (self.removeProtocolProviderService(pps) < 1))
        {
            for (Iterator<Self> it = selves.values().iterator(); it.hasNext();)
            {
                if (it.next() == self)
                    it.remove();
            }
            self.dispose();
        }
    }

    /**
     * Notifies the <tt>Messenger</tt> class about a service change in the
     * <tt>BundleContext</tt> in which the <tt>msofficecomm</tt> bundle has been
     * started
     *
     * @param event a <tt>ServiceEvent</tt> describing the service change in the
     * <tt>BundleContext</tt> in which the <tt>msofficecomm</tt> bundle has been
     * started
     */
    private static void serviceChanged(ServiceEvent event)
    {
        Object service = bundleContext.getService(event.getServiceReference());

        if (service instanceof ProtocolProviderService)
        {
            ProtocolProviderService pps = (ProtocolProviderService) service;
            /*
             * The Messenger class implements an integration of Jitsi presence
             * into Microsoft Office so the only accounts of interest to it are
             * the ones which support presence.
             */
            OperationSetPresence presenceOpSet
                = pps.getOperationSet(OperationSetPresence.class);

            if (presenceOpSet != null)
            {
                String signinName = getSigninName(null, pps);

                if (signinName != null)
                {
                    switch (event.getType())
                    {
                    case ServiceEvent.REGISTERED:
                        addSelf(signinName, pps, presenceOpSet);
                        break;
                    case ServiceEvent.UNREGISTERING:
                        removeSelf(signinName, pps);
                        break;
                    }
                }
            }
        }
    }

    /**
     * Starts the <tt>Messenger</tt> class and instance functionality in a
     * specific <tt>BundleContext</tt>.
     *
     * @param bundleContext the <tt>BundleContext</tt> in which the
     * <tt>Messenger</tt> class and instance functionality is to be started
     * @throws Exception if anything goes wrong while starting the
     * <tt>Messenger</tt> class and instance functionality in the specified
     * <tt>BundleContext</tt>
     */
    static synchronized void start(BundleContext bundleContext)
        throws Exception
    {
        Messenger.bundleContext = bundleContext;
        metaContactListService
            = ServiceUtils.getService(
                    bundleContext,
                    MetaContactListService.class);

        bundleContext.addServiceListener(serviceListener);

        ServiceReference[] serviceReferences
            = bundleContext.getServiceReferences(
                    ProtocolProviderService.class.getName(),
                    null);

        if ((serviceReferences != null) && (serviceReferences.length != 0))
        {
            for (ServiceReference serviceReference : serviceReferences)
            {
                serviceListener.serviceChanged(
                        new ServiceEvent(
                                ServiceEvent.REGISTERED,
                                serviceReference));
            }
        }

        if (logger.isInfoEnabled())
            logger.info("Messenger [REGISTERED] as service listener.");
    }

    /**
     * Stops the <tt>Messenger</tt> class and instance functionality in a
     * specific <tt>BundleContext</tt>.
     *
     * @param bundleContext the <tt>BundleContext</tt> in which the
     * <tt>Messenger</tt> class and instance functionality is to be stopped
     * @throws Exception if anything goes wrong while stopping the
     * <tt>Messenger</tt> class and instance functionality in the specified
     * <tt>BundleContext</tt>
     */
    static synchronized void stop(BundleContext bundleContext)
        throws Exception
    {
        bundleContext.removeServiceListener(serviceListener);

        Messenger.bundleContext = null;
        metaContactListService = null;

        // selves
        for (Iterator<Self> it = selves.values().iterator(); it.hasNext();)
        {
            it.next().dispose();
            it.remove();
        }
    }

    /**
     * Initializes a new <tt>Messenger</tt> instance which is to represent the
     * Java counterpart of a native <tt>IMessenger</tt> implementation.
     */
    public Messenger()
    {
    }

    /**
     * Starts a conversation with one or more other users using text, voice,
     * video, or data.
     *
     * @param conversationType a <tt>CONVERSATION_TYPE</tt> value specifying the
     * type of the conversation to be started
     * @param participants an array of <tt>String</tt> values specifying the
     * other users to start a conversation with
     * @param conversationData an XML BLOB specifying the phone numbers to be
     * dialed in order to start the conversation
     */
    public void startConversation(
            final int conversationType,
            String[] participants,
            String conversationData)
    {
        if(logger.isTraceEnabled())
            logger.trace("Got startConversation participants:"
                + participants == null? "" : Arrays.asList(participants)
                + ", conversationData=" + conversationData);

        /*
         * Firstly, resolve the participants into Contacts which may include
         * looking up their vCards.
         */
        Class<? extends OperationSet> opSetClass;

        switch (conversationType)
        {
        case CONVERSATION_TYPE_AUDIO:
        case CONVERSATION_TYPE_PHONE:
        case CONVERSATION_TYPE_PSTN:
            opSetClass = OperationSetBasicTelephony.class;

            if ((conversationData != null) && (conversationData.length() != 0))
            {
                if (logger.isTraceEnabled())
                {
                    logger.trace(
                            "conversationData = \"" + conversationData + "\"");
                }

                // According to MSDN, vConversationData could be an XML BLOB.
                if (conversationData.startsWith("<"))
                {
                    try
                    {
                        Document document
                            = XMLUtils.createDocument(conversationData);
                        Element documentElement = document.getDocumentElement();

                        if ("TelURIs".equalsIgnoreCase(
                                documentElement.getTagName()))
                        {
                            NodeList childNodes
                                = documentElement.getChildNodes();

                            if (childNodes != null)
                            {
                                int childNodeCount = childNodes.getLength();
                                List<String> phoneNumbers
                                    = new ArrayList<String>(childNodeCount);

                                for (int childNodeIndex = 0;
                                        childNodeIndex < childNodeCount;
                                        childNodeIndex++)
                                {
                                    Node childNode
                                        = childNodes.item(childNodeIndex);

                                    if (childNode.getNodeType()
                                            == Node.ELEMENT_NODE)
                                    {
                                        phoneNumbers.add(
                                                childNode.getTextContent());
                                    }
                                }

                                int count = participants.length;

                                if (phoneNumbers.size() == count)
                                {
                                    for (int i = 0; i < count; i++)
                                    {
                                        String phoneNumber
                                            = phoneNumbers.get(i);

                                        if ((phoneNumber != null)
                                                && (phoneNumber.length() != 0))
                                        {
                                            if (phoneNumber
                                                    .toLowerCase()
                                                        .startsWith("tel:"))
                                            {
                                                phoneNumber
                                                    = phoneNumber.substring(4);
                                            }
                                            participants[i] = phoneNumber;
                                        }
                                    }
                                }
                            }
                        }
                    }
                    catch (Exception e)
                    {
                        logger.error(
                                "Failed to parse"
                                    + " IMessengerAdvanced::StartConversation"
                                    + " vConversationData: "
                                    + conversationData,
                                e);
                    }
                }
                else
                {
                    /*
                     * Practice/testing shows that vConversationData is the
                     * phone number in the case of a single participant.
                     */
                    if (participants.length == 1)
                        participants[0] = conversationData;
                }
            }

            break;
        case CONVERSATION_TYPE_IM:
            opSetClass = OperationSetBasicInstantMessaging.class;
            break;

        default:
            throw new UnsupportedOperationException();
        }

        List<String> contactList = new ArrayList<String>();
        ConfigurationService cfg
            = ServiceUtils.getService(
                    bundleContext,
                    ConfigurationService.class);
        boolean createCallByPhoneNumber
            = (cfg != null)
                && cfg.getBoolean(PNAME_CREATE_CALL_BY_PHONE_NUMBER, false);

        for (String participant : participants)
        {
            List<Contact> participantContacts
                = findContactsBySigninName(participant, opSetClass, 1);

            if (participantContacts.size() > 0)
            {
                contactList.add(
                        getSigninName(participantContacts.get(0), null));
            }
            else if (opSetClass.equals(OperationSetBasicTelephony.class))
            {
                /*
                 * The boolean ConfigurationService property
                 * PNAME_CREATE_CALL_BY_PHONE_NUMBER enables instructing whether
                 * a sign-in name which does not resolve to a Contact with
                 * support for OperationSetBasicTelephony is to be resolved to
                 * a phone number (via an associated vCard).
                 */
                if (createCallByPhoneNumber)
                {
                    Set<PhoneNumberDetail> participantPhoneNumbers
                        = findPhoneNumbersBySigninName(participant, 1);

                    if (participantPhoneNumbers.size() > 0)
                    {
                        contactList.add(
                                participantPhoneNumbers.iterator().next()
                                        .getNumber());
                    }
                }
                else
                {
                    /*
                     * There is no Contact for the specified participant which
                     * supports OperationSetBasicTelephony. Try without the
                     * support restriction.
                     */
                    participantContacts
                        = findContactsBySigninName(participant, null, 1);
                    if (participantContacts.size() > 0)
                    {
                        contactList.add(
                                getSigninName(
                                        participantContacts.get(0),
                                        null));
                    }
                    else
                    {
                        /*
                         * Well, just try to start a conversation with the
                         * unresolved contact.
                         */
                        contactList.add(participant);
                    }
                }
            }
        }

        final String[] contactArray
            = contactList.toArray(new String[contactList.size()]);

        /*
         * Secondly, start the conversation of the specified type with the
         * resolved Contacts.
         */
        SwingUtilities.invokeLater(
                new Runnable()
                {
                    public void run()
                    {
                        BundleContext bundleContext = Messenger.bundleContext;

                        if (bundleContext != null)
                        {
                            UIService uiService
                                = ServiceUtils.getService(
                                        bundleContext,
                                        UIService.class);

                            if (uiService != null)
                            {
                                switch (conversationType)
                                {
                                case CONVERSATION_TYPE_AUDIO:
                                case CONVERSATION_TYPE_PHONE:
                                case CONVERSATION_TYPE_PSTN:
                                    uiService.createCall(contactArray);
                                    break;
                                case CONVERSATION_TYPE_IM:
                                    uiService.startChat(contactArray);
                                    break;
                                }
                            }
                        }
                    }
                });
    }

    /**
     * Represents a presence status reported by a specific protocol. Allows
     * distinguishing statuses more specific than the ranges defined by the
     * <tt>PresenceStatus</tt> class.
     */
    private static class ProtocolPresenceStatus
    {
        /**
         * The <tt>PresenceStatus</tt> instance represented by this instance.
         */
        private PresenceStatus presenceStatus;

        /**
         * The name of the protocol from which {@link #presenceStatus} has
         * originated. Allows translating <tt>presenceStatus</tt> to a
         * <tt>MISTATUS</tt> value which is equivalent to a protocol-specific
         * status not defined in the generic <tt>PresenceStatus</tt> class.
         */
        private String protocolName;

        /**
         * Initializes a new <tt>ProtocolPresenceStatus</tt> instance which is
         * to represent a specific <tt>PresenceStatus</tt> originating from a
         * specific protocol.
         *
         * @param protocolName the name of the protocol from which the specified
         * <tt>PresenceStatus</tt> has originated
         * @param presenceStatus the <tt>PresenceStatus</tt> to be represented
         * by the new instance
         */
        public ProtocolPresenceStatus(
                String protocolName,
                PresenceStatus presenceStatus)
        {
            setPresenceStatus(protocolName, presenceStatus);
        }

        /**
         * Returns <tt>-1</tt>, <tt>0</tt> or <tt>1</tt> if the
         * <tt>PresenceStatus</tt> represented by this instance is,
         * respectively, less than, equal to or greater than a specific
         * <tt>PresenceStatus</tt>.
         *
         * @param presenceStatus the <tt>PresenceStatus</tt> this instance is to
         * be compared to
         * @return <tt>-1</tt>, <tt>0</tt> or <tt>1</tt> if the
         * <tt>PresenceStatus</tt> represented by this instance is, respectively,
         * less than, equal to or greater than the specified
         * <tt>presenceStatus</tt>
         */
        public int compareTo(PresenceStatus presenceStatus)
        {
            return this.presenceStatus.compareTo(presenceStatus);
        }

        /**
         * Returns <tt>-1</tt>, <tt>0</tt> or <tt>1</tt> if the
         * <tt>PresenceStatus</tt> represented by this instance is,
         * respectively, less than, equal to or greater than the
         * <tt>PresenceStatus</tt> represented by a specific
         * <tt>ProtocolPresenceStatus</tt> instance.
         *
         * @param protocolPresenceStatus the <tt>ProtocolPresenceStatus</tt>
         * this instance is to be compared to
         * @return <tt>-1</tt>, <tt>0</tt> or <tt>1</tt> if the
         * <tt>PresenceStatus</tt> represented by this instance is, respectively,
         * less than, equal to or greater than the specified
         * <tt>protocolPresenceStatus</tt>
         */
        public int compareTo(ProtocolPresenceStatus protocolPresenceStatus)
        {
            return compareTo(protocolPresenceStatus.presenceStatus);
        }

        /**
         * Sets the <tt>PresenceStatus</tt> to be represented by this instance.
         *
         * @param protocolName the name of the protocol from which the
         * <tt>PresenceStatus</tt> to be set on this instance has originated
         * @param presenceStatus the <tt>PresenceStatus</tt> to be represented
         * by this instance
         */
        public void setPresenceStatus(
                String protocolName,
                PresenceStatus presenceStatus)
        {
            this.protocolName = protocolName;
            this.presenceStatus = presenceStatus;
        }

        /**
         * Gets an <tt>int</tt> value in the terms of <tt>PresenceStatus</tt>
         * which is equivalent to the <tt>PresenceStatus</tt> represented by
         * this instance.
         *
         * @return an <tt>int</tt> value in the terms of <tt>PresenceStatus</tt>
         * which is equivalent to the <tt>PresenceStatus</tt> represented by
         * this instance
         */
        public int toInt()
        {
            return presenceStatus.getStatus();
        }

        /**
         * Gets a <tt>MISTATUS</tt> value which is equivalent to the
         * <tt>PresenceStatus</tt> represented by this instance.
         *
         * @return a <tt>MISTATUS</tt> value which is equivalent to the
         * <tt>PresenceStatus</tt> represented by this instance
         */
        public int toMISTATUS()
        {
            return toMISTATUS(protocolName, presenceStatus);
        }

        /**
         * Gets a <tt>MISTATUS</tt> value which is equivalent to the
         * <tt>PresenceStatus</tt> represented by a specific
         * <tt>ProtocolPresenceStatus</tt> instance.
         *
         * @param protocolPresenceStatus the <tt>ProtocolPresenceStatus</tt> to
         * get an equivalent <tt>MISTATUS</tt> value for
         * @return a <tt>MISTATUS</tt> value which is equivalent to the
         * <tt>PresenceStatus</tt> represented by the specified
         * <tt>protocolPresenceStatus</tt>
         */
        public static int toMISTATUS(
                ProtocolPresenceStatus protocolPresenceStatus)
        {
            return
                (protocolPresenceStatus == null)
                    ? MISTATUS_UNKNOWN
                    : protocolPresenceStatus.toMISTATUS();
        }

        /**
         * Gets a <tt>MISTATUS</tt> value which is equivalent to a specific
         * <tt>PresenceStatus</tt> which has originated from a protocol with a
         * specific name.
         *
         * @param protocolName the name of the protocol from which the specified
         * <tt>PresenceStatus</tt> has originated
         * @param presenceStatus the <tt>PresenceStatus</tt> for which an
         * equivalent <tt>MISTATUS</tt> value is to be retrieved
         * @return a <tt>MISTATUS</tt> value which is equivalent to the
         * specified <tt>presenceStatus</tt> in the context of the protocol with
         * the specified <tt>protocolName</tt>
         */
        public static int toMISTATUS(
                String protocolName,
                PresenceStatus presenceStatus)
        {
            int i
                = (presenceStatus == null)
                    ? Integer.MIN_VALUE
                    : presenceStatus.getStatus();
            int mistatus;

            if (i == Integer.MIN_VALUE)
                mistatus = MISTATUS_UNKNOWN;
            else
            {
                if ((i == 31 /* FIXME */)
                        && ProtocolNames.JABBER.equalsIgnoreCase(protocolName)
                        && JabberStatusEnum.ON_THE_PHONE.equalsIgnoreCase(
                                presenceStatus.getStatusName()))
                {
                    mistatus = MISTATUS_ON_THE_PHONE;
                }
                else if ((i == 32 /* FIXME */)
                    && ProtocolNames.JABBER.equalsIgnoreCase(protocolName)
                    && JabberStatusEnum.IN_A_MEETING.equalsIgnoreCase(
                            presenceStatus.getStatusName()))
                {
                    mistatus = MISTATUS_IN_A_MEETING;
                }
                else if (i < PresenceStatus.ONLINE_THRESHOLD)
                    mistatus = MISTATUS_OFFLINE;
                else if (i < PresenceStatus.AWAY_THRESHOLD)
                    mistatus = MISTATUS_MAY_BE_AVAILABLE;
                else if (i < PresenceStatus.AVAILABLE_THRESHOLD)
                    mistatus = MISTATUS_AWAY;
                else
                    mistatus = MISTATUS_ONLINE;
            }
            return mistatus;
        }
    }

    /**
     * Describes a (local) account which corresponds to an
     * <tt>IMessengerContact</tt> implementation having <tt>true</tt> as the
     * value of its <tt>self</tt> boolean property.
     */
    private static class Self
        implements ContactPresenceStatusListener,
                   ProviderPresenceStatusListener
    {
        private final Map<ProtocolProviderService, OperationSetPresence> ppss
            = new HashMap<ProtocolProviderService, OperationSetPresence>();

        /**
         * The <tt>PresenceStatus</tt> of this (local) account and the name of
         * the protocol from which it has originated.
         */
        private ProtocolPresenceStatus presenceStatus;

        /**
         * The sign-in name associated with this (local) account.
         */
        public final String signinName;

        /**
         * Initializes a new <tt>Self</tt> instance which is to describe a
         * (local) account associated with a specific sign-in name.
         *
         * @param signinName the sign-in name to be associated with the new
         * instance
         */
        public Self(String signinName)
        {
            this.signinName = signinName;
        }

        void addProtocolProviderService(
                ProtocolProviderService pps,
                OperationSetPresence presenceOpSet)
        {
            if (!ppss.containsKey(pps))
            {
                ppss.put(pps, presenceOpSet);

                presenceOpSet.addContactPresenceStatusListener(this);
                presenceOpSet.addProviderPresenceStatusListener(this);
                providerStatusChanged(null);
            }
        }

        public void contactPresenceStatusChanged(
                ContactPresenceStatusChangeEvent event)
        {
            String signinName
                = getSigninName(
                        event.getSourceContact(),
                        event.getSourceProvider());

            if (signinName != null)
            {
                String oldProtocolName
                    = event.getSourceProvider().getProtocolName();
                PresenceStatus oldStatus = event.getOldStatus();

                Messenger.onContactStatusChange(
                        signinName,
                        ProtocolPresenceStatus.toMISTATUS(
                                oldProtocolName,
                                oldStatus));
            }
        }

        /**
         * Disposes this instance by releasing the resources it has acquired by
         * now. Removes this instance as a listener from the associated
         * <tt>OperationSetPresence</tt> instances.
         */
        void dispose()
        {
            Iterator<Map.Entry<ProtocolProviderService, OperationSetPresence>>
                it
                    = ppss.entrySet().iterator();

            while (it.hasNext())
            {
                Map.Entry<ProtocolProviderService, OperationSetPresence> e
                    = it.next();
                OperationSetPresence presenceOpSet = e.getValue();

                presenceOpSet.removeContactPresenceStatusListener(this);
                presenceOpSet.removeProviderPresenceStatusListener(this);
                it.remove();
            }
        }

        /**
         * Gets the <tt>Contact</tt> instances which are associated with a
         * specific <tt>IMessengerContact</tt> sign-in name and which support a
         * specific <tt>OperationSet</tt>.
         *
         * @param signinName the <tt>IMessengerContact</tt> sign-in name for
         * which the associated <tt>Contact</tt> instances are to be found
         * @param opSetClass the <tt>OperationSet</tt> class to be supported by
         * the possibly found <tt>Contact</tt> instances or <tt>null</tt> if no
         * specific <tt>OperationSet</tt> class is required of the possibly
         * found <tt>Contact</tt> instances
         * @param limit the maximum number of found <tt>Contact</tt>s at which
         * the search should stop or {@link Integer#MAX_VALUE} if the search is
         * to be unbound with respect to the number of found <tt>Contact</tt>s
         * @param contacts a list with <tt>Contact</tt> element type which is to
         * receive the possibly found <tt>Contact</tt> instances
         */
        void findContactsBySigninName(
                String signinName,
                Class<? extends OperationSet> opSetClass,
                int limit,
                List<Contact> contacts)
        {
            for (Map.Entry<ProtocolProviderService, OperationSetPresence> e
                    : ppss.entrySet())
            {
                ProtocolProviderService pps = e.getKey();
                OperationSetContactCapabilities contactCapabilitiesOpSet
                    = (opSetClass == null)
                        ? null
                        : pps.getOperationSet(
                                OperationSetContactCapabilities.class);

                for (Contact contact
                        : Messenger.findContactsBySigninName(
                                pps,
                                e.getValue(),
                                signinName))
                {
                    if ((contactCapabilitiesOpSet == null)
                            || (contactCapabilitiesOpSet.getOperationSet(
                                        contact,
                                        opSetClass)
                                    != null))
                    {
                        contacts.add(contact);
                        /*
                         * Obey the specified limit of the number of found
                         * Contacts.
                         */
                        if (contacts.size() >= limit)
                            break;
                    }
                }
                /* Obey the specified limit of the number of found Contacts. */
                if (contacts.size() >= limit)
                    break;
            }
        }

        /**
         * Gets the <tt>PhoneNumberDetail</tt> instances which are associated
         * with a specific <tt>IMessengerContact</tt> sign-in name.
         *
         * @param signinName the <tt>IMessengerContact</tt> sign-in name for
         * which the associated <tt>PhoneNumberDetail</tt> instances are to be
         * found
         * @param limit the maximum number of found <tt>PhoneNumberDetail</tt>s
         * at which the search should stop or {@link Integer#MAX_VALUE} if the
         * search is to be unbound with respect to the number of found
         * <tt>PhoneNumberDetail</tt>s
         * @param phoneNumbers a list with <tt>PhoneNumberDetail</tt> element
         * type which is to receive the possibly found
         * <tt>PhoneNumberDetail</tt> instances
         */
        void findPhoneNumbersBySigninName(
                String signinName,
                int limit,
                Set<PhoneNumberDetail> phoneNumbers)
        {
            for (Map.Entry<ProtocolProviderService, OperationSetPresence> e
                    : ppss.entrySet())
            {
                ProtocolProviderService pps = e.getKey();
                OperationSetServerStoredContactInfo serverStoredContactInfoOpSet
                    = pps.getOperationSet(
                            OperationSetServerStoredContactInfo.class);

                if (serverStoredContactInfoOpSet == null)
                    continue;

                for (Contact contact
                        : Messenger.findContactsBySigninName(
                                pps,
                                e.getValue(),
                                signinName))
                {
                    Iterator<PhoneNumberDetail> iter
                        = serverStoredContactInfoOpSet.getDetailsAndDescendants(
                                contact,
                                PhoneNumberDetail.class);

                    if (iter == null)
                        continue;

                    while (iter.hasNext())
                    {
                        PhoneNumberDetail phoneNumber = iter.next();

                        if (getMPHONE_TYPE(phoneNumber) != MPHONE_TYPE_CUSTOM)
                        {
                            phoneNumbers.add(phoneNumber);
                            /*
                             * Obey the specified limit of the number of found
                             * PhoneNumberDetails.
                             */
                            if (phoneNumbers.size() >= limit)
                                break;
                        }
                    }
                }
                /*
                 * Obey the specified limit of the number of found
                 * PhoneNumberDetails.
                 */
                if (phoneNumbers.size() >= limit)
                    break;
            }
        }

        /**
         * Gets the <tt>PresenceStatus</tt> of this instance and the name of the
         * protocol from which it has originated.
         *
         * @return the <tt>PresenceStatus</tt> of this instance and the name of
         * the protocol from which it has originated
         */
        ProtocolPresenceStatus getPresenceStatus()
        {
            return presenceStatus;
        }

        /**
         * Gets the <tt>PresenceStatus</tt> of a <tt>Contact</tt> known to this
         * instance to be associated with a specific <tt>IMessengerContact</tt>
         * sign-in name.
         *
         * @param signinName the sign-in name associated with the
         * <tt>IMessengerContact</tt> whose <tt>PresenceStatus</tt> is to be
         * retrieved
         * @return the <tt>PresenceStatus</tt> and the name of the protocol from
         * which it has originated of a <tt>Contact</tt> known to this instance
         * to be associated with the specified <tt>signinName</tt> or
         * <tt>null</tt> if no such association is known to this instance
         */
        ProtocolPresenceStatus getPresenceStatus(String signinName)
        {
            ProtocolPresenceStatus presenceStatus;

            if (this.signinName.equalsIgnoreCase(signinName))
                presenceStatus = getPresenceStatus();
            else
            {
                presenceStatus = null;
                for (Map.Entry<ProtocolProviderService, OperationSetPresence> e
                        : ppss.entrySet())
                {
                    try
                    {
                        ProtocolProviderService pps = e.getKey();
                        Iterable<Contact> contacts
                            = Messenger.findContactsBySigninName(
                                    pps,
                                    e.getValue(),
                                    signinName);
                        String protocolName = pps.getProtocolName();

                        for (Contact contact : contacts)
                        {
                            PresenceStatus contactPresenceStatus
                                = contact.getPresenceStatus();

                            if (contactPresenceStatus != null)
                            {
                                if (presenceStatus == null)
                                {
                                    presenceStatus
                                        = new ProtocolPresenceStatus(
                                                protocolName,
                                                contactPresenceStatus);
                                }
                                else if (presenceStatus.compareTo(
                                            contactPresenceStatus)
                                        < 0)
                                {
                                    presenceStatus.setPresenceStatus(
                                            protocolName,
                                            contactPresenceStatus);
                                }
                                if (presenceStatus.toInt()
                                        >= PresenceStatus.MAX_STATUS_VALUE)
                                    break;
                            }
                        }
                        if ((presenceStatus != null)
                                && (presenceStatus.toInt()
                                        >= PresenceStatus.MAX_STATUS_VALUE))
                            break;
                    }
                    catch (Throwable t)
                    {
                        /*
                         * It does not sound like it makes a lot of sense to
                         * fail the getting of the presence status of the
                         * specified signinName just because one of the possibly
                         * many OperationSetPresence instances has failed.
                         * Additionally, the native counterpart will swallow any
                         * Java exception anyway.
                         */
                        if (t instanceof ThreadDeath)
                            throw (ThreadDeath) t;
                        else
                            t.printStackTrace(System.err);
                    }
                }
            }
            return presenceStatus;
        }

        /**
         * Gets an indicator which determines whether the (local) account
         * described by this <tt>Self</tt> instance is associated with a
         * specific sign-in name.
         *
         * @param signinName the sign-in name to be determined whether it is
         * associated with this <tt>Self</tt> instance
         * @return <tt>true</tt> if the specified <tt>signinName</tt> is
         * associated with the (local) account described by this <tt>Self</tt>
         * instance
         */
        boolean isSelf(String signinName)
        {
            boolean self;

            if (this.signinName.equalsIgnoreCase(signinName))
                self = true;
            else
            {
                self = false;
                for (ProtocolProviderService pps : ppss.keySet())
                {
                    if(!pps.isRegistered())
                    {
                        continue;
                    }

                    OperationSetServerStoredAccountInfo
                        serverStoredAccountInfoOpSet
                            = pps.getOperationSet(
                                    OperationSetServerStoredAccountInfo.class);

                    if (serverStoredAccountInfoOpSet != null)
                    {
                        for (Iterator<EmailAddressDetail> emailAddressDetailIt
                                    = serverStoredAccountInfoOpSet
                                        .getDetailsAndDescendants(
                                                EmailAddressDetail.class);
                                emailAddressDetailIt.hasNext();)
                        {
                            EmailAddressDetail emailAddressDetail
                                = emailAddressDetailIt.next();

                            if (signinName.equalsIgnoreCase(
                                    emailAddressDetail.getEMailAddress()))
                            {
                                self = true;
                                break;
                            }
                        }
                        if (self)
                            break;
                    }
                }
            }
            return self;
        }

        public void providerStatusChanged(
                ProviderPresenceStatusChangeEvent event)
        {
            ProtocolPresenceStatus protocolPresenceStatus = null;

            for (Map.Entry<ProtocolProviderService, OperationSetPresence> e
                    : ppss.entrySet())
            {
                OperationSetPresence presenceOpSet = e.getValue();
                PresenceStatus presenceOpSetStatus
                    = presenceOpSet.getPresenceStatus();

                if (presenceOpSetStatus != null)
                {
                    if (protocolPresenceStatus == null)
                    {
                        protocolPresenceStatus
                            = new ProtocolPresenceStatus(
                                    e.getKey().getProtocolName(),
                                    presenceOpSetStatus);
                    }
                    else if (protocolPresenceStatus.compareTo(
                                presenceOpSetStatus)
                            < 0)
                    {
                        protocolPresenceStatus.setPresenceStatus(
                                e.getKey().getProtocolName(),
                                presenceOpSetStatus);
                    }
                    if (protocolPresenceStatus.toInt()
                            >= PresenceStatus.MAX_STATUS_VALUE)
                        break;
                }
            }

            setPresenceStatus(protocolPresenceStatus);
        }

        public void providerStatusMessageChanged(PropertyChangeEvent event) {}

        int removeProtocolProviderService(ProtocolProviderService pps)
        {
            OperationSetPresence presenceOpSet = ppss.get(pps);

            if (presenceOpSet != null)
            {
                presenceOpSet.removeContactPresenceStatusListener(this);
                presenceOpSet.removeProviderPresenceStatusListener(this);
                ppss.remove(pps);
                providerStatusChanged(null);
            }
            return ppss.size();
        }

        /**
         * Sets the <tt>PresenceStatus</tt> of this instance.
         *
         * @param presenceStatus the <tt>PresenceStatus</tt> and the name of the
         * protocol from which it has originated
         */
        private void setPresenceStatus(ProtocolPresenceStatus presenceStatus)
        {
            int oldMISTATUS
                = ProtocolPresenceStatus.toMISTATUS(this.presenceStatus);
            int newMISTATUS = ProtocolPresenceStatus.toMISTATUS(presenceStatus);

            if (oldMISTATUS != newMISTATUS)
            {
                this.presenceStatus = presenceStatus;

                Messenger.onContactStatusChange(
                        signinName,
                        oldMISTATUS);
            }
        }
    }
}
