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

import java.awt.*;
import java.lang.ref.*;
import java.text.*;
import java.util.*;
import java.util.List;
import java.util.regex.*;

import javax.swing.*;

import net.java.sip.communicator.impl.gui.*;
import net.java.sip.communicator.impl.gui.main.*;
import net.java.sip.communicator.impl.gui.main.contactlist.*;
import net.java.sip.communicator.plugin.desktoputil.*;
import net.java.sip.communicator.plugin.desktoputil.transparent.*;
import net.java.sip.communicator.service.contactlist.*;
import net.java.sip.communicator.service.contactsource.*;
import net.java.sip.communicator.service.gui.*;
import net.java.sip.communicator.service.protocol.*;
import net.java.sip.communicator.service.protocol.event.*;
import net.java.sip.communicator.service.protocol.media.*;
import net.java.sip.communicator.util.*;
import net.java.sip.communicator.util.Logger;
import net.java.sip.communicator.util.account.*;

import org.jitsi.service.neomedia.*;
import org.jitsi.service.neomedia.codec.*;
import org.jitsi.service.neomedia.device.*;
import org.jitsi.service.neomedia.format.*;
import org.jitsi.service.resources.*;
import org.jitsi.util.*;

/**
 * The <tt>CallManager</tt> is the one that handles calls. It contains also
 * the "Call" and "Hang up" buttons panel. Here are handles incoming and
 * outgoing calls from and to the call operation set.
 *
 * @author Yana Stamcheva
 * @author Lyubomir Marinov
 * @author Boris Grozev
 */
public class CallManager
{
    /**
     * The <tt>Logger</tt> used by the <tt>CallManager</tt> class and its
     * instances for logging output.
     */
    private static final Logger logger = Logger.getLogger(CallManager.class);

    /**
     * The name of the property which indicates whether the user should be
     * warned when starting a desktop sharing session.
     */
    private static final String desktopSharingWarningProperty
        = "net.java.sip.communicator.impl.gui.main"
            + ".call.SHOW_DESKTOP_SHARING_WARNING";

    /**
     * The name of the property which indicates whether the preferred provider
     * will be used when calling UIContact (call history).
     */
    private static final String IGNORE_PREFERRED_PROVIDER_PROP
        = "net.java.sip.communicator.impl.gui.main"
            + ".call.IGNORE_PREFERRED_PROVIDER_PROP";

    /**
     * The <tt>CallPanel</tt>s opened by <tt>CallManager</tt> (because
     * <tt>CallContainer</tt> does not give access to such lists.)
     */
    private static final Map<CallConference, CallPanel> callPanels
        = new HashMap<CallConference, CallPanel>();

    /**
     * A map of active outgoing calls per <tt>UIContactImpl</tt>.
     */
    private static Map<Call, UIContactImpl> uiContactCalls;

    /**
     * The group of notifications dedicated to missed calls.
     */
    private static UINotificationGroup missedCallGroup;

    /**
     * A <tt>CallListener</tt>.
     */
    public static class GuiCallListener
        extends SwingCallListener
    {
        /**
         * Maps for incoming call handlers. The handlers needs to be created
         * in the protocol thread while their method
         * incomingCallReceivedInEventDispatchThread will be called on EDT.
         * On the protocol thread a call state changed listener is added,
         * if this is done on the EDT there is a almost no gap between incoming
         * CallEvent and call state changed when doing auto answer and we
         * end up with call answered and dialog for incoming call.
         */
        private Map<CallEvent,WeakReference<IncomingCallHandler>>
            inCallHandlers = Collections.synchronizedMap(
                new WeakHashMap<CallEvent,
                                WeakReference<IncomingCallHandler>>());

        /**
         * Delivers the <tt>CallEvent</tt> in the protocol thread.
         */
        public void incomingCallReceived(CallEvent ev)
        {
            inCallHandlers.put(
                ev,
                new WeakReference<IncomingCallHandler>(
                        new IncomingCallHandler(ev.getSourceCall())));

            super.incomingCallReceived(ev);
        }

        /**
         * Implements {@link CallListener#incomingCallReceived(CallEvent)}. When
         * a call is received, creates a <tt>ReceivedCallDialog</tt> and plays
         * the ring phone sound to the user.
         *
         * @param ev the <tt>CallEvent</tt>
         */
        @Override
        public void incomingCallReceivedInEventDispatchThread(CallEvent ev)
        {
            WeakReference<IncomingCallHandler> ihRef
                = inCallHandlers.remove(ev);

            if(ihRef != null)
            {
                ihRef.get().incomingCallReceivedInEventDispatchThread(ev);
            }
        }

        /**
         * Implements CallListener.callEnded. Stops sounds that are playing at
         * the moment if there're any. Removes the <tt>CallPanel</tt> and
         * disables the hang-up button.
         *
         * @param ev the <tt>CallEvent</tt> which specifies the <tt>Call</tt>
         * that has ended
         */
        @Override
        public void callEndedInEventDispatchThread(CallEvent ev)
        {
            CallConference callConference = ev.getCallConference();

            closeCallContainerIfNotNecessary(callConference);

            /*
             * Notify the existing CallPanels about the CallEvent (in case
             * they need to update their UI, for example).
             */
            forwardCallEventToCallPanels(ev);

            // If we're currently in the call history view, refresh
            // it.
            TreeContactList contactList
                = GuiActivator.getContactList();

            if (contactList.getCurrentFilter().equals(
                    TreeContactList.historyFilter))
            {
                contactList.applyFilter(
                        TreeContactList.historyFilter);
            }
        }

        /**
         * Creates and opens a call dialog. Implements
         * {@link CallListener#outgoingCallCreated(CallEvent)}.
         *
         * @param ev the <tt>CallEvent</tt>
         */
        @Override
        public void outgoingCallCreatedInEventDispatchThread(CallEvent ev)
        {
            Call sourceCall = ev.getSourceCall();

            openCallContainerIfNecessary(sourceCall);

            /*
             * Notify the existing CallPanels about the CallEvent (in case they
             * need to update their UI, for example).
             */
            forwardCallEventToCallPanels(ev);
        }
    }

    /**
     * Handles incoming calls. Must be created on the protocol thread while the
     * method incomingCallReceivedInEventDispatchThread is executed on the EDT.
     */
    private static class IncomingCallHandler
        extends CallChangeAdapter
    {
        /**
         * The dialog shown
         */
        private ReceivedCallDialog receivedCallDialog;

        /**
         * Peer name.
         */
        private String peerName;

        /**
         * The time of the incoming call.
         */
        private long callTime;

        /**
         * Construct
         * @param sourceCall
         */
        IncomingCallHandler(Call sourceCall)
        {
            Iterator<? extends CallPeer> peerIter = sourceCall.getCallPeers();

            if(!peerIter.hasNext())
            {
                return;
            }

            peerName = peerIter.next().getDisplayName();
            callTime = System.currentTimeMillis();

            sourceCall.addCallChangeListener(this);
        }

        /**
         * State has changed.
         * @param ev
         */
        @Override
        public void callStateChanged(final CallChangeEvent ev)
        {
            if(!SwingUtilities.isEventDispatchThread())
            {
                SwingUtilities.invokeLater(
                        new Runnable()
                        {
                            public void run()
                            {
                                callStateChanged(ev);
                            }
                        });
                return;
            }
            if (!CallChangeEvent.CALL_STATE_CHANGE
                    .equals(ev.getPropertyName()))
                return;

            // When the call state changes, we ensure here that the
            // received call notification dialog is closed.
            if (receivedCallDialog != null && receivedCallDialog.isVisible())
                receivedCallDialog.setVisible(false);

            // Ensure that the CallDialog is created, because it is the
            // one that listens for CallPeers.
            Object newValue = ev.getNewValue();
            Call call = ev.getSourceCall();

            if (CallState.CALL_INITIALIZATION.equals(newValue)
                    || CallState.CALL_IN_PROGRESS.equals(newValue))
            {
                openCallContainerIfNecessary(call);
            }
            else if (CallState.CALL_ENDED.equals(newValue))
            {
                if (ev.getOldValue().equals(
                        CallState.CALL_INITIALIZATION))
                {
                    // If the call was answered elsewhere, don't mark it
                    // as missed.
                    CallPeerChangeEvent cause = ev.getCause();

                    if ((cause == null)
                            || (cause.getReasonCode()
                                    != CallPeerChangeEvent
                                            .NORMAL_CALL_CLEARING))
                    {
                        addMissedCallNotification(peerName, callTime);
                    }
                }

                call.removeCallChangeListener(this);
            }
        }

        /**
         * Executed on EDT cause will create dialog and will show it.
         * @param ev
         */
        public void incomingCallReceivedInEventDispatchThread(CallEvent ev)
        {
            Call sourceCall = ev.getSourceCall();
            boolean isVideoCall
                = ev.isVideoCall()
                    && ConfigurationUtils.hasEnabledVideoFormat(
                            sourceCall.getProtocolProvider());
            receivedCallDialog = new ReceivedCallDialog(
                sourceCall,
                isVideoCall,
                (CallManager.getInProgressCalls().size() > 0),
                ev.isDesktopStreaming());

            receivedCallDialog.setVisible(true);

            Iterator<? extends CallPeer> peerIter = sourceCall.getCallPeers();

            if(!peerIter.hasNext())
            {
                if (receivedCallDialog.isVisible())
                    receivedCallDialog.setVisible(false);
                return;
            }

            /*
             * Notify the existing CallPanels about the CallEvent (in case they
             * need to update their UI, for example).
             */
            forwardCallEventToCallPanels(ev);
        }
    }

    /**
     * Answers the given call.
     *
     * @param call the call to answer
     */
    public static void answerCall(Call call)
    {
        answerCall(call, null, false /* without video */);
    }

    /**
     * Answers a specific <tt>Call</tt> with or without video and, optionally,
     * does that in a telephony conference with an existing <tt>Call</tt>.
     *
     * @param call
     * @param existingCall
     * @param video
     */
    private static void answerCall(Call call, Call existingCall, boolean video)
    {
        if (existingCall == null)
            openCallContainerIfNecessary(call);

        new AnswerCallThread(call, existingCall, video).start();
    }

    /**
     * Answers the given call in an existing call. It will end up with a
     * conference call.
     *
     * @param call the call to answer
     */
    public static void answerCallInFirstExistingCall(Call call)
    {
        // Find the first existing call.
        Iterator<Call> existingCallIter = getInProgressCalls().iterator();
        Call existingCall
            = existingCallIter.hasNext() ? existingCallIter.next() : null;

        answerCall(call, existingCall, false /* without video */);
    }

    /**
     * Merges specific existing <tt>Call</tt>s into a specific telephony
     * conference.
     *
     * @param conference the conference
     * @param calls list of calls
     */
    public static void mergeExistingCalls(
            CallConference conference,
            Collection<Call> calls)
    {
        new MergeExistingCalls(conference, calls).start();
    }

    /**
     * Answers the given call with video.
     *
     * @param call the call to answer
     */
    public static void answerVideoCall(Call call)
    {
        answerCall(call, null, true /* with video */);
    }

    /**
     * Hang ups the given call.
     *
     * @param call the call to hang up
     */
    public static void hangupCall(Call call)
    {
        new HangupCallThread(call).start();
    }

    /**
     * Hang ups the given <tt>callPeer</tt>.
     *
     * @param peer the <tt>CallPeer</tt> to hang up
     */
    public static void hangupCallPeer(CallPeer peer)
    {
        new HangupCallThread(peer).start();
    }

    /**
     * Asynchronously hangs up the <tt>Call</tt>s participating in a specific
     * <tt>CallConference</tt>.
     *
     * @param conference the <tt>CallConference</tt> whose participating
     * <tt>Call</tt>s are to be hanged up
     */
    public static void hangupCalls(CallConference conference)
    {
        new HangupCallThread(conference).start();
    }

    /**
     * Creates a call to the contact represented by the given string.
     *
     * @param protocolProvider the protocol provider to which this call belongs.
     * @param contact the contact to call to
     */
    public static void createCall(  ProtocolProviderService protocolProvider,
                                    String contact)
    {
        new CreateCallThread(protocolProvider, contact, false /* audio-only */)
            .start();
    }

    /**
     * Creates a call to the contact represented by the given string.
     *
     * @param protocolProvider the protocol provider to which this call belongs.
     * @param contact the contact to call to
     * @param uiContact the meta contact we're calling
     */
    public static void createCall(  ProtocolProviderService protocolProvider,
                                    String contact,
                                    UIContactImpl uiContact)
    {
        new CreateCallThread(protocolProvider, null, null, uiContact,
            contact, null, null, false /* audio-only */).start();
    }

    /**
     * Creates a video call to the contact represented by the given string.
     *
     * @param protocolProvider the protocol provider to which this call belongs.
     * @param contact the contact to call to
     */
    public static void createVideoCall(ProtocolProviderService protocolProvider,
                                        String contact)
    {
        new CreateCallThread(protocolProvider, contact, true /* video */)
            .start();
    }

    /**
     * Creates a video call to the contact represented by the given string.
     *
     * @param protocolProvider the protocol provider to which this call belongs.
     * @param contact the contact to call to
     * @param uiContact the <tt>UIContactImpl</tt> we're calling
     */
    public static void createVideoCall( ProtocolProviderService protocolProvider,
                                        String contact,
                                        UIContactImpl uiContact)
    {
        new CreateCallThread(protocolProvider, null, null, uiContact,
            contact, null, null, true /* video */).start();
    }

    /**
     * Enables/disables local video for a specific <tt>Call</tt>.
     *
     * @param call the <tt>Call</tt> to enable/disable to local video for
     * @param enable <tt>true</tt> to enable the local video; otherwise,
     * <tt>false</tt>
     */
    public static void enableLocalVideo(Call call, boolean enable)
    {
        new EnableLocalVideoThread(call, enable).start();
    }

    /**
     * Indicates if the desktop sharing is currently enabled for the given
     * <tt>call</tt>.
     *
     * @param call the <tt>Call</tt>, for which we would to check if the desktop
     * sharing is currently enabled
     * @return <tt>true</tt> if the desktop sharing is currently enabled for the
     * given <tt>call</tt>, <tt>false</tt> otherwise
     */
    public static boolean isLocalVideoEnabled(Call call)
    {
        OperationSetVideoTelephony telephony
            = call.getProtocolProvider().getOperationSet(
                    OperationSetVideoTelephony.class);

        return (telephony != null) && telephony.isLocalVideoAllowed(call);
    }

    /**
     * Creates a desktop sharing call to the contact represented by the given
     * string.
     *
     * @param protocolProvider the protocol provider to which this call belongs.
     * @param contact the contact to call to
     * @param uiContact the <tt>UIContactImpl</tt> we're calling
     */
    private static void createDesktopSharing(
            ProtocolProviderService protocolProvider,
            String contact,
            UIContactImpl uiContact)
    {
        // If the user presses cancel on the desktop sharing warning then we
        // have nothing more to do here.
        if (!showDesktopSharingWarning())
            return;

        MediaService mediaService = GuiActivator.getMediaService();
        List<MediaDevice> desktopDevices
            = mediaService.getDevices(MediaType.VIDEO, MediaUseCase.DESKTOP);
        int deviceNumber = desktopDevices.size();

        if (deviceNumber == 1)
        {
            createDesktopSharing(
                    protocolProvider,
                    contact,
                    uiContact,
                    desktopDevices.get(0),
                    true);
        }
        else if (deviceNumber > 1)
        {
            SelectScreenDialog selectDialog
                = new SelectScreenDialog(desktopDevices);

            selectDialog.setVisible(true);
            if (selectDialog.getSelectedDevice() != null)
                createDesktopSharing(
                        protocolProvider,
                        contact,
                        uiContact,
                        selectDialog.getSelectedDevice(),
                        true);
        }
    }

    /**
     * Creates a region desktop sharing through the given
     * <tt>protocolProvider</tt> with the given <tt>contact</tt>.
     *
     * @param protocolProvider the <tt>ProtocolProviderService</tt>, through
     * which the sharing session will be established
     * @param contact the address of the contact recipient
     * @param uiContact the <tt>UIContactImpl</tt> we're calling
     */
    private static void createRegionDesktopSharing(
                                    ProtocolProviderService protocolProvider,
                                    String contact,
                                    UIContactImpl uiContact)
    {
        if (showDesktopSharingWarning())
        {
            TransparentFrame frame = DesktopSharingFrame
                .createTransparentFrame(
                    protocolProvider, contact, uiContact, true);

            frame.setLocationRelativeTo(null);
            frame.setVisible(true);
        }
    }

    /**
     * Creates a desktop sharing call to the contact represented by the given
     * string.
     *
     * @param protocolProvider the protocol provider to which this call belongs.
     * @param contact the contact to call to
     * @param uiContact the <tt>MetaContact</tt> we're calling
     * @param x the x coordinate of the shared region
     * @param y the y coordinated of the shared region
     * @param width the width of the shared region
     * @param height the height of the shared region
     */
    public static void createRegionDesktopSharing(
                                    ProtocolProviderService protocolProvider,
                                    String contact,
                                    UIContactImpl uiContact,
                                    int x,
                                    int y,
                                    int width,
                                    int height)
    {
        MediaService mediaService = GuiActivator.getMediaService();

        List<MediaDevice> desktopDevices = mediaService.getDevices(
            MediaType.VIDEO, MediaUseCase.DESKTOP);

        int deviceNumber = desktopDevices.size();

        if (deviceNumber > 0)
        {
            createDesktopSharing(
                    protocolProvider,
                    contact,
                    uiContact,
                    mediaService.getMediaDeviceForPartialDesktopStreaming(
                        width,
                        height,
                        x,
                        y),
                    false);
        }
    }

    /**
     * Creates a desktop sharing call to the contact represented by the given
     * string.
     *
     * @param protocolProvider the protocol provider to which this call belongs.
     * @param contact the contact to call to
     * @param uiContact the <tt>UIContactImpl</tt> we're calling
     * @param mediaDevice the media device corresponding to the screen to share
     * @param fullscreen whether we are sharing the fullscreen
     */
    private static void createDesktopSharing(
                                    ProtocolProviderService protocolProvider,
                                    String contact,
                                    UIContactImpl uiContact,
                                    MediaDevice mediaDevice,
                                    boolean fullscreen)
    {
        new CreateDesktopSharingThread( protocolProvider,
                                        contact,
                                        uiContact,
                                        mediaDevice,
                                        fullscreen).start();
    }

    /**
     * Enables the desktop sharing in an existing <tt>call</tt>.
     *
     * @param call the call for which desktop sharing should be enabled
     * @param enable indicates if the desktop sharing should be enabled or
     * disabled
     */
    public static void enableDesktopSharing(Call call, boolean enable)
    {
        if (!enable)
            enableDesktopSharing(call, null, enable);
        else if (showDesktopSharingWarning())
        {
            MediaService mediaService = GuiActivator.getMediaService();
            List<MediaDevice> desktopDevices
                = mediaService.getDevices(MediaType.VIDEO, MediaUseCase.DESKTOP);
            int deviceNumber = desktopDevices.size();

            new FullScreenShareIndicator(call);

            if (deviceNumber == 1)
                enableDesktopSharing(call, null, enable);
            else if (deviceNumber > 1)
            {
                SelectScreenDialog selectDialog
                    = new SelectScreenDialog(desktopDevices);

                selectDialog.setVisible(true);

                if (selectDialog.getSelectedDevice() != null)
                    enableDesktopSharing(
                        call, selectDialog.getSelectedDevice(), enable);
            }
        }

        // in case we switch to video, disable remote control if it was
        // enabled
        enableDesktopRemoteControl(call.getCallPeers().next(), false);
    }

    /**
     * Enables the region desktop sharing for the given call.
     *
     * @param call the call, for which the region desktop sharing should be
     * enabled
     * @param enable indicates if the desktop sharing should be enabled or
     * disabled
     */
    public static void enableRegionDesktopSharing(Call call, boolean enable)
    {
        if (!enable)
            enableDesktopSharing(call, null, enable);
        else if (showDesktopSharingWarning())
        {
            TransparentFrame frame
                = DesktopSharingFrame.createTransparentFrame(call, true);

            frame.setVisible(true);
        }
    }

    /**
     * Creates a desktop sharing call to the contact represented by the given
     * string.
     *
     * @param call the call for which desktop sharing should be enabled
     * @param x the x coordinate of the shared region
     * @param y the y coordinated of the shared region
     * @param width the width of the shared region
     * @param height the height of the shared region
     */
    public static void enableRegionDesktopSharing(
                                    Call call,
                                    int x,
                                    int y,
                                    int width,
                                    int height)
    {
        // Use the default media device corresponding to the screen to share
        MediaService mediaService = GuiActivator.getMediaService();

        List<MediaDevice> desktopDevices = mediaService.getDevices(
            MediaType.VIDEO, MediaUseCase.DESKTOP);

        int deviceNumber = desktopDevices.size();

        if (deviceNumber > 0)
        {
            boolean succeed = enableDesktopSharing(
                    call,
                    mediaService.getMediaDeviceForPartialDesktopStreaming(
                        width,
                        height,
                        x,
                        y),
                    true);
            // If the region sharing succeed, then display the frame of the
            // current region shared.
            if(succeed)
            {
                TransparentFrame frame
                    = DesktopSharingFrame.createTransparentFrame(call, false);

                frame.setVisible(true);
            }
        }

        // in case we switch to video, disable remote control if it was
        // enabled
        enableDesktopRemoteControl(call.getCallPeers().next(), false);
    }

    /**
     * Enables the desktop sharing in an existing <tt>call</tt>.
     *
     * @param call the call for which desktop sharing should be enabled
     * @param mediaDevice the media device corresponding to the screen to share
     * @param enable indicates if the desktop sharing should be enabled or
     * disabled
     *
     * @return True if the desktop sharing succeed (we are currently sharing the
     * whole or a part of the desktop). False, otherwise.
     */
    private static boolean enableDesktopSharing(Call call,
                                            MediaDevice mediaDevice,
                                            boolean enable)
    {
        OperationSetDesktopStreaming desktopOpSet
            = call.getProtocolProvider().getOperationSet(
                OperationSetDesktopStreaming.class);
        boolean enableSucceeded = false;

        // This shouldn't happen at this stage, because we disable the button
        // if the operation set isn't available.
        if (desktopOpSet != null)
        {
            // First make sure the local video button is disabled.
            if (enable && isLocalVideoEnabled(call))
                getActiveCallContainer(call).setVideoButtonSelected(false);

            try
            {
                if (mediaDevice != null)
                {
                    desktopOpSet.setLocalVideoAllowed(
                            call,
                            mediaDevice,
                            enable);
                }
                else
                    desktopOpSet.setLocalVideoAllowed(call, enable);

                enableSucceeded = true;
            }
            catch (OperationFailedException ex)
            {
                logger.error(
                        "Failed to toggle the streaming of local video.",
                        ex);
            }
        }

        return (enable && enableSucceeded);
    }

    /**
     * Indicates if the desktop sharing is currently enabled for the given
     * <tt>call</tt>.
     *
     * @param call the <tt>Call</tt>, for which we would to check if the desktop
     * sharing is currently enabled
     * @return <tt>true</tt> if the desktop sharing is currently enabled for the
     * given <tt>call</tt>, <tt>false</tt> otherwise
     */
    public static boolean isDesktopSharingEnabled(Call call)
    {
        OperationSetDesktopStreaming desktopOpSet
            = call.getProtocolProvider().getOperationSet(
                OperationSetDesktopStreaming.class);

        if (desktopOpSet != null
            && desktopOpSet.isLocalVideoAllowed(call))
            return true;

        return false;
    }

    /**
     * Indicates if the desktop sharing is currently enabled for the given
     * <tt>call</tt>.
     *
     * @param call the <tt>Call</tt>, for which we would to check if the desktop
     * sharing is currently enabled
     * @return <tt>true</tt> if the desktop sharing is currently enabled for the
     * given <tt>call</tt>, <tt>false</tt> otherwise
     */
    public static boolean isRegionDesktopSharingEnabled(Call call)
    {
        OperationSetDesktopStreaming desktopOpSet
            = call.getProtocolProvider().getOperationSet(
                OperationSetDesktopStreaming.class);

        if (desktopOpSet != null
            && desktopOpSet.isPartialStreaming(call))
            return true;

        return false;
    }

    /**
     * Enables/disables remote control when in a desktop sharing session with
     * the given <tt>callPeer</tt>.
     *
     * @param callPeer the call peer for which we enable/disable remote control
     * @param isEnable indicates if the remote control should be enabled
     */
    public static void enableDesktopRemoteControl(  CallPeer callPeer,
                                                    boolean isEnable)
    {
        OperationSetDesktopSharingServer sharingOpSet
            = callPeer.getProtocolProvider().getOperationSet(
                OperationSetDesktopSharingServer.class);

        if (sharingOpSet == null)
            return;

        if (isEnable)
            sharingOpSet.enableRemoteControl(callPeer);
        else
            sharingOpSet.disableRemoteControl(callPeer);
    }

    /**
     * Creates a call to the given call string. The given component indicates
     * where should be shown the "call via" menu if needed.
     *
     * @param callString the string to call
     * @param c the component, which indicates where should be shown the "call
     * via" menu if needed
     */
    public static void createCall(  String callString,
                                    JComponent c)
    {
        createCall(callString, c, null);
    }

    /**
     * Creates a call to the given call string. The given component indicates
     * where should be shown the "call via" menu if needed.
     *
     * @param callString the string to call
     * @param c the component, which indicates where should be shown the "call
     * via" menu if needed
     * @param l listener that is notified when the call interface has been
     * started after call was created
     */
    public static void createCall(  String callString,
                                    JComponent c,
                                    CallInterfaceListener l)
    {
        callString = callString.trim();

        // Removes special characters from phone numbers.
        if (ConfigurationUtils.isNormalizePhoneNumber()
            && !NetworkUtils.isValidIPAddress(callString))
        {
            callString = GuiActivator.getPhoneNumberI18nService()
                .normalize(callString);
        }

        List<ProtocolProviderService> telephonyProviders
            = CallManager.getTelephonyProviders();

        if (telephonyProviders.size() == 1)
        {
            CallManager.createCall(
                telephonyProviders.get(0), callString);

            if (l != null)
                l.callInterfaceStarted();
        }
        else if (telephonyProviders.size() > 1)
        {
            /*
             * Allow plugins which do not have a (Jitsi) UI to create calls by
             * automagically picking up a telephony provider.
             */
            if (c == null)
            {
                ProtocolProviderService preferredTelephonyProvider = null;

                for (ProtocolProviderService telephonyProvider
                        : telephonyProviders)
                {
                    try
                    {
                        OperationSetPresence presenceOpSet
                            = telephonyProvider.getOperationSet(
                                    OperationSetPresence.class);

                        if ((presenceOpSet != null)
                                && (presenceOpSet.findContactByID(callString)
                                        != null))
                        {
                            preferredTelephonyProvider = telephonyProvider;
                            break;
                        }
                    }
                    catch (Throwable t)
                    {
                        if (t instanceof ThreadDeath)
                            throw (ThreadDeath) t;
                    }
                }
                if (preferredTelephonyProvider == null)
                    preferredTelephonyProvider = telephonyProviders.get(0);

                CallManager.createCall(preferredTelephonyProvider, callString);
                if (l != null)
                    l.callInterfaceStarted();
            }
            else
            {
                ChooseCallAccountPopupMenu chooseAccountDialog
                    = new ChooseCallAccountPopupMenu(
                            c,
                            callString,
                            telephonyProviders,
                            l);

                chooseAccountDialog.setLocation(c.getLocation());
                chooseAccountDialog.showPopupMenu();
            }
        }
        else
        {
            ResourceManagementService resources = GuiActivator.getResources();

            new ErrorDialog(
                    null,
                    resources.getI18NString("service.gui.WARNING"),
                    resources.getI18NString(
                            "service.gui.NO_ONLINE_TELEPHONY_ACCOUNT"))
                .showDialog();
        }
    }

    /**
     * Creates a call to the given list of contacts.
     *
     * @param protocolProvider the protocol provider to which this call belongs.
     * @param callees the list of contacts to call to
     */
    public static void createConferenceCall(
            String[] callees,
            ProtocolProviderService protocolProvider)
    {
        Map<ProtocolProviderService, List<String>> crossProtocolCallees
            = new HashMap<ProtocolProviderService, List<String>>();

        crossProtocolCallees.put(protocolProvider, Arrays.asList(callees));
        createConferenceCall(crossProtocolCallees);
    }

    /**
     * Invites the given list of <tt>callees</tt> to the given conference
     * <tt>call</tt>.
     *
     * @param callees the list of contacts to invite
     * @param call the protocol provider to which this call belongs
     */
    public static void inviteToConferenceCall(String[] callees, Call call)
    {
        Map<ProtocolProviderService, List<String>> crossProtocolCallees
            = new HashMap<ProtocolProviderService, List<String>>();

        crossProtocolCallees.put(
                call.getProtocolProvider(),
                Arrays.asList(callees));
        inviteToConferenceCall(crossProtocolCallees, call);
    }

    /**
     * Invites the given list of <tt>callees</tt> to the given conference
     * <tt>call</tt>.
     *
     * @param callees the list of contacts to invite
     * @param call existing call
     */
    public static void inviteToConferenceCall(
            Map<ProtocolProviderService, List<String>> callees,
            Call call)
    {
        new InviteToConferenceCallThread(callees, call).start();
    }

    /**
     * Invites specific <tt>callees</tt> to a specific telephony conference.
     *
     * @param callees the list of contacts to invite
     * @param conference the telephony conference to invite the specified
     * <tt>callees</tt> into
     */
    public static void inviteToConferenceCall(
            Map<ProtocolProviderService, List<String>> callees,
            CallConference conference)
    {
        /*
         * InviteToConferenceCallThread takes a specific Call but actually
         * invites to the telephony conference associated with the specified
         * Call (if any). In order to not change the signature of its
         * constructor at this time, just pick up a Call participating in the
         * specified telephony conference (if any).
         */
        Call call = null;

        if (conference != null)
        {
            List<Call> calls = conference.getCalls();

            if (!calls.isEmpty())
                call = calls.get(0);
        }

        new InviteToConferenceCallThread(callees, call).start();
    }

    /**
     * Asynchronously creates a new conference <tt>Call</tt> with a specific
     * list of participants/callees.
     *
     * @param callees the list of participants/callees to invite to a
     * newly-created conference <tt>Call</tt>
     */
    public static void createConferenceCall(
        Map<ProtocolProviderService, List<String>> callees)
    {
        new InviteToConferenceCallThread(callees, null).start();
    }

    /**
     * Asynchronously creates a new video bridge conference <tt>Call</tt> with
     * a specific list of participants/callees.
     *
     * @param callProvider the <tt>ProtocolProviderService</tt> to use for
     * creating the call
     * @param callees the list of participants/callees to invite to the
     * newly-created video bridge conference <tt>Call</tt>
     */
    public static void createJitsiVideobridgeConfCall(
                                        ProtocolProviderService callProvider,
                                        String[] callees)
    {
        new InviteToConferenceBridgeThread(callProvider, callees, null).start();
    }

    /**
     * Invites the given list of <tt>callees</tt> to the given conference
     * <tt>call</tt>.
     *
     * @param callees the list of contacts to invite
     * @param call the protocol provider to which this call belongs
     */
    public static void inviteToJitsiVideobridgeConfCall(String[] callees, Call call)
    {
        new InviteToConferenceBridgeThread( call.getProtocolProvider(),
                                            callees,
                                            call).start();
    }

    /**
     * Puts on or off hold the given <tt>callPeer</tt>.
     * @param callPeer the peer to put on/off hold
     * @param isOnHold indicates the action (on hold or off hold)
     */
    public static void putOnHold(CallPeer callPeer, boolean isOnHold)
    {
        new PutOnHoldCallPeerThread(callPeer, isOnHold).start();
    }

    /**
     * Transfers the given <tt>peer</tt> to the given <tt>target</tt>.
     * @param peer the <tt>CallPeer</tt> to transfer
     * @param target the <tt>CallPeer</tt> target to transfer to
     */
    public static void transferCall(CallPeer peer, CallPeer target)
    {
        OperationSetAdvancedTelephony<?> telephony
            = peer.getCall().getProtocolProvider()
                .getOperationSet(OperationSetAdvancedTelephony.class);

        if (telephony != null)
        {
            try
            {
                telephony.transfer(peer, target);
            }
            catch (OperationFailedException ex)
            {
                logger.error("Failed to transfer " + peer.getAddress()
                    + " to " + target, ex);
            }
        }
    }

    /**
     * Transfers the given <tt>peer</tt> to the given <tt>target</tt>.
     * @param peer the <tt>CallPeer</tt> to transfer
     * @param target the target of the transfer
     */
    public static void transferCall(CallPeer peer, String target)
    {
        OperationSetAdvancedTelephony<?> telephony
            = peer.getCall().getProtocolProvider()
                .getOperationSet(OperationSetAdvancedTelephony.class);

        if (telephony != null)
        {
            try
            {
                telephony.transfer(peer, target);
            }
            catch (OperationFailedException ex)
            {
                logger.error("Failed to transfer " + peer.getAddress()
                    + " to " + target, ex);
            }
        }
    }

    /**
     * Closes the <tt>CallPanel</tt> of a specific <tt>Call</tt> if it is no
     * longer necessary (i.e. is not used by other <tt>Call</tt>s participating
     * in the same telephony conference as the specified <tt>Call</tt>.)
     *
     * @param callConference The <tt>CallConference</tt> which is to have its
     * associated <tt>CallPanel</tt>, if any
     */
    private static void closeCallContainerIfNotNecessary(
            final CallConference callConference)
    {
        CallPanel callPanel = callPanels.get(callConference);

        if (callPanel != null)
            closeCallContainerIfNotNecessary(
                callConference, callPanel.isCloseWaitAfterHangup());
    }

    /**
     * Closes the <tt>CallPanel</tt> of a specific <tt>Call</tt> if it is no
     * longer necessary (i.e. is not used by other <tt>Call</tt>s participating
     * in the same telephony conference as the specified <tt>Call</tt>.)
     *
     * @param callConference The <tt>CallConference</tt> which is to have its
     * associated <tt>CallPanel</tt>, if any, closed
     * @param wait <tt>true</tt> to set <tt>delay</tt> param of
     * {@link CallContainer#close(CallPanel, boolean)} (CallPanel)}
     */
    private static void closeCallContainerIfNotNecessary(
            final CallConference callConference,
            final boolean wait)
    {
        if (!SwingUtilities.isEventDispatchThread())
        {
            SwingUtilities.invokeLater(
                    new Runnable()
                    {
                        public void run()
                        {
                            closeCallContainerIfNotNecessary(
                                callConference,
                                wait);
                        }
                    });
            return;
        }

        /*
         * XXX The integrity of the execution of the method may be compromised
         * if it is not invoked on the AWT event dispatching thread because
         * findCallPanel and callPanels.remove must be atomically executed. The
         * uninterrupted execution (with respect to the synchronization) is
         * guaranteed by requiring all modifications to callPanels to be made on
         * the AWT event dispatching thread.
         */

        for (Iterator<Map.Entry<CallConference, CallPanel>> entryIter
                    = callPanels.entrySet().iterator();
                entryIter.hasNext();)
        {
            Map.Entry<CallConference, CallPanel> entry = entryIter.next();
            CallConference aConference = entry.getKey();
            boolean notNecessary = aConference.isEnded();

            if (notNecessary)
            {
                CallPanel aCallPanel = entry.getValue();
                CallContainer window = aCallPanel.getCallWindow();

                try
                {
                    window.close(
                            aCallPanel,
                            wait && (aConference == callConference));
                }
                finally
                {
                    /*
                     * We allow non-modifications i.e. reads of callPanels on
                     * threads other than the AWT event dispatching thread so we
                     * have to make sure that we will not cause
                     * ConcurrentModificationException.
                     */
                    synchronized (callPanels)
                    {
                        entryIter.remove();
                    }

                    aCallPanel.dispose();
                }
            }
        }
    }

    /**
     * Opens a <tt>CallPanel</tt> for a specific <tt>Call</tt> if there is none.
     * <p>
     * <b>Note</b>: The method can be called only on the AWT event dispatching
     * thread.
     * </p>
     *
     * @param call the <tt>Call</tt> to open a <tt>CallPanel</tt> for
     * @return the <tt>CallPanel</tt> associated with the <tt>Call</tt>
     * @throws RuntimeException if the method is not called on the AWT event
     * dispatching thread
     */
    private static CallPanel openCallContainerIfNecessary(Call call)
    {
        /*
         * XXX The integrity of the execution of the method may be compromised
         * if it is not invoked on the AWT event dispatching thread because
         * findCallPanel and callPanels.put must be atomically executed. The
         * uninterrupted execution (with respect to the synchronization) is
         * guaranteed by requiring all modifications to callPanels to be made on
         * the AWT event dispatching thread.
         */
        assertIsEventDispatchingThread();

        /*
         * CallPanel displays a CallConference (which may contain multiple
         * Calls.)
         */
        CallConference conference = call.getConference();
        CallPanel callPanel = findCallPanel(conference);

        if (callPanel == null)
        {
            // If we're in single-window mode, the single window is the
            // CallContainer.
            CallContainer callContainer
                = GuiActivator.getUIService().getSingleWindowContainer();

            // If we're in multi-window mode, we create the CallDialog.
            if (callContainer == null)
                callContainer = new CallDialog();

            callPanel = new CallPanel(conference, callContainer);
            callContainer.addCallPanel(callPanel);

            synchronized (callPanels)
            {
                callPanels.put(conference, callPanel);
            }
        }

        return callPanel;
    }

    /**
     * Returns a list of all currently registered telephony providers.
     * @return a list of all currently registered telephony providers
     */
    public static List<ProtocolProviderService> getTelephonyProviders()
    {
        return AccountUtils
            .getRegisteredProviders(OperationSetBasicTelephony.class);
    }

    /**
     * Returns a list of all currently registered telephony providers supporting
     * conferencing.
     *
     * @return a list of all currently registered telephony providers supporting
     * conferencing
     */
    public static List<ProtocolProviderService>
                                            getTelephonyConferencingProviders()
    {
        return AccountUtils
            .getRegisteredProviders(OperationSetTelephonyConferencing.class);
    }

    /**
     * Returns a list of all currently active calls.
     *
     * @return a list of all currently active calls
     */
    private static List<Call> getActiveCalls()
    {
        CallConference[] conferences;

        synchronized (callPanels)
        {
            Set<CallConference> keySet = callPanels.keySet();

            conferences = keySet.toArray(new CallConference[keySet.size()]);
        }

        List<Call> calls = new ArrayList<Call>();

        for (CallConference conference : conferences)
        {
            for (Call call : conference.getCalls())
            {
                if (call.getCallState() == CallState.CALL_IN_PROGRESS)
                    calls.add(call);
            }
        }
        return calls;
    }

    /**
     * Returns a collection of all currently in progress calls. A call is active
     * if it is in progress so the method merely delegates to
     *
     * @return a collection of all currently in progress calls.
     */
    public static Collection<Call> getInProgressCalls()
    {
        return getActiveCalls();
    }

    /**
     * Returns the <tt>CallContainer</tt> corresponding to the given
     * <tt>call</tt>. If the call has been finished and no active
     * <tt>CallContainer</tt> could be found it returns null.
     *
     * @param call the <tt>Call</tt>, which dialog we're looking for
     * @return the <tt>CallContainer</tt> corresponding to the given
     * <tt>call</tt>
     */
    public static CallPanel getActiveCallContainer(Call call)
    {
        return findCallPanel(call.getConference());
    }

    /**
     * A informative text to show for the peer. If display name is missing
     * return the address.
     * @param peer the peer.
     * @param listener the listener to fire change events for later resolutions
     * of display name and image, if exist.
     * @return the text contain display name.
     */
    public static String getPeerDisplayName(CallPeer peer,
                                            DetailsResolveListener listener)
    {
        String displayName = null;

        // We try to find the <tt>UIContact</tt>, to which the call was
        // created if this was an outgoing call.
        UIContactImpl uiContact
            = CallManager.getCallUIContact(peer.getCall());

        if(uiContact != null)
        {
            if(uiContact.getDescriptor() instanceof SourceContact)
            {
                // if it is source contact (history record)
                // search for cusax contact match
                Contact contact = getPeerCusaxContact(peer,
                    (SourceContact)uiContact.getDescriptor());
                if(contact != null)
                    displayName = contact.getDisplayName();
            }

            if(StringUtils.isNullOrEmpty(displayName, true))
                displayName = uiContact.getDisplayName();
        }

        // We search for a contact corresponding to this call peer and
        // try to get its display name.
        if (StringUtils.isNullOrEmpty(displayName, true)
            && peer.getContact() != null)
        {
            displayName = peer.getContact().getDisplayName();
        }

        // We try to find the an alternative peer address.
        if (StringUtils.isNullOrEmpty(displayName, true))
        {
            String imppAddress = peer.getAlternativeIMPPAddress();

            if (!StringUtils.isNullOrEmpty(imppAddress))
            {
                int protocolPartIndex = imppAddress.indexOf(":");

                imppAddress = (protocolPartIndex >= 0)
                        ? imppAddress.substring(protocolPartIndex + 1)
                        : imppAddress;

                        Collection<ProtocolProviderService> cusaxProviders
                        = AccountUtils.getRegisteredProviders(
                            OperationSetCusaxUtils.class);

                if (cusaxProviders != null && cusaxProviders.size() > 0)
                {
                    Iterator<ProtocolProviderService> iter
                        = cusaxProviders.iterator();
                    while(iter.hasNext())
                    {
                        Contact contact = getPeerContact(
                            peer,
                            iter.next(),
                            imppAddress);

                        displayName = (contact != null)
                            ? contact.getDisplayName() : null;

                        if(!StringUtils.isNullOrEmpty(displayName, true))
                            break;
                    }
                }
                else
                {
                    MetaContact metaContact
                        = getPeerMetaContact(peer, imppAddress);

                    displayName = (metaContact != null)
                                    ? metaContact.getDisplayName() : null;
                }
            }
        }

        if (StringUtils.isNullOrEmpty(displayName, true))
        {
            displayName = (!StringUtils.isNullOrEmpty
                            (peer.getDisplayName(), true))
                            ? peer.getDisplayName()
                            : peer.getAddress();

            // Try to resolve the display name
            String resolvedName = queryContactSource(displayName, listener);
            if(resolvedName != null)
            {
                displayName = resolvedName;
            }
        }

        return displayName;
    }

    /**
     * Returns the image corresponding to the given <tt>peer</tt>.
     *
     * @param peer the call peer, for which we're returning an image
     * @return the peer image
     */
    public static byte[] getPeerImage(CallPeer peer)
    {
        byte[] image = null;
        // We search for a contact corresponding to this call peer and
        // try to get its image.
        if (peer.getContact() != null)
        {
            image = getContactImage(peer.getContact());
        }

        // We try to find the <tt>UIContact</tt>, to which the call was
        // created if this was an outgoing call.
        if (image == null || image.length == 0)
        {
            UIContactImpl uiContact
                = CallManager.getCallUIContact(peer.getCall());

            if (uiContact != null)
            {
                if(uiContact.getDescriptor() instanceof SourceContact
                    && ((SourceContact)uiContact.getDescriptor())
                        .isDefaultImage())
                {
                    // if it is source contact (history record)
                    // search for cusax contact match
                    Contact contact = getPeerCusaxContact(peer,
                        (SourceContact)uiContact.getDescriptor());

                    if(contact != null)
                        image = contact.getImage();
                }
                else
                    image = uiContact.getAvatar();
            }
        }

        // We try to find the an alternative peer address.
        if (image == null || image.length == 0)
        {
            String imppAddress = peer.getAlternativeIMPPAddress();

            if (!StringUtils.isNullOrEmpty(imppAddress))
            {
                int protocolPartIndex = imppAddress.indexOf(":");

                imppAddress = (protocolPartIndex >= 0)
                        ? imppAddress.substring(protocolPartIndex + 1)
                        : imppAddress;

                Collection<ProtocolProviderService> cusaxProviders
                    = AccountUtils.getRegisteredProviders(
                        OperationSetCusaxUtils.class);

                if (cusaxProviders != null && cusaxProviders.size() > 0)
                {
                    Iterator<ProtocolProviderService> iter
                        = cusaxProviders.iterator();
                    while(iter.hasNext())
                    {
                        Contact contact = getPeerContact(
                            peer,
                            iter.next(),
                            imppAddress);

                        image = (contact != null) ?
                            getContactImage(contact) : null;

                        if(image != null)
                            break;
                    }
                }
                else
                {
                    MetaContact metaContact
                        = getPeerMetaContact(peer, imppAddress);

                    image = (metaContact != null)
                                ? metaContact.getAvatar() : null;
                }
            }
        }

        // If the icon is still null we try to get an image from the call
        // peer.
        if ((image == null || image.length == 0)
                && peer.getImage() != null)
            image = peer.getImage();

        return image;
    }

    /**
     * Searches the cusax enabled providers for a contact with
     * the detail (address) of the call peer if found and the contact
     * is provided by a provider which is IM capable, return the contact.
     * @param peer the peer we are calling.
     * @return the im capable contact corresponding the <tt>peer</tt>.
     */
    public static Contact getIMCapableCusaxContact(CallPeer peer)
    {
        // We try to find the <tt>UIContact</tt>, to which the call was
        // created if this was an outgoing call.
        UIContactImpl uiContact
            = CallManager.getCallUIContact(peer.getCall());

        if (uiContact != null)
        {
            if(uiContact.getDescriptor() instanceof MetaContact)
            {
                MetaContact metaContact =
                    (MetaContact)uiContact.getDescriptor();
                Iterator<Contact> iter = metaContact.getContacts();
                while(iter.hasNext())
                {
                    Contact contact = iter.next();
                    if(contact.getProtocolProvider()
                        .getOperationSet(
                            OperationSetBasicInstantMessaging.class) != null)
                        return contact;
                }
            }
            else if(uiContact.getDescriptor() instanceof SourceContact)
            {
                // if it is source contact (history record)
                // search for cusax contact match
                Contact contact = getPeerCusaxContact(peer,
                            (SourceContact)uiContact.getDescriptor());
                if(contact != null
                    && contact.getProtocolProvider().getOperationSet(
                            OperationSetBasicInstantMessaging.class) != null)
                    return contact;
            }
        }

        // We try to find the an alternative peer address.
        String imppAddress = peer.getAlternativeIMPPAddress();

        if (!StringUtils.isNullOrEmpty(imppAddress))
        {
            int protocolPartIndex = imppAddress.indexOf(":");

            imppAddress = (protocolPartIndex >= 0)
                    ? imppAddress.substring(protocolPartIndex + 1)
                    : imppAddress;

            Collection<ProtocolProviderService> cusaxProviders
                = AccountUtils.getRegisteredProviders(
                    OperationSetCusaxUtils.class);

            if (cusaxProviders != null && cusaxProviders.size() > 0)
            {
                Iterator<ProtocolProviderService> iter
                    = cusaxProviders.iterator();
                while(iter.hasNext())
                {
                    ProtocolProviderService cusaxProvider = iter.next();

                    Contact contact = getPeerContact(
                        peer,
                        cusaxProvider,
                        imppAddress);

                    if(contact != null
                        && cusaxProvider.getOperationSet(
                        OperationSetBasicInstantMessaging.class) != null)
                    {
                        return contact;
                    }
                }
            }
        }

        return null;
    }

    /**
     * Find is there a linked cusax protocol provider for this source contact,
     * if it exist we try to resolve current peer to one of its contacts
     * or details of a contact (numbers).
     * @param peer the peer to check
     * @param sourceContact the currently selected source contact.
     * @return matching cusax contact.
     */
    private static Contact getPeerCusaxContact(
        CallPeer peer, SourceContact sourceContact)
    {
        ProtocolProviderService linkedCusaxProvider = null;
        for(ContactDetail detail : sourceContact.getContactDetails())
        {
            ProtocolProviderService pps
                = detail.getPreferredProtocolProvider(
                OperationSetBasicTelephony.class);

            if(pps != null)
            {
                OperationSetCusaxUtils cusaxOpSet =
                    pps.getOperationSet(OperationSetCusaxUtils.class);

                if(cusaxOpSet != null)
                {
                    linkedCusaxProvider
                        = cusaxOpSet.getLinkedCusaxProvider();
                    break;
                }
            }
        }

        // if we do not have preferred protocol, lets check the one
        // used to dial the peer
        if(linkedCusaxProvider == null)
        {
            ProtocolProviderService pps = peer.getProtocolProvider();

            OperationSetCusaxUtils cusaxOpSet =
                pps.getOperationSet(OperationSetCusaxUtils.class);

            if(cusaxOpSet != null)
            {
                linkedCusaxProvider
                    = cusaxOpSet.getLinkedCusaxProvider();
            }
        }

        if(linkedCusaxProvider != null)
        {
            OperationSetPersistentPresence opSetPersistentPresence
                = linkedCusaxProvider.getOperationSet(
                        OperationSetPersistentPresence.class);

            if(opSetPersistentPresence != null)
            {
                String peerAddress = peer.getAddress();

                // will strip the @server-address part, as the regular expression
                // will match it
                int index = peerAddress.indexOf("@");
                String peerUserID =
                    (index > -1) ? peerAddress.substring(0, index) : peerAddress;

                // searches for the whole number/username or with the @serverpart
                String peerUserIDQ = Pattern.quote(peerUserID);

                Pattern pattern = Pattern.compile(
                    "^(" + peerUserIDQ + "|" + peerUserIDQ + "@.*)$");

                return findContactByPeer(
                    peerUserID,
                    pattern,
                    opSetPersistentPresence.getServerStoredContactListRoot(),
                    linkedCusaxProvider.getOperationSet(
                        OperationSetCusaxUtils.class));
            }
        }

        return null;
    }

    /**
     * Finds a matching cusax contact.
     * @param peerUserID the userID of the call peer to search for
     * @param searchPattern the pattern (userID | userID@...)
     * @param parent the parent group of the groups and contacts to search in
     * @param cusaxOpSet the opset of the provider which will be used to match
     *                   contact's details to peer userID (stored numbers).
     * @return a cusax matching contac
     */
    private static Contact findContactByPeer(
        String peerUserID,
        Pattern searchPattern,
        ContactGroup parent,
        OperationSetCusaxUtils cusaxOpSet)
    {
        Iterator<Contact> contactIterator = parent.contacts();
        while(contactIterator.hasNext())
        {
            Contact contact = contactIterator.next();

            if(searchPattern.matcher(contact.getAddress()).find()
                || cusaxOpSet.doesDetailBelong(contact, peerUserID))
            {
                return contact;
            }
        }

        Iterator<ContactGroup> groupsIterator = parent.subgroups();
        while(groupsIterator.hasNext())
        {
            ContactGroup gr = groupsIterator.next();
            Contact contact = findContactByPeer(
                peerUserID, searchPattern, gr, cusaxOpSet);
            if(contact != null)
                return contact;
        }

        return null;
    }

    /**
     * Returns the image for the given contact.
     *
     * @param contact the <tt>Contact</tt>, which image we're looking for
     * @return the array of bytes representing the image for the given contact
     * or null if such image doesn't exist
     */
    private static byte[] getContactImage(Contact contact)
    {
        MetaContact metaContact = GuiActivator.getContactListService()
            .findMetaContactByContact(contact);

        if (metaContact != null)
            return metaContact.getAvatar();

        return null;
    }

    /**
     * Returns the peer contact for the given <tt>alternativePeerAddress</tt> by
     * checking the if the <tt>callPeer</tt> exists as a detail in the given
     * <tt>cusaxProvider</tt>.
     *
     * @param callPeer the <tt>CallPeer</tt> to check in the cusax provider
     * details
     * @param cusaxProvider the linked cusax <tt>ProtocolProviderService</tt>
     * @param alternativePeerAddress the alternative peer address to obtain the
     * image from
     * @return the protocol <tt>Contact</tt> corresponding to the given
     * <tt>alternativePeerAddress</tt>
     */
    private static Contact getPeerContact( CallPeer callPeer,
                                        ProtocolProviderService cusaxProvider,
                                        String alternativePeerAddress)
    {
        OperationSetPresence presenceOpSet
            = cusaxProvider.getOperationSet(OperationSetPresence.class);

        if (presenceOpSet == null)
            return null;

        Contact contact = presenceOpSet.findContactByID(alternativePeerAddress);

        if (contact == null)
            return null;

        OperationSetCusaxUtils cusaxOpSet
            = cusaxProvider.getOperationSet(OperationSetCusaxUtils.class);

        if (cusaxOpSet != null && cusaxOpSet.doesDetailBelong(
                contact, callPeer.getAddress()))
            return contact;

        return null;
    }

    /**
     * Returns the metacontact for the given <tt>CallPeer</tt> by
     * checking the if the <tt>callPeer</tt> contact exists, if not checks the
     * contacts in our contact list that are provided by cusax enabled
     * providers.
     *
     * @param peer the <tt>CallPeer</tt> to check in contact details
     * @return the <tt>MetaContact</tt> corresponding to the given
     * <tt>peer</tt>.
     */
    public static MetaContact getPeerMetaContact(CallPeer peer)
    {
        if(peer == null)
            return null;

        if(peer.getContact() != null)
            return GuiActivator.getContactListService()
                .findMetaContactByContact(peer.getContact());

        // We try to find the <tt>UIContact</tt>, to which the call was
        // created if this was an outgoing call.
        UIContactImpl uiContact
            = CallManager.getCallUIContact(peer.getCall());

        if (uiContact != null)
        {
            if(uiContact.getDescriptor() instanceof MetaContact)
            {
                return (MetaContact)uiContact.getDescriptor();
            }
            else if(uiContact.getDescriptor() instanceof SourceContact)
            {
                // if it is a source contact check for matching cusax contact
                Contact contact = getPeerCusaxContact(peer,
                    (SourceContact)uiContact.getDescriptor());
                if(contact != null)
                    return GuiActivator.getContactListService()
                                .findMetaContactByContact(contact);
            }
        }

        String imppAddress = peer.getAlternativeIMPPAddress();

        if (!StringUtils.isNullOrEmpty(imppAddress))
        {
            int protocolPartIndex = imppAddress.indexOf(":");

            imppAddress = (protocolPartIndex >= 0)
                    ? imppAddress.substring(protocolPartIndex + 1)
                    : imppAddress;

            Collection<ProtocolProviderService> cusaxProviders
                = AccountUtils.getRegisteredProviders(
                    OperationSetCusaxUtils.class);

            if (cusaxProviders != null && cusaxProviders.size() > 0)
            {
                Iterator<ProtocolProviderService> iter
                    = cusaxProviders.iterator();
                while(iter.hasNext())
                {
                    Contact contact = getPeerContact(
                        peer,
                        iter.next(),
                        imppAddress);

                    MetaContact res = GuiActivator.getContactListService()
                        .findMetaContactByContact(contact);

                    if(res != null)
                        return res;
                }
            }
            else
            {
                return getPeerMetaContact(peer, imppAddress);
            }
        }

        return null;
    }

    /**
     * Returns the image for the given <tt>alternativePeerAddress</tt> by
     * checking the if the <tt>callPeer</tt> exists as a detail in one of the
     * contacts in our contact list.
     *
     * @param callPeer the <tt>CallPeer</tt> to check in contact details
     * @param alternativePeerAddress the alternative peer address to obtain the
     * image from
     * @return the <tt>MetaContact</tt> corresponding to the given
     * <tt>alternativePeerAddress</tt>
     */
    private static MetaContact getPeerMetaContact(
                                            CallPeer callPeer,
                                            String alternativePeerAddress)
    {
        Iterator<MetaContact> metaContacts
            = GuiActivator.getContactListService()
                .findAllMetaContactsForAddress(alternativePeerAddress);

        while (metaContacts.hasNext())
        {
            MetaContact metaContact = metaContacts.next();

            UIPhoneUtil phoneUtil
                = UIPhoneUtil.getPhoneUtil(metaContact);

            List<UIContactDetail> additionalNumbers
                = phoneUtil.getAdditionalNumbers();

            if (additionalNumbers == null || additionalNumbers.size() > 0)
                continue;

            Iterator<UIContactDetail> numbersIter
                = additionalNumbers.iterator();
            while (numbersIter.hasNext())
            {
                if (numbersIter.next().getAddress()
                    .equals(callPeer.getAddress()))
                    return metaContact;
            }
        }

        return null;
    }

    /**
     * Opens a call transfer dialog to transfer the given <tt>peer</tt>.
     * @param peer the <tt>CallPeer</tt> to transfer
     */
    public static void openCallTransferDialog(CallPeer peer)
    {
        final TransferCallDialog dialog
            = new TransferCallDialog(peer);

        final Call call = peer.getCall();

        /*
         * Transferring a call works only when the call is in progress
         * so close the dialog (if it's not already closed, of course)
         * once the dialog ends.
         */
        CallChangeListener callChangeListener = new CallChangeAdapter()
        {
            /*
             * Overrides
             * CallChangeAdapter#callStateChanged(CallChangeEvent).
             */
            @Override
            public void callStateChanged(CallChangeEvent evt)
            {
                // we are interested only in CALL_STATE_CHANGEs
                if(!evt.getEventType().equals(
                        CallChangeEvent.CALL_STATE_CHANGE))
                    return;

                if (!CallState.CALL_IN_PROGRESS.equals(call
                    .getCallState()))
                {
                    dialog.setVisible(false);
                    dialog.dispose();
                }
            }
        };
        call.addCallChangeListener(callChangeListener);
        try
        {
            dialog.pack();
            dialog.setVisible(true);
        }
        finally
        {
            call.removeCallChangeListener(callChangeListener);
        }
    }

    /**
     * Checks whether the <tt>callPeer</tt> supports setting video
     * quality presets. If quality controls is null, its not supported.
     * @param callPeer the peer, which video quality we're checking
     * @return whether call peer supports setting quality preset.
     */
    public static boolean isVideoQualityPresetSupported(CallPeer callPeer)
    {
        ProtocolProviderService provider = callPeer.getProtocolProvider();
        OperationSetVideoTelephony videoOpSet
            = provider.getOperationSet(OperationSetVideoTelephony.class);

        if (videoOpSet == null)
            return false;

        return videoOpSet.getQualityControl(callPeer) != null;
    }

    /**
     * Sets the given quality preset for the video of the given call peer.
     *
     * @param callPeer the peer, which video quality we're setting
     * @param qualityPreset the new quality settings
     */
    public static void setVideoQualityPreset(final CallPeer callPeer,
                                            final QualityPreset qualityPreset)
    {
        ProtocolProviderService provider = callPeer.getProtocolProvider();
        final OperationSetVideoTelephony videoOpSet
            = provider.getOperationSet(OperationSetVideoTelephony.class);

        if (videoOpSet == null)
            return;

        final QualityControl qualityControl =
                    videoOpSet.getQualityControl(callPeer);

        if (qualityControl != null)
        {
            new Thread(new Runnable()
            {
                public void run()
                {
                    try
                    {
                        qualityControl.setPreferredRemoteSendMaxPreset(
                                qualityPreset);
                    }
                    catch(Exception e)
                    {
                        logger.info("Unable to change video quality.", e);

                        ResourceManagementService resources
                            = GuiActivator.getResources();

                        new ErrorDialog(
                                null,
                                resources.getI18NString("service.gui.WARNING"),
                                resources.getI18NString(
                                        "service.gui.UNABLE_TO_CHANGE_VIDEO_QUALITY"),
                                e)
                            .showDialog();
                    }
                }
            }).start();
        }
    }

    /**
     * Indicates if we have video streams to show in this interface.
     *
     * @param call the call to check for video streaming
     * @return <tt>true</tt> if we have video streams to show in this interface;
     * otherwise, <tt>false</tt>
     */
    public static boolean isVideoStreaming(Call call)
    {
        return isVideoStreaming(call.getConference());
    }

    /**
     * Indicates if we have video streams to show in this interface.
     *
     * @param conference the conference we check for video streaming
     * @return <tt>true</tt> if we have video streams to show in this interface;
     * otherwise, <tt>false</tt>
     */
    public static boolean isVideoStreaming(CallConference conference)
    {
        for (Call call : conference.getCalls())
        {
            OperationSetVideoTelephony videoTelephony
                = call.getProtocolProvider().getOperationSet(
                        OperationSetVideoTelephony.class);

            if (videoTelephony == null)
                continue;

            if (videoTelephony.isLocalVideoStreaming(call))
                return true;

            Iterator<? extends CallPeer> callPeers = call.getCallPeers();

            while (callPeers.hasNext())
            {
                List<Component> remoteVideos
                    = videoTelephony.getVisualComponents(callPeers.next());

                if ((remoteVideos != null) && (remoteVideos.size() > 0))
                    return true;
            }
        }
        return false;
    }

    /**
     * Determines whether two specific addresses refer to one and the same
     * peer/resource/contact.
     * <p>
     * <b>Warning</b>: Use the functionality sparingly because it assumes that
     * an unspecified service is equal to any service.
     * </p>
     *
     * @param a one of the addresses to be compared
     * @param b the other address to be compared to <tt>a</tt>
     * @return <tt>true</tt> if <tt>a</tt> and <tt>b</tt> name one and the same
     * peer/resource/contact; <tt>false</tt>, otherwise
     */
    public static boolean addressesAreEqual(String a, String b)
    {
        if (a.equals(b))
            return true;

        int aProtocolIndex = a.indexOf(':');
        if(aProtocolIndex != -1)
            a = a.substring(aProtocolIndex + 1);

        int bProtocolIndex = b.indexOf(':');
        if(bProtocolIndex != -1)
            b = b.substring(bProtocolIndex + 1);

        if (a.equals(b))
            return true;

        int aServiceBegin = a.indexOf('@', aProtocolIndex);
        String aUserID;
        String aService;

        if (aServiceBegin != -1)
        {
            aUserID = a.substring(0, aServiceBegin);
            ++aServiceBegin;

            int aResourceBegin = a.indexOf('/', aServiceBegin);
            if (aResourceBegin != -1)
                aService = a.substring(aServiceBegin, aResourceBegin);
            else
                aService = a.substring(aServiceBegin);
        }
        else
        {
            aUserID = a;
            aService = null;
        }

        int bServiceBegin = b.indexOf('@', bProtocolIndex);
        String bUserID;
        String bService;

        if (bServiceBegin != -1)
        {
            bUserID = b.substring(0, bServiceBegin);
            ++bServiceBegin;

            int bResourceBegin = b.indexOf('/', bServiceBegin);
            if (bResourceBegin != -1)
                bService = b.substring(bServiceBegin, bResourceBegin);
            else
                bService = b.substring(bServiceBegin);
        }
        else
        {
            bUserID = b;
            bService = null;
        }

        boolean userIDsAreEqual;

        if ((aUserID == null) || (aUserID.length() < 1))
            userIDsAreEqual = ((bUserID == null) || (bUserID.length() < 1));
        else
            userIDsAreEqual = aUserID.equals(bUserID);
        if (!userIDsAreEqual)
            return false;

        boolean servicesAreEqual;

        /*
         * It's probably a veeery long shot but it's assumed here that an
         * unspecified service is equal to any service. Such a case is, for
         * example, RegistrarLess SIP.
         */
        if (((aService == null) || (aService.length() < 1))
                || ((bService == null) || (bService.length() < 1)))
            servicesAreEqual = true;
        else
            servicesAreEqual = aService.equals(bService);

        return servicesAreEqual;
    }

    /**
     * Indicates if the given <tt>ConferenceMember</tt> corresponds to the local
     * user.
     *
     * @param conferenceMember the conference member to check
     * @return <tt>true</tt> if the given <tt>conferenceMember</tt> is the local
     * user, <tt>false</tt> - otherwise
     */
    public static boolean isLocalUser(ConferenceMember conferenceMember)
    {
        String localUserAddress
            = conferenceMember.getConferenceFocusCallPeer()
                .getProtocolProvider().getAccountID().getAccountAddress();

        return CallManager.addressesAreEqual(
            conferenceMember.getAddress(), localUserAddress);
    }

    /**
     * Adds a missed call notification.
     *
     * @param peerName the name of the peer
     * @param callTime the time of the call
     */
    private static void addMissedCallNotification(String peerName, long callTime)
    {
        if (missedCallGroup == null)
        {
            missedCallGroup
                = new UINotificationGroup(
                        "MissedCalls",
                        GuiActivator.getResources().getI18NString(
                                "service.gui.MISSED_CALLS_TOOL_TIP"));
        }

        UINotificationManager.addNotification(
                new UINotification(peerName, callTime, missedCallGroup));
    }

    /**
     * Returns of supported/enabled list of audio formats for a provider.
     * @param device the <tt>MediaDevice</tt>, which audio formats we're
     * looking for
     * @param protocolProvider the provider to check.
     * @return list of supported/enabled auido formats or empty list
     * otherwise.
     */
    private static List<MediaFormat> getAudioFormats(
        MediaDevice device,
        ProtocolProviderService protocolProvider)
    {
        List<MediaFormat> res = new ArrayList<MediaFormat>();

        Map<String, String> accountProperties
           = protocolProvider.getAccountID().getAccountProperties();
        String overrideEncodings
           = accountProperties.get(ProtocolProviderFactory.OVERRIDE_ENCODINGS);

        List<MediaFormat> formats;
        if(Boolean.parseBoolean(overrideEncodings))
        {
           /*
            * The account properties associated with account
             * override the global EncodingConfiguration.
            */
           EncodingConfiguration encodingConfiguration
               = ProtocolMediaActivator.getMediaService()
                       .createEmptyEncodingConfiguration();

           encodingConfiguration.loadProperties(
                   accountProperties,
                   ProtocolProviderFactory.ENCODING_PROP_PREFIX);

            formats = device.getSupportedFormats(
                       null, null, encodingConfiguration);
        }
        else /* The global EncodingConfiguration is in effect. */
        {
            formats = device.getSupportedFormats();
        }

        // skip the special telephony event
        for(MediaFormat format : formats)
        {
            if(!format.getEncoding().equals(Constants.TELEPHONE_EVENT))
                res.add(format);
        }

        return res;
    }

    /**
     * Creates a new (audio-only or video) <tt>Call</tt> to a contact specified
     * as a <tt>Contact</tt> instance or a <tt>String</tt> contact
     * address/identifier.
     */
    private static class CreateCallThread
        extends Thread
    {
        /**
         * The contact to call.
         */
        private final Contact contact;

        /**
         * The specific contact resource to call.
         */
        private final ContactResource contactResource;

        /**
         * The <tt>UIContactImpl</tt> we're calling.
         */
        private final UIContactImpl uiContact;

        /**
         * The protocol provider through which the call goes.
         */
        private final ProtocolProviderService protocolProvider;

        /**
         * The string to call.
         */
        private final String stringContact;

        /**
         * The description of a conference to call, if any.
         */
        private final ConferenceDescription conferenceDescription;

        /**
         * The indicator which determines whether this instance is to create a
         * new video (as opposed to audio-only) <tt>Call</tt>.
         */
        private final boolean video;

        /**
         * The chat room associated with the call.
         */
        private final ChatRoom chatRoom;
        
        /**
         * Creates an instance of <tt>CreateCallThread</tt>.
         *
         * @param protocolProvider the protocol provider through which the call
         * is going.
         * @param contact the contact to call
         * @param contactResource the specific <tt>ContactResource</tt> we're
         * calling
         * @param video indicates if this is a video call
         */
        public CreateCallThread(
                ProtocolProviderService protocolProvider,
                Contact contact,
                ContactResource contactResource,
                boolean video)
        {
            this(protocolProvider, contact, contactResource, null, null, null,
                null, video);
        }

        /**
         * Creates an instance of <tt>CreateCallThread</tt>.
         *
         * @param protocolProvider the protocol provider through which the call
         * is going.
         * @param contact the contact to call
         * @param video indicates if this is a video call
         */
        public CreateCallThread(
                ProtocolProviderService protocolProvider,
                String contact,
                boolean video)
        {
            this(protocolProvider, null, null, null, contact, null, null, video);
        }

        /**
         * Initializes a new <tt>CreateCallThread</tt> instance which is to
         * create a new <tt>Call</tt> to a conference specified via a
         * <tt>ConferenceDescription</tt>.
         * @param protocolProvider the <tt>ProtocolProviderService</tt> which is
         * to perform the establishment of the new <tt>Call</tt>.
         * @param conferenceDescription the description of the conference to
         * call.
         * @param chatRoom the chat room associated with the call.
         */
        public CreateCallThread(
                ProtocolProviderService protocolProvider,
                ConferenceDescription conferenceDescription,
                ChatRoom chatRoom)
        {
            this(protocolProvider, null, null, null, null,
                    conferenceDescription, chatRoom,
                    false /* video */);
        }

        /**
         * Initializes a new <tt>CreateCallThread</tt> instance which is to
         * create a new <tt>Call</tt> to a contact specified either as a
         * <tt>Contact</tt> instance or as a <tt>String</tt> contact
         * address/identifier.
         * <p>
         * The constructor is private because it relies on its arguments being
         * validated prior to its invocation.
         * </p>
         *
         * @param protocolProvider the <tt>ProtocolProviderService</tt> which is
         * to perform the establishment of the new <tt>Call</tt>
         * @param contact the contact to call
         * @param contactResource the specific contact resource to call
         * @param uiContact the ui contact we're calling
         * @param stringContact the string to call
         * @param video <tt>true</tt> if this instance is to create a new video
         * (as opposed to audio-only) <tt>Call</tt>
         * @param conferenceDescription the description of a conference to call
         * @param chatRoom the chat room associated with the call.
         */
        public CreateCallThread(
                ProtocolProviderService protocolProvider,
                Contact contact,
                ContactResource contactResource,
                UIContactImpl uiContact,
                String stringContact,
                ConferenceDescription conferenceDescription,
                ChatRoom chatRoom,
                boolean video)
        {
            this.protocolProvider = protocolProvider;
            this.contact = contact;
            this.contactResource = contactResource;
            this.uiContact = uiContact;
            this.stringContact = stringContact;
            this.video = video;
            this.conferenceDescription = conferenceDescription;
            this.chatRoom = chatRoom;
        }

        @Override
        public void run()
        {
            if(!video)
            {
                // if it is not video let's check for available audio codecs
                // and available audio devices
                MediaService mediaService = GuiActivator.getMediaService();
                MediaDevice dev = mediaService.getDefaultDevice(
                   MediaType.AUDIO, MediaUseCase.CALL);

                List<MediaFormat> formats
                    = getAudioFormats(dev, protocolProvider);

                String errorMsg = null;

                if(!dev.getDirection().allowsSending())
                    errorMsg = GuiActivator.getResources().getI18NString(
                        "service.gui.CALL_NO_AUDIO_DEVICE");
                else if(formats.isEmpty())
                {
                    errorMsg = GuiActivator.getResources().getI18NString(
                        "service.gui.CALL_NO_AUDIO_CODEC");
                }

                if(errorMsg != null)
                {
                    if(GuiActivator.getUIService()
                        .getPopupDialog().showConfirmPopupDialog(
                            errorMsg + " " +
                                GuiActivator.getResources().getI18NString(
                                    "service.gui.CALL_NO_DEVICE_CODECS_Q"),
                            GuiActivator.getResources().getI18NString(
                                    "service.gui.CALL"),
                                PopupDialog.YES_NO_OPTION,
                                PopupDialog.QUESTION_MESSAGE)
                        == PopupDialog.NO_OPTION)
                    {
                        return;
                    }
                }
            }

            Contact contact = this.contact;
            String stringContact = this.stringContact;

            if (ConfigurationUtils.isNormalizePhoneNumber()
                && !NetworkUtils.isValidIPAddress(stringContact))
            {
                if (contact != null)
                {
                    stringContact = contact.getAddress();
                    contact = null;
                }

                if (stringContact != null)
                {
                    stringContact = GuiActivator.getPhoneNumberI18nService()
                        .normalize(stringContact);
                }
            }

            try
            {
                if (conferenceDescription != null)
                {
                    internalCall(  protocolProvider,
                                   conferenceDescription,
                                   chatRoom);
                }
                else
                {
                    if (video)
                    {
                        internalCallVideo(  protocolProvider,
                                            contact,
                                            uiContact,
                                            stringContact);
                    }
                    else
                    {
                        internalCall(   protocolProvider,
                                        contact,
                                        stringContact,
                                        contactResource,
                                        uiContact);
                    }
                }
            }
            catch (Throwable t)
            {
                if (t instanceof ThreadDeath)
                    throw (ThreadDeath) t;

                logger.error("The call could not be created: ", t);

                String message = GuiActivator.getResources()
                    .getI18NString("servoce.gui.CREATE_CALL_FAILED");

                if (t.getMessage() != null)
                    message += " " +  t.getMessage();

                new ErrorDialog(
                        null,
                        GuiActivator.getResources().getI18NString(
                                "service.gui.ERROR"),
                        message,
                        t)
                    .showDialog();
            }
        }
    }

    /**
     * Creates a video call through the given <tt>protocolProvider</tt>.
     *
     * @param protocolProvider the <tt>ProtocolProviderService</tt> through
     * which to make the call
     * @param contact the <tt>Contact</tt> to call
     * @param uiContact the <tt>UIContactImpl</tt> we're calling
     * @param stringContact the contact string to call
     *
     * @throws OperationFailedException thrown if the call operation fails
     * @throws ParseException thrown if the contact string is malformated
     */
    private static void internalCallVideo(
                                    ProtocolProviderService protocolProvider,
                                    Contact contact,
                                    UIContactImpl uiContact,
                                    String stringContact)
        throws  OperationFailedException,
                ParseException
    {
        OperationSetVideoTelephony telephony
            = protocolProvider.getOperationSet(
                    OperationSetVideoTelephony.class);

        Call createdCall = null;
        if (telephony != null)
        {
            if (contact != null)
            {
                createdCall = telephony.createVideoCall(contact);
            }
            else if (stringContact != null)
                createdCall = telephony.createVideoCall(stringContact);
        }

        if (uiContact != null && createdCall != null)
            addUIContactCall(uiContact, createdCall);
    }

    /**
     * Creates a call through the given <tt>protocolProvider</tt>.
     *
     * @param protocolProvider the <tt>ProtocolProviderService</tt> through
     * which to make the call
     * @param contact the <tt>Contact</tt> to call
     * @param stringContact the contact string to call
     * @param contactResource the specific <tt>ContactResource</tt> to call
     * @param uiContact the <tt>UIContactImpl</tt> we're calling
     *
     * @throws OperationFailedException thrown if the call operation fails
     * @throws ParseException thrown if the contact string is malformated
     */
    private static void internalCall(
                                ProtocolProviderService protocolProvider,
                                Contact contact,
                                String stringContact,
                                ContactResource contactResource,
                                UIContactImpl uiContact)
        throws  OperationFailedException,
                ParseException
    {
        OperationSetBasicTelephony<?> telephony
            = protocolProvider.getOperationSet(
                    OperationSetBasicTelephony.class);

        OperationSetResourceAwareTelephony resourceTelephony
            = protocolProvider.getOperationSet(
                    OperationSetResourceAwareTelephony.class);

        Call createdCall = null;

        if (resourceTelephony != null && contactResource != null)
        {
            if (contact != null)
                createdCall
                    = resourceTelephony.createCall(contact, contactResource);
            else if (!StringUtils.isNullOrEmpty(stringContact))
                createdCall = resourceTelephony.createCall(
                    stringContact, contactResource.getResourceName());
        }
        else if (telephony != null)
        {
            if (contact != null)
            {
                createdCall = telephony.createCall(contact);
            }
            else if (!StringUtils.isNullOrEmpty(stringContact))
                createdCall = telephony.createCall(stringContact);
        }

        if (uiContact != null && createdCall != null)
            addUIContactCall(uiContact, createdCall);
    }

    /**
     * Creates a call through the given <tt>protocolProvider</tt>.
     *
     * @param protocolProvider the <tt>ProtocolProviderService</tt> through
     * which to make the call
     * @param conferenceDescription the description of the conference to call
     * @param chatRoom the chat room associated with the call.
     */
    private static void internalCall(ProtocolProviderService protocolProvider,
                                     ConferenceDescription conferenceDescription,
                                     ChatRoom chatRoom)
            throws OperationFailedException
    {
        OperationSetBasicTelephony<?> telephony
                = protocolProvider.getOperationSet(
                OperationSetBasicTelephony.class);

        if (telephony != null)
        {
            telephony.createCall(conferenceDescription, chatRoom);
        }
    }

    /**
     * Returns the <tt>MetaContact</tt>, to which the given <tt>Call</tt>
     * was initially created.
     *
     * @param call the <tt>Call</tt>, which corresponding <tt>MetaContact</tt>
     * we're looking for
     * @return the <tt>UIContactImpl</tt>, to which the given <tt>Call</tt>
     * was initially created
     */
    public static UIContactImpl getCallUIContact(Call call)
    {
        if (uiContactCalls != null)
            return uiContactCalls.get(call);
        return null;
    }

    /**
     * Adds a call for a <tt>metaContact</tt>.
     *
     * @param uiContact the <tt>UIContact</tt> corresponding to the call
     * @param call the <tt>Call</tt> corresponding to the <tt>MetaContact</tt>
     */
    private static void addUIContactCall( UIContactImpl uiContact,
                                          Call call)
    {
        if (uiContactCalls == null)
            uiContactCalls = new WeakHashMap<Call, UIContactImpl>();

        uiContactCalls.put(call, uiContact);
    }

    /**
     * Creates a desktop sharing session with the given Contact or a given
     * String.
     */
    private static class CreateDesktopSharingThread
        extends Thread
    {
        /**
         * The string contact to share the desktop with.
         */
        private final String stringContact;

        /**
         * The protocol provider through which we share our desktop.
         */
        private final ProtocolProviderService protocolProvider;

        /**
         * The media device corresponding to the screen we would like to share.
         */
        private final MediaDevice mediaDevice;

        /**
         * The <tt>UIContactImpl</tt> we're calling.
         */
        private final UIContactImpl uiContact;

        /**
         * Whether user has selected sharing full screen or region.
         */
        private boolean fullscreen = false;

        /**
         * Creates a desktop sharing session thread.
         *
         * @param protocolProvider protocol provider through which we share our
         * desktop
         * @param contact the contact to share the desktop with
         * @param uiContact the <tt>UIContact</tt>, which initiated the desktop
         * sharing session
         * @param mediaDevice the media device corresponding to the screen we
         * would like to share
         */
        public CreateDesktopSharingThread(
                                    ProtocolProviderService protocolProvider,
                                    String contact,
                                    UIContactImpl uiContact,
                                    MediaDevice mediaDevice,
                                    boolean fullscreen)
        {
            this.protocolProvider = protocolProvider;
            this.stringContact = contact;
            this.uiContact = uiContact;
            this.mediaDevice = mediaDevice;
            this.fullscreen = fullscreen;
        }

        @Override
        public void run()
        {
            OperationSetDesktopStreaming desktopSharingOpSet
                = protocolProvider.getOperationSet(
                    OperationSetDesktopStreaming.class);

            /*
             * XXX If we are here and we just discover that
             * OperationSetDesktopStreaming is not supported, then we're
             * already in trouble - we've already started a whole new thread
             * just to check that a reference is null.
             */
            if (desktopSharingOpSet == null)
                return;

            Throwable exception = null;

            Call createdCall = null;
            try
            {
                if (mediaDevice != null)
                {
                    createdCall = desktopSharingOpSet.createVideoCall(
                            stringContact,
                            mediaDevice);
                }
                else
                    createdCall
                        = desktopSharingOpSet.createVideoCall(stringContact);
            }
            catch (OperationFailedException e)
            {
                exception = e;
            }
            catch (ParseException e)
            {
                exception = e;
            }
            if (exception != null)
            {
                logger.error("The call could not be created: ", exception);

                new ErrorDialog(
                        null,
                        GuiActivator.getResources().getI18NString(
                                "service.gui.ERROR"),
                        exception.getMessage(),
                        ErrorDialog.ERROR)
                    .showDialog();
            }

            if (uiContact != null && createdCall != null)
                addUIContactCall(uiContact, createdCall);


            if(createdCall != null && fullscreen)
            {
                new FullScreenShareIndicator(createdCall);
            }
        }
    }

    /**
     * Answers to all <tt>CallPeer</tt>s associated with a specific
     * <tt>Call</tt> and, optionally, does that in a telephony conference with
     * an existing <tt>Call</tt>.
     */
    private static class AnswerCallThread
        extends Thread
    {
        /**
         * The <tt>Call</tt> which is to be answered.
         */
        private final Call call;

        /**
         * The existing <tt>Call</tt>, if any, which represents a telephony
         * conference in which {@link #call} is to be answered.
         */
        private final Call existingCall;

        /**
         * The indicator which determines whether this instance is to answer
         * {@link #call} with video.
         */
        private final boolean video;

        public AnswerCallThread(Call call, Call existingCall, boolean video)
        {
            this.call = call;
            this.existingCall = existingCall;
            this.video = video;
        }

        @Override
        public void run()
        {
            if (existingCall != null)
                call.setConference(existingCall.getConference());

            ProtocolProviderService pps = call.getProtocolProvider();
            Iterator<? extends CallPeer> peers = call.getCallPeers();

            while (peers.hasNext())
            {
                CallPeer peer = peers.next();

                if (video)
                {
                    OperationSetVideoTelephony telephony
                        = pps.getOperationSet(OperationSetVideoTelephony.class);

                    try
                    {
                        telephony.answerVideoCallPeer(peer);
                    }
                    catch (OperationFailedException ofe)
                    {
                        logger.error(
                                "Could not answer " + peer + " with video"
                                    + " because of the following exception: "
                                    + ofe);
                    }
                }
                else
                {
                    OperationSetBasicTelephony<?> telephony
                        = pps.getOperationSet(OperationSetBasicTelephony.class);

                    try
                    {
                        telephony.answerCallPeer(peer);
                    }
                    catch (OperationFailedException ofe)
                    {
                        logger.error(
                                "Could not answer " + peer
                                    + " because of the following exception: ",
                                ofe);
                    }
                }
            }
        }
    }

    /**
     * Invites a list of callees to a conference <tt>Call</tt>. If the specified
     * <tt>Call</tt> is <tt>null</tt>, creates a brand new telephony conference.
     */
    private static class InviteToConferenceCallThread
        extends Thread
    {
        /**
         * The addresses of the callees to be invited into the telephony
         * conference to be organized by this instance. For further details,
         * refer to the documentation on the <tt>callees</tt> parameter of the
         * respective <tt>InviteToConferenceCallThread</tt> constructor.
         */
        private final Map<ProtocolProviderService, List<String>>
            callees;

        /**
         * The <tt>Call</tt>, if any, into the telephony conference of which
         * {@link #callees} are to be invited. If non-<tt>null</tt>, its
         * <tt>CallConference</tt> state will be shared with all <tt>Call</tt>s
         * established by this instance for the purposes of having the
         * <tt>callees</tt> into the same telephony conference.
         */
        private final Call call;

        /**
         * Initializes a new <tt>InviteToConferenceCallThread</tt> instance
         * which is to invite a list of callees to a conference <tt>Call</tt>.
         * If the specified <tt>call</tt> is <tt>null</tt>, creates a brand new
         * telephony conference.
         *
         * @param callees the addresses of the callees to be invited into a
         * telephony conference. The addresses are provided in multiple
         * <tt>List&lt;String&gt;</tt>s. Each such list of addresses is mapped
         * by the <tt>ProtocolProviderService</tt> through which they are to be
         * invited into the telephony conference. If there are multiple
         * <tt>ProtocolProviderService</tt>s in the specified <tt>Map</tt>, the
         * resulting telephony conference is known by the name
         * &quot;cross-protocol&quot;. It is also allowed to have a list of
         * addresses mapped to <tt>null</tt> which means that the new instance
         * will automatically choose a <tt>ProtocolProviderService</tt> to
         * invite the respective callees into the telephony conference.
         * @param call the <tt>Call</tt> to invite the specified
         * <tt>callees</tt> into. If <tt>null</tt>, this instance will create a
         * brand new telephony conference. Technically, a <tt>Call</tt> instance
         * is protocol/account-specific and it is possible to have
         * cross-protocol/account telephony conferences. That's why the
         * specified <tt>callees</tt> are invited into one and the same
         * <tt>CallConference</tt>: the one in which the specified <tt>call</tt>
         * is participating or a new one if <tt>call</tt> is <tt>null</tt>. Of
         * course, an attempt is made to have all callees from one and the same
         * protocol/account into one <tt>Call</tt> instance.
         */
        public InviteToConferenceCallThread(
                Map<ProtocolProviderService, List<String>> callees,
                Call call)
        {
            this.callees = callees;
            this.call = call;
        }

        /**
         * Invites {@link #callees} into a telephony conference which is
         * optionally specified by {@link #call}.
         */
        @Override
        public void run()
        {
            CallConference conference
                = (call == null) ? null : call.getConference();

            for(Map.Entry<ProtocolProviderService, List<String>> entry
                    : callees.entrySet())
            {
                ProtocolProviderService pps = entry.getKey();

                /*
                 * We'd like to allow specifying callees without specifying an
                 * associated ProtocolProviderService.
                 */
                if (pps != null)
                {
                    OperationSetBasicTelephony<?> basicTelephony
                        = pps.getOperationSet(OperationSetBasicTelephony.class);

                    if(basicTelephony == null)
                        continue;
                }

                List<String> contactList = entry.getValue();
                String[] contactArray
                    = contactList.toArray(new String[contactList.size()]);

                if (ConfigurationUtils.isNormalizePhoneNumber())
                    normalizePhoneNumbers(contactArray);

                /* Try to have a single Call per ProtocolProviderService. */
                Call ppsCall;

                if ((call != null) && call.getProtocolProvider().equals(pps))
                    ppsCall = call;
                else
                {
                    ppsCall = null;
                    if (conference != null)
                    {
                        List<Call> conferenceCalls = conference.getCalls();

                        if (pps == null)
                        {
                            /*
                             * We'd like to allow specifying callees without
                             * specifying an associated ProtocolProviderService.
                             * The simplest approach is to just choose the first
                             * ProtocolProviderService involved in the telephony
                             * conference.
                             */
                            if (call == null)
                            {
                                if (!conferenceCalls.isEmpty())
                                {
                                    ppsCall = conferenceCalls.get(0);
                                    pps = ppsCall.getProtocolProvider();
                                }
                            }
                            else
                            {
                                ppsCall = call;
                                pps = ppsCall.getProtocolProvider();
                            }
                        }
                        else
                        {
                            for (Call conferenceCall : conferenceCalls)
                            {
                                if (pps.equals(
                                        conferenceCall.getProtocolProvider()))
                                {
                                    ppsCall = conferenceCall;
                                    break;
                                }
                            }
                        }
                    }
                }

                OperationSetTelephonyConferencing telephonyConferencing
                    = pps.getOperationSet(
                            OperationSetTelephonyConferencing.class);

                try
                {
                    if (ppsCall == null)
                    {
                        ppsCall
                            = telephonyConferencing.createConfCall(
                                    contactArray,
                                    conference);
                        if (conference == null)
                            conference = ppsCall.getConference();
                    }
                    else
                    {
                        for (String contact : contactArray)
                        {
                            telephonyConferencing.inviteCalleeToCall(
                                    contact,
                                    ppsCall);
                        }
                    }
                }
                catch(Exception e)
                {
                    logger.error(
                            "Failed to invite callees: "
                                + Arrays.toString(contactArray),
                            e);
                    new ErrorDialog(
                            null,
                            GuiActivator.getResources().getI18NString(
                                    "service.gui.ERROR"),
                            e.getMessage(),
                            ErrorDialog.ERROR)
                        .showDialog();
                }
            }
        }
    }

    /**
     * Invites a list of callees to a specific conference <tt>Call</tt>. If the
     * specified <tt>Call</tt> is <tt>null</tt>, creates a brand new telephony
     * conference.
     */
    private static class InviteToConferenceBridgeThread
        extends Thread
    {
        private final ProtocolProviderService callProvider;

        private final String[] callees;

        private final Call call;

        public InviteToConferenceBridgeThread(
                                        ProtocolProviderService callProvider,
                                        String[] callees,
                                        Call call)
        {
            this.callProvider = callProvider;
            this.callees = callees;
            this.call = call;
        }

        @Override
        public void run()
        {
            OperationSetVideoBridge opSetVideoBridge
                = callProvider.getOperationSet(
                    OperationSetVideoBridge.class);

            // Normally if this method is called then this should not happen
            // but we check in order to be sure to be able to proceed.
            if (opSetVideoBridge == null || !opSetVideoBridge.isActive())
                return;

            if (ConfigurationUtils.isNormalizePhoneNumber())
                normalizePhoneNumbers(callees);

            try
            {
                if (call == null)
                {
                    opSetVideoBridge.createConfCall(callees);
                }
                else
                {
                    for (String contact : callees)
                        opSetVideoBridge.inviteCalleeToCall(contact, call);
                }
            }
            catch(Exception e)
            {
                logger.error(
                        "Failed to invite callees: "
                            + Arrays.toString(callees),
                        e);
                new ErrorDialog(
                        null,
                        GuiActivator.getResources().getI18NString(
                                "service.gui.ERROR"),
                        e.getMessage(),
                        e)
                    .showDialog();
            }
        }
    }

    /**
     * Hangs up a specific <tt>Call</tt> (i.e. all <tt>CallPeer</tt>s associated
     * with a <tt>Call</tt>), <tt>CallConference</tt> (i.e. all <tt>Call</tt>s
     * participating in a <tt>CallConference</tt>), or <tt>CallPeer</tt>.
     */
    private static class HangupCallThread
        extends Thread
    {
        private final Call call;

        private final CallConference conference;

        private final CallPeer peer;

        /**
         * Initializes a new <tt>HangupCallThread</tt> instance which is to hang
         * up a specific <tt>Call</tt> i.e. all <tt>CallPeer</tt>s associated
         * with the <tt>Call</tt>.
         *
         * @param call the <tt>Call</tt> whose associated <tt>CallPeer</tt>s are
         * to be hanged up
         */
        public HangupCallThread(Call call)
        {
            this(call, null, null);
        }

        /**
         * Initializes a new <tt>HangupCallThread</tt> instance which is to hang
         * up a specific <tt>CallConference</tt> i.e. all <tt>Call</tt>s
         * participating in the <tt>CallConference</tt>.
         *
         * @param conference the <tt>CallConference</tt> whose participating
         * <tt>Call</tt>s re to be hanged up
         */
        public HangupCallThread(CallConference conference)
        {
            this(null, conference, null);
        }

        /**
         * Initializes a new <tt>HangupCallThread</tt> instance which is to hang
         * up a specific <tt>CallPeer</tt>.
         *
         * @param peer the <tt>CallPeer</tt> to hang up
         */
        public HangupCallThread(CallPeer peer)
        {
            this(null, null, peer);
        }

        /**
         * Initializes a new <tt>HangupCallThread</tt> instance which is to hang
         * up a specific <tt>Call</tt>, <tt>CallConference</tt>, or
         * <tt>CallPeer</tt>.
         *
         * @param call the <tt>Call</tt> whose associated <tt>CallPeer</tt>s are
         * to be hanged up
         * @param conference the <tt>CallConference</tt> whose participating
         * <tt>Call</tt>s re to be hanged up
         * @param peer the <tt>CallPeer</tt> to hang up
         */
        private HangupCallThread(
                Call call,
                CallConference conference,
                CallPeer peer)
        {
            this.call = call;
            this.conference = conference;
            this.peer = peer;
        }

        @Override
        public void run()
        {
            /*
             * There is only an OperationSet which hangs up a CallPeer at a time
             * so prepare a list of all CallPeers to be hanged up.
             */
            Set<CallPeer> peers = new HashSet<CallPeer>();

            if (call != null)
            {
                Iterator<? extends CallPeer> peerIter = call.getCallPeers();

                while (peerIter.hasNext())
                    peers.add(peerIter.next());
            }

            if (conference != null)
                peers.addAll(conference.getCallPeers());

            if (peer != null)
                peers.add(peer);

            for (CallPeer peer : peers)
            {
                OperationSetBasicTelephony<?> basicTelephony
                    = peer.getProtocolProvider().getOperationSet(
                            OperationSetBasicTelephony.class);

                try
                {
                    basicTelephony.hangupCallPeer(peer);
                }
                catch (OperationFailedException ofe)
                {
                    logger.error("Could not hang up: " + peer, ofe);
                }
            }
        }
    }

    /**
     * Creates the enable local video call thread.
     */
    private static class EnableLocalVideoThread
        extends Thread
    {
        private final Call call;

        private final boolean enable;

        /**
         * Creates the enable local video call thread.
         *
         * @param call the call, for which to enable/disable
         * @param enable
         */
        public EnableLocalVideoThread(Call call, boolean enable)
        {
            this.call = call;
            this.enable = enable;
        }

        @Override
        public void run()
        {
            OperationSetVideoTelephony videoTelephony
                = call.getProtocolProvider().getOperationSet(
                        OperationSetVideoTelephony.class);
            boolean enableSucceeded = false;

            if (videoTelephony != null)
            {
                // First make sure the desktop sharing is disabled.
                if (enable && isDesktopSharingEnabled(call))
                {
                    JFrame frame = DesktopSharingFrame.getFrameForCall(call);

                    if (frame != null)
                        frame.dispose();
                }

                try
                {
                    videoTelephony.setLocalVideoAllowed(call, enable);
                    enableSucceeded = true;
                }
                catch (OperationFailedException ex)
                {
                    logger.error(
                            "Failed to toggle the streaming of local video.",
                            ex);

                    ResourceManagementService r = GuiActivator.getResources();
                    String title
                        = r.getI18NString(
                                "service.gui.LOCAL_VIDEO_ERROR_TITLE");
                    String message
                        = r.getI18NString(
                                "service.gui.LOCAL_VIDEO_ERROR_MESSAGE");

                    GuiActivator.getAlertUIService().showAlertPopup(
                            title,
                            message,
                            ex);
                }
            }

            // If the operation didn't succeeded for some reason, make sure the
            // video button doesn't remain selected.
            if (enable && !enableSucceeded)
                getActiveCallContainer(call).setVideoButtonSelected(false);
        }
    }

    /**
     * Puts on hold the given <tt>CallPeer</tt>.
     */
    private static class PutOnHoldCallPeerThread
        extends Thread
    {
        private final CallPeer callPeer;

        private final boolean isOnHold;

        public PutOnHoldCallPeerThread(CallPeer callPeer, boolean isOnHold)
        {
            this.callPeer = callPeer;
            this.isOnHold = isOnHold;
        }

        @Override
        public void run()
        {
            OperationSetBasicTelephony<?> telephony
                = callPeer.getProtocolProvider().getOperationSet(
                        OperationSetBasicTelephony.class);

            try
            {
                if (isOnHold)
                    telephony.putOnHold(callPeer);
                else
                    telephony.putOffHold(callPeer);
            }
            catch (OperationFailedException ex)
            {
                logger.error(
                        "Failed to put"
                            + callPeer.getAddress()
                            + (isOnHold ? " on hold." : " off hold."),
                        ex);
            }
        }
    }

    /**
     * Merges specific existing <tt>Call</tt>s into a specific telephony
     * conference.
     */
    private static class MergeExistingCalls
        extends Thread
    {
        /**
         * The telephony conference in which {@link #calls} are to be merged.
         */
        private final CallConference conference;

        /**
         * Second call.
         */
        private final Collection<Call> calls;

        /**
         * Initializes a new <tt>MergeExistingCalls</tt> instance which is to
         * merge specific existing <tt>Call</tt>s into a specific telephony
         * conference.
         *
         * @param conference the telephony conference in which the specified
         * <tt>Call</tt>s are to be merged
         * @param calls the <tt>Call</tt>s to be merged into the specified
         * telephony conference
         */
        public MergeExistingCalls(
                CallConference conference,
                Collection<Call> calls)
        {
            this.conference = conference;
            this.calls = calls;
        }

        /**
         * Puts off hold the <tt>CallPeer</tt>s of a specific <tt>Call</tt>
         * which are locally on hold.
         *
         * @param call the <tt>Call</tt> which is to have its <tt>CallPeer</tt>s
         * put off hold
         */
        private void putOffHold(Call call)
        {
            Iterator<? extends CallPeer> peers = call.getCallPeers();
            OperationSetBasicTelephony<?> telephony
                = call.getProtocolProvider().getOperationSet(
                        OperationSetBasicTelephony.class);

            while (peers.hasNext())
            {
                CallPeer callPeer = peers.next();
                boolean putOffHold = true;

                if(callPeer instanceof MediaAwareCallPeer)
                {
                    putOffHold
                        = ((MediaAwareCallPeer<?,?,?>) callPeer)
                            .getMediaHandler()
                                .isLocallyOnHold();
                }
                if(putOffHold)
                {
                    try
                    {
                        telephony.putOffHold(callPeer);
                        Thread.sleep(400);
                    }
                    catch(Exception ofe)
                    {
                        logger.error("Failed to put off hold.", ofe);
                    }
                }
            }
        }

        @Override
        public void run()
        {
            // conference
            for (Call call : conference.getCalls())
                putOffHold(call);

            // calls
            if (!calls.isEmpty())
            {
                for(Call call : calls)
                {
                    if (conference.containsCall(call))
                        continue;

                    putOffHold(call);

                    /*
                     * Dispose of the CallPanel associated with the Call which
                     * is to be merged.
                     */
                    closeCallContainerIfNotNecessary(conference, false);

                    call.setConference(conference);
                }
            }
        }
    }

    /**
     * Shows a warning window to warn the user that she's about to start a
     * desktop sharing session.
     *
     * @return <tt>true</tt> if the user has accepted the desktop sharing
     * session; <tt>false</tt>, otherwise
     */
    private static boolean showDesktopSharingWarning()
    {
        Boolean isWarningEnabled
            = GuiActivator.getConfigurationService().getBoolean(
                    desktopSharingWarningProperty,
                    true);

        if (isWarningEnabled.booleanValue())
        {
            ResourceManagementService resources = GuiActivator.getResources();
            MessageDialog warningDialog
                = new MessageDialog(
                        null,
                        resources.getI18NString("service.gui.WARNING"),
                        resources.getI18NString(
                                "service.gui.DESKTOP_SHARING_WARNING"),
                        true);

            switch (warningDialog.showDialog())
            {
                case MessageDialog.OK_RETURN_CODE:
                    return true;
                case MessageDialog.CANCEL_RETURN_CODE:
                    return false;
                case MessageDialog.OK_DONT_ASK_CODE:
                    GuiActivator.getConfigurationService().setProperty(
                            desktopSharingWarningProperty,
                            false);
                    return true;
            }
        }

        return true;
    }

    /**
     * Normalizes the phone numbers (if any) in a list of <tt>String</tt>
     * contact addresses or phone numbers.
     *
     * @param callees the list of contact addresses or phone numbers to be
     * normalized
     */
    private static void normalizePhoneNumbers(String callees[])
    {
        for (int i = 0 ; i < callees.length ; i++)
            callees[i] = GuiActivator.getPhoneNumberI18nService()
                .normalize(callees[i]);
    }

    /**
     * Throws a <tt>RuntimeException</tt> if the current thread is not the AWT
     * event dispatching thread.
     */
    public static void assertIsEventDispatchingThread()
    {
        if (!SwingUtilities.isEventDispatchThread())
        {
            throw new RuntimeException(
                    "The methon can be called only on the AWT event dispatching"
                        + " thread.");
        }
    }

    /**
     * Finds the <tt>CallPanel</tt>, if any, which depicts a specific
     * <tt>CallConference</tt>.
     * <p>
     * <b>Note</b>: The method can be called only on the AWT event dispatching
     * thread.
     * </p>
     *
     * @param conference the <tt>CallConference</tt> to find the depicting
     * <tt>CallPanel</tt> of
     * @return the <tt>CallPanel</tt> which depicts the specified
     * <tt>CallConference</tt> if such a <tt>CallPanel</tt> exists; otherwise,
     * <tt>null</tt>
     * @throws RuntimeException if the method is not called on the AWT event
     * dispatching thread
     */
    private static CallPanel findCallPanel(CallConference conference)
    {
        synchronized (callPanels)
        {
            return callPanels.get(conference);
        }
    }

    /**
     * Notifies {@link #callPanels} about a specific <tt>CallEvent</tt> received
     * by <tt>CallManager</tt> (because they may need to update their UI, for
     * example).
     * <p>
     * <b>Note</b>: The method can be called only on the AWT event dispatching
     * thread.
     * </p>
     *
     * @param ev the <tt>CallEvent</tt> received by <tt>CallManager</tt> which
     * is to be forwarded to <tt>callPanels</tt> for further
     * <tt>CallPanel</tt>-specific handling
     * @throws RuntimeException if the method is not called on the AWT event
     * dispatching thread
     */
    private static void forwardCallEventToCallPanels(CallEvent ev)
    {
        assertIsEventDispatchingThread();

        CallPanel[] callPanels;

        synchronized (CallManager.callPanels)
        {
            Collection<CallPanel> values = CallManager.callPanels.values();

            callPanels = values.toArray(new CallPanel[values.size()]);
        }

        for (CallPanel callPanel : callPanels)
        {
            try
            {
                callPanel.onCallEvent(ev);
            }
            catch (Exception ex)
            {
                /*
                 * There is no practical reason while the failure of a CallPanel
                 * to handle the CallEvent should cause the other CallPanels to
                 * be left out-of-date.
                 */
                logger.error("A CallPanel failed to handle a CallEvent", ex);
            }
        }
    }

    /**
     * Creates a call for the supplied operation set.
     * @param opSetClass the operation set to use to create call.
     * @param protocolProviderService the protocol provider
     * @param contact the contact address to call
     */
    static void createCall(Class<? extends OperationSet> opSetClass,
                    ProtocolProviderService protocolProviderService,
                    String contact)
    {
        createCall(opSetClass, protocolProviderService, contact, null);
    }

    /**
     * Creates a call for the supplied operation set.
     * @param opSetClass the operation set to use to create call.
     * @param protocolProviderService the protocol provider
     * @param contact the contact address to call
     *  @param uiContact the <tt>MetaContact</tt> we're calling
     */
    static void createCall(
                    Class<? extends OperationSet> opSetClass,
                    ProtocolProviderService protocolProviderService,
                    String contact,
                    UIContactImpl uiContact)
    {
        if (opSetClass.equals(OperationSetBasicTelephony.class))
        {
            createCall(protocolProviderService, contact, uiContact);
        }
        else if (opSetClass.equals(OperationSetVideoTelephony.class))
        {
            createVideoCall(protocolProviderService, contact, uiContact);
        }
        else if (opSetClass.equals(OperationSetDesktopStreaming.class))
        {
            createDesktopSharing(
                protocolProviderService, contact, uiContact);
        }
    }

    /**
     * Creates a call for the default contact of the metacontact
     *
     * @param metaContact the metacontact that will be called.
     * @param isVideo if <tt>true</tt> will create video call.
     * @param isDesktop if <tt>true</tt> will share the desktop.
     * @param shareRegion if <tt>true</tt> will share a region of the desktop.
     */
    public static void call(MetaContact metaContact,
                            boolean isVideo,
                            boolean isDesktop,
                            boolean shareRegion)
    {
        Contact contact = metaContact
            .getDefaultContact(getOperationSetForCall(isVideo, isDesktop));

        call(contact, isVideo, isDesktop, shareRegion);
    }

    /**
     * A particular contact has been selected no options to select
     * will just call it.
     * @param contact the contact to call
     * @param contactResource the specific contact resource
     * @param isVideo is video enabled
     * @param isDesktop is desktop sharing enabled
     * @param shareRegion is sharing the whole desktop or just a region.
     */
    public static void call(Contact contact,
                            ContactResource contactResource,
                            boolean isVideo,
                            boolean isDesktop,
                            boolean shareRegion)
    {
        if(isDesktop)
        {
            if(shareRegion)
            {
                createRegionDesktopSharing(
                    contact.getProtocolProvider(),
                    contact.getAddress(),
                    null);
            }
            else
                createDesktopSharing(contact.getProtocolProvider(),
                                    contact.getAddress(),
                                    null);
        }
        else
        {
            new CreateCallThread(
                    contact.getProtocolProvider(),
                    contact,
                    contactResource,
                    isVideo).start();
        }
    }

    /**
     * Creates a call to the conference described in
     * <tt>conferenceDescription</tt> through <tt>protocolProvider</tt>
     *
     * @param protocolProvider the protocol provider through which to create
     * the call
     * @param conferenceDescription the description of the conference to call
     * @param chatRoom the chat room associated with the call.
     */
    public static void call(ProtocolProviderService protocolProvider,
                            ConferenceDescription conferenceDescription,
                            ChatRoom chatRoom)
    {
        new CreateCallThread(protocolProvider, conferenceDescription, chatRoom)
            .start();
    }

    /**
     * A particular contact has been selected no options to select
     * will just call it.
     * @param contact the contact to call
     * @param isVideo is video enabled
     * @param isDesktop is desktop sharing enabled
     * @param shareRegion is sharing the whole desktop or just a region.
     */
    public static void call(Contact contact,
                            boolean isVideo,
                            boolean isDesktop,
                            boolean shareRegion)
    {
        if(isDesktop)
        {
            if(shareRegion)
            {
                createRegionDesktopSharing(
                    contact.getProtocolProvider(),
                    contact.getAddress(),
                    null);
            }
            else
                createDesktopSharing(contact.getProtocolProvider(),
                                    contact.getAddress(),
                                    null);
        }
        else
        {
            new CreateCallThread(   contact.getProtocolProvider(),
                                    contact,
                                    null,
                                    isVideo).start();
        }
    }

    /**
     * Calls a phone showing a dialog to choose a provider.
     * @param phone phone to call
     * @param isVideo if <tt>true</tt> will create video call.
     * @param isDesktop if <tt>true</tt> will share the desktop.
     * @param shareRegion if <tt>true</tt> will share a region of the desktop.
     */
    public static void call(final String phone,
                            boolean isVideo,
                            boolean isDesktop,
                            final boolean shareRegion)
    {
        call(phone, null, isVideo, isDesktop, shareRegion);
    }

    /**
     * Calls a phone showing a dialog to choose a provider.
     * @param phone phone to call
     * @param uiContact the <tt>UIContactImpl</tt> we're calling
     * @param isVideo if <tt>true</tt> will create video call.
     * @param isDesktop if <tt>true</tt> will share the desktop.
     * @param shareRegion if <tt>true</tt> will share a region of the desktop.
     */
    public static void call(final String phone,
                            final UIContactImpl uiContact,
                            boolean isVideo,
                            boolean isDesktop,
                            final boolean shareRegion)
    {
        List<ProtocolProviderService> providers =
            CallManager.getTelephonyProviders();

        if(providers.size() > 1)
        {
            ChooseCallAccountDialog chooseAccount =
                new ChooseCallAccountDialog(
                    phone,
                    getOperationSetForCall(isVideo, isDesktop),
                    providers)
            {
                    @Override
                    public void callButtonPressed()
                    {
                        if(shareRegion)
                        {
                            createRegionDesktopSharing(
                                getSelectedProvider(), phone, uiContact);
                        }
                        else
                            super.callButtonPressed();
                    }
            };
            chooseAccount.setUIContact(uiContact);
            chooseAccount.setVisible(true);
        }
        else
        {
            createCall(providers.get(0), phone, uiContact);
        }
    }

    /**
     * Obtain operation set checking the params.
     * @param isVideo if <tt>true</tt> use OperationSetVideoTelephony.
     * @param isDesktop if <tt>true</tt> use OperationSetDesktopStreaming.
     * @return the operation set, default is OperationSetBasicTelephony.
     */
    private static Class<? extends OperationSet> getOperationSetForCall(
        boolean isVideo, boolean isDesktop)
    {
        if(isVideo)
        {
            if(isDesktop)
                return OperationSetDesktopStreaming.class;
            else
                return OperationSetVideoTelephony.class;
        }
        else
            return OperationSetBasicTelephony.class;
    }

    /**
     * Call any of the supplied details.
     *
     * @param uiContactDetailList the list with details to choose for calling
     * @param uiContact the <tt>UIContactImpl</tt> to check what is enabled,
     * available.
     * @param isVideo if <tt>true</tt> will create video call.
     * @param isDesktop if <tt>true</tt> will share the desktop.
     * @param invoker the invoker component
     * @param location the location where this was invoked.
     */
    public static void call(List<UIContactDetail> uiContactDetailList,
                            UIContactImpl uiContact,
                            boolean isVideo,
                            boolean isDesktop,
                            JComponent invoker,
                            Point location)
    {
        call(uiContactDetailList,
            uiContact,
            isVideo,
            isDesktop,
            invoker,
            location,
            true);
    }

    /**
     * Call any of the supplied details.
     *
     * @param uiContactDetailList the list with details to choose for calling
     * @param uiContact the <tt>UIContactImpl</tt> to check what is enabled,
     * available.
     * @param isVideo if <tt>true</tt> will create video call.
     * @param isDesktop if <tt>true</tt> will share the desktop.
     * @param invoker the invoker component
     * @param location the location where this was invoked.
     * @param usePreferredProvider whether to use the <tt>uiContact</tt>
     * preferredProvider if provided.
     */
    private static void call(List<UIContactDetail> uiContactDetailList,
                            UIContactImpl uiContact,
                            boolean isVideo,
                            boolean isDesktop,
                            JComponent invoker,
                            Point location,
                            boolean usePreferredProvider)
    {
        ChooseCallAccountPopupMenu chooseCallAccountPopupMenu = null;

        Class<? extends OperationSet> opsetClass =
            getOperationSetForCall(isVideo, isDesktop);

        UIPhoneUtil contactPhoneUtil = null;
        if (uiContact != null
            && uiContact.getDescriptor() instanceof MetaContact)
            contactPhoneUtil = UIPhoneUtil
                .getPhoneUtil((MetaContact) uiContact.getDescriptor());

        if(contactPhoneUtil != null)
        {
            boolean addAdditionalNumbers = false;
            if(!isVideo
                || ConfigurationUtils
                        .isRouteVideoAndDesktopUsingPhoneNumberEnabled())
            {
                addAdditionalNumbers = true;
            }
            else
            {
                if(isVideo && contactPhoneUtil != null)
                {
                    // lets check is video enabled in additional numbers
                    addAdditionalNumbers =
                        contactPhoneUtil.isVideoCallEnabled() ?
                            isDesktop ?
                                contactPhoneUtil.isDesktopSharingEnabled()
                                : true
                            : false;
                }
            }

            if(addAdditionalNumbers)
            {
                uiContactDetailList.addAll(
                    contactPhoneUtil.getAdditionalNumbers());
            }
        }

        if (uiContactDetailList.size() == 1)
        {
            UIContactDetail detail = uiContactDetailList.get(0);

            ProtocolProviderService preferredProvider = null;

            if(usePreferredProvider)
                preferredProvider =
                    detail.getPreferredProtocolProvider(opsetClass);

            List<ProtocolProviderService> providers = null;
            String protocolName = null;

            if (preferredProvider != null)
            {
                if (preferredProvider.isRegistered())
                {
                    createCall(opsetClass,
                               preferredProvider,
                               detail.getAddress(),
                               uiContact);
                }

                // If we have a provider, but it's not registered we try to
                // obtain all registered providers for the same protocol as the
                // given preferred provider.
                else
                {
                    protocolName = preferredProvider.getProtocolName();
                    providers = AccountUtils.getRegisteredProviders(protocolName,
                        opsetClass);
                }
            }
            // If we don't have a preferred provider we try to obtain a
            // preferred protocol name and all registered providers for it.
            else
            {
                protocolName = detail.getPreferredProtocol(opsetClass);

                if (protocolName != null)
                    providers
                        = AccountUtils.getRegisteredProviders(protocolName,
                            opsetClass);
                else
                    providers
                        = AccountUtils.getRegisteredProviders(opsetClass);
            }

            // If our call didn't succeed, try to call through one of the other
            // protocol providers obtained above.
            if (providers != null)
            {
                int providersCount = providers.size();

                if (providersCount <= 0)
                {
                    new ErrorDialog(null,
                        GuiActivator.getResources().getI18NString(
                            "service.gui.CALL_FAILED"),
                        GuiActivator.getResources().getI18NString(
                            "service.gui.NO_ONLINE_TELEPHONY_ACCOUNT"))
                    .showDialog();
                }
                else if (providersCount == 1)
                {
                    createCall(
                        opsetClass,
                        providers.get(0),
                        detail.getAddress(),
                        uiContact);
                }
                else if (providersCount > 1)
                {
                    chooseCallAccountPopupMenu =
                        new ChooseCallAccountPopupMenu(
                            invoker, detail.getAddress(), providers,
                            opsetClass);
                }
            }
        }
        else if (uiContactDetailList.size() > 1)
        {
            chooseCallAccountPopupMenu
                = new ChooseCallAccountPopupMenu(invoker, uiContactDetailList,
                        opsetClass);
        }

        // If the choose dialog is created we're going to show it.
        if (chooseCallAccountPopupMenu != null)
        {
            if (uiContact != null)
                chooseCallAccountPopupMenu.setUIContact(uiContact);

            chooseCallAccountPopupMenu.showPopupMenu(location.x, location.y);
        }
    }


    /**
     * Call the ui contact.
     *
     * @param uiContact the contact to call.
     * @param isVideo if <tt>true</tt> will create video call.
     * @param isDesktop if <tt>true</tt> will share the desktop.
     * @param invoker the invoker component
     * @param location the location where this was invoked.
     */
    public static void call(UIContact uiContact,
                            boolean isVideo,
                            boolean isDesktop,
                            JComponent invoker,
                            Point location)
    {
        UIContactImpl uiContactImpl = null;
        if(uiContact instanceof UIContactImpl)
        {
            uiContactImpl = (UIContactImpl) uiContact;
        }

        List<UIContactDetail> telephonyContacts
            = uiContact.getContactDetailsForOperationSet(
                getOperationSetForCall(isVideo, isDesktop));

        boolean ignorePreferredProvider =
            GuiActivator.getConfigurationService().getBoolean(
                IGNORE_PREFERRED_PROVIDER_PROP, false);

        call(   telephonyContacts,
                uiContactImpl,
                isVideo,
                isDesktop,
                invoker,
                location,
                !ignorePreferredProvider);
    }

    /**
     * Tries to resolves a peer address into a display name, by reqesting the
     * <tt>ContactSourceService</tt>s. This function returns only the
     * first match.
     *
     * @param peerAddress The peer address.
     * @param listener the listener to fire change events for later resolutions
     * of display name and image, if exist.
     * @return The corresponding display name, if there is a match. Null
     * otherwise.
     */
    private static String queryContactSource(
        String peerAddress,
        DetailsResolveListener listener)
    {
        String displayName = null;

        if(!StringUtils.isNullOrEmpty(peerAddress))
        {
            ContactSourceSearcher searcher
                = new ContactSourceSearcher(peerAddress, listener);

            if(listener == null)
            {
                searcher.run();
                displayName = searcher.displayName;
            }
            else
                new Thread(searcher, searcher.getClass().getName()).start();
        }

        return displayName;
    }

    /**
     * Runnable that will search for a source contact and when found will
     * fire events to inform that display name or contact image is found.
     */
    private static class ContactSourceSearcher
        implements Runnable
    {
        private final DetailsResolveListener listener;

        private final String peerAddress;

        private String displayName;
        private byte[] displayImage;

        private ContactSourceSearcher(
            String peerAddress,
            DetailsResolveListener listener)
        {
            this.peerAddress = peerAddress;
            this.listener = listener;
        }

        @Override
        public void run()
        {
            Vector<ResolveAddressToDisplayNameContactQueryListener> resolvers
                = new Vector<ResolveAddressToDisplayNameContactQueryListener>
                    (1, 1);

            // will strip the @server-address part, as the regular expression
            // will match it
            int index = peerAddress.indexOf("@");
            String peerUserID =
                (index > -1) ? peerAddress.substring(0, index) : peerAddress;

            // searches for the whole number/username or with the @serverpart
            String quotedPeerUserID = Pattern.quote(peerUserID);
            Pattern pattern = Pattern.compile(
                "^(" + quotedPeerUserID + "|" + quotedPeerUserID + "@.*)$");

            // Queries all available resolvers
            for(ContactSourceService css : GuiActivator.getContactSources())
            {
                if(css.getType() != ContactSourceService.SEARCH_TYPE)
                    continue;

                ContactQuery query;
                if(css instanceof ExtendedContactSourceService)
                {
                    // use the pattern method of (ExtendedContactSourceService)
                    query = ((ExtendedContactSourceService)css)
                        .createContactQuery(pattern);
                }
                else
                {
                    query = css.createContactQuery(peerUserID);
                }

                if(query == null)
                    continue;

                resolvers.add(
                    new ResolveAddressToDisplayNameContactQueryListener(query));
                query.start();
            }

            long startTime = System.currentTimeMillis();

            // The detault timeout is set to 500ms.
            long timeout = listener == null ? 500 : -1;
            boolean hasRunningResolver = true;
            // Loops until we found a valid display name and image,
            // or waits for timeout if any.
            while((displayName == null || displayImage == null)
                    && hasRunningResolver
                    && (listener == null || listener.isInterested()))
            {
                hasRunningResolver = false;

                for(int i = 0; i < resolvers.size()
                    && (displayName == null || displayImage == null)
                    && (listener == null || listener.isInterested()); ++i)
                {
                    ResolveAddressToDisplayNameContactQueryListener resolver
                        = resolvers.get(i);
                    if(!resolver.isRunning())
                    {
                        if(displayName == null
                            && resolver.isFoundName())
                        {
                            displayName = resolver.getResolvedName();
                            // If this is the same result as the peer address,
                            // then that is not what we are looking for. Then,
                            // continue the search.
                            if(displayName.equals(peerAddress))
                            {
                                displayName = null;
                            }

                            if(listener != null && displayName != null)
                            {
                                // fire
                                listener.displayNameUpdated(displayName);
                            }
                        }

                        if(displayImage == null
                            && resolver.isFoundImage())
                        {
                            displayImage = resolver.getResolvedImage();

                            String name = resolver.getResolvedName();
                            // If this is the same result as the peer address,
                            // then that is not what we are looking for. Then,
                            // continue the search.
                            if(name != null && name.equals(peerAddress))
                            {
                                displayImage = null;
                            }
                            else if(listener != null && displayImage != null)
                            {
                                // fire
                                listener.imageUpdated(displayImage);
                            }
                        }
                    }
                    else
                        hasRunningResolver = true;
                }
                Thread.yield();

                if( timeout > 0 &&
                    System.currentTimeMillis() - startTime >= timeout)
                    break;
            }

            // Free lasting resolvers.
            for(int i = 0; i < resolvers.size(); ++i)
            {
                ResolveAddressToDisplayNameContactQueryListener resolver
                    = resolvers.get(i);
                if(resolver.isRunning())
                {
                    resolver.stop();
                }
            }
        }
    }

    /**
     * A listener that will be notified for found source contacts details.
     */
    public static interface DetailsResolveListener
    {
        /**
         * When a display name is found.
         * @param displayName the name that was found.
         */
        public void displayNameUpdated(String displayName);

        /**
         * The image that was found.
         * @param image the image that was found.
         */
        public void imageUpdated(byte[] image);

        /**
         * Whether the listener is still interested in the events.
         * When the window/panel using this resolver listener is closed
         * will return false;
         * @return whether the listener is still interested in the events.
         */
        public boolean isInterested();
    }
}
