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

import java.text.*;
import java.util.*;

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

/**
 * Tests Basic telephony functionality by making one provider call the other.
 *
 * @author Emil Ivov
 */
public class TestOperationSetBasicTelephonySipImpl
    extends TestCase
{
    private static final Logger logger
        = Logger.getLogger(TestOperationSetBasicTelephonySipImpl.class);

    /**
     *
     */
    private SipSlickFixture fixture = new SipSlickFixture();

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

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

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

    /**
     * Creates a call from provider1 to provider2 then cancels it without
     * waiting for provider1 to answer.
     *
     * @throws ParseException if we hand a malformed URI to someone
     * @throws OperationFailedException if something weird happens.
     */
    public void testCreateCancelCall()
        throws ParseException, OperationFailedException
    {
        OperationSetBasicTelephony<?> basicTelephonyP1
            = fixture.provider1.getOperationSet(
                OperationSetBasicTelephony.class);
        OperationSetBasicTelephony<?> basicTelephonyP2
            = fixture.provider2.getOperationSet(
                OperationSetBasicTelephony.class);

        CallEventCollector call1Listener
            = new CallEventCollector(basicTelephonyP1);
        CallEventCollector call2Listener
            = new CallEventCollector(basicTelephonyP2);

        //Provider1 calls Provider2
        String provider2Address
            = fixture.provider2.getAccountID().getAccountAddress();

        Call callAtP1 = basicTelephonyP1.createCall(provider2Address);

        call1Listener.waitForEvent(10000);
        call2Listener.waitForEvent(10000);

        //make sure that both listeners have received their events.
        assertEquals("The provider that created the call did not dispatch "
                     + "an event that it has done so."
                     , 1, call1Listener.collectedEvents.size());
        //call1 listener checks
        CallEvent callCreatedEvent
            = (CallEvent)call1Listener.collectedEvents.get(0);

        assertEquals("CallEvent.getEventID()"
                     ,CallEvent.CALL_INITIATED, callCreatedEvent.getEventID());
        assertSame("CallEvent.getSource()"
                     , callAtP1, callCreatedEvent.getSource());

        //call2 listener checks
        assertTrue("The callee provider did not receive a call or did "
                     +"not issue an event."
                     , call2Listener.collectedEvents.size() > 0);
        CallEvent callReceivedEvent
            = (CallEvent)call2Listener.collectedEvents.get(0);
        Call callAtP2 = callReceivedEvent.getSourceCall();

        assertEquals("CallEvent.getEventID()"
                     ,CallEvent.CALL_RECEIVED, callReceivedEvent.getEventID());
        assertNotNull("CallEvent.getSource()", callAtP2);

        //verify that call peers are properly created
        assertEquals("callAtP1.getCallPeerCount()"
                     , 1, callAtP1.getCallPeerCount());
        assertEquals("callAtP2.getCallPeerCount()"
                     , 1, callAtP2.getCallPeerCount());

        CallPeer peerAtP1
            = callAtP1.getCallPeers().next();
        CallPeer peerAtP2
            = callAtP2.getCallPeers().next();

        //now add listeners to the peers and make sure they have entered
        //the states they were expected to.
        //check states for call peers at both parties
        CallPeerStateEventCollector stateCollectorForPp1
            = new CallPeerStateEventCollector(
                peerAtP1, CallPeerState.ALERTING_REMOTE_SIDE);
        CallPeerStateEventCollector stateCollectorForPp2
            = new CallPeerStateEventCollector(
                peerAtP2, CallPeerState.INCOMING_CALL);

        stateCollectorForPp1.waitForEvent(10000, true);
        stateCollectorForPp2.waitForEvent(10000, true);

        assertSame("peerAtP1.getCall"
                   , peerAtP1.getCall(), callAtP1);
        assertSame("peerAtP2.getCall"
                   , peerAtP2.getCall(), callAtP2);

        //make sure that the peers are in the proper state
        assertEquals("The peer at provider one was not in the "
                     +"right state."
                    , CallPeerState.ALERTING_REMOTE_SIDE
                    , peerAtP1.getState());
        assertEquals("The peer at provider two was not in the "
                     +"right state."
                    , CallPeerState.INCOMING_CALL
                    , peerAtP2.getState());


        //test whether caller/callee info is properly distributed in case
        //the server is said to support it.
        if(Boolean.getBoolean("accounts.sip.PRESERVE_PEER_INFO"))
        {
            //check properties on the remote call peer for the party that
            //initiated the call.
            String expectedPeer1Address
                = fixture.provider2.getAccountID().getAccountAddress();
            String expectedPeer1DisplayName
                = System.getProperty(
                    SipProtocolProviderServiceLick.ACCOUNT_2_PREFIX
                    + ProtocolProviderFactory.DISPLAY_NAME);

            //do not asser equals here since one of the addresses may contain a
            //display name or something of the kind
            assertTrue("Provider 2 did not advertise their "
                       + "accountID.getAccoutAddress() address."
                       , expectedPeer1Address.indexOf(
                           peerAtP1.getAddress()) != -1
                       || peerAtP1.getAddress().indexOf(
                           expectedPeer1Address) != -1);
            assertEquals("Provider 2 did not properly advertise their "
                         + "display name."
                         , expectedPeer1DisplayName
                         , peerAtP1.getDisplayName());

            //check properties on the remote call peer for the party that
            //receives the call.
            String expectedPeer2Address
                = fixture.provider1.getAccountID().getAccountAddress();
            String expectedPeer2DisplayName
                = System.getProperty(
                    SipProtocolProviderServiceLick.ACCOUNT_1_PREFIX
                    + ProtocolProviderFactory.DISPLAY_NAME);

            //do not asser equals here since one of the addresses may contain a
            //display name or something of the kind
            assertTrue("Provider 1 did not advertise their "
                       + "accountID.getAccoutAddress() address."
                       , expectedPeer2Address.indexOf(
                           peerAtP2.getAddress()) != -1
                       || peerAtP2.getAddress().indexOf(
                           expectedPeer2Address) != -1);
            assertEquals("Provider 1 did not properly advertise their "
                         + "display name."
                         , expectedPeer2DisplayName
                         , peerAtP2.getDisplayName());
        }

        //we'll now try to cancel the call

        //listeners monitoring state change of the peer
        stateCollectorForPp1 = new CallPeerStateEventCollector(
                peerAtP1, CallPeerState.DISCONNECTED);
        stateCollectorForPp2 = new CallPeerStateEventCollector(
                peerAtP2, CallPeerState.DISCONNECTED);

        //listeners waiting for the op set to announce the end of the call
        call1Listener = new CallEventCollector(basicTelephonyP1);
        call2Listener = new CallEventCollector(basicTelephonyP2);

        //listeners monitoring the state of the call
        CallStateEventCollector call1StateCollector
            = new CallStateEventCollector(callAtP1, CallState.CALL_ENDED);
        CallStateEventCollector call2StateCollector
            = new CallStateEventCollector(callAtP2, CallState.CALL_ENDED);

        //Now make the caller CANCEL the call.
        basicTelephonyP1.hangupCallPeer(peerAtP1);

        //wait for everything to happen
        call1Listener.waitForEvent(10000);
        call2Listener.waitForEvent(10000);
        stateCollectorForPp1.waitForEvent(10000);
        stateCollectorForPp2.waitForEvent(10000);
        call1StateCollector.waitForEvent(10000);
        call2StateCollector.waitForEvent(10000);


        //make sure that the peer is disconnected
        assertEquals("The peer at provider one was not in the "
                     +"right state."
                    , CallPeerState.DISCONNECTED
                    , peerAtP1.getState());

        //make sure the telephony operation set distributed an event for the end
        //of the call
        assertEquals("The basic telephony operation set did not distribute "
                     +"an event to notify us that a call has been ended."
                    , 1
                    , call1Listener.collectedEvents.size());

        CallEvent collectedEvent
            = (CallEvent)call1Listener.collectedEvents.get(0);

        assertEquals("The basic telephony operation set did not distribute "
                     +"an event to notify us that a call has been ended."
                     , CallEvent.CALL_ENDED
                     , collectedEvent.getEventID());

        //same for provider 2

        //make sure that the peer is disconnected
        assertEquals("The peer at provider one was not in the "
                     +"right state."
                    , CallPeerState.DISCONNECTED
                    , peerAtP2.getState());

        //make sure the telephony operation set distributed an event for the end
        //of the call
        assertEquals("The basic telephony operation set did not distribute "
                     +"an event to notify us that a call has been ended."
                    , 1
                    , call2Listener.collectedEvents.size());

        collectedEvent
            = (CallEvent)call2Listener.collectedEvents.get(0);

        assertEquals("The basic telephony operation set did not distribute "
                     +"an event to notify us that a call has been ended."
                     , CallEvent.CALL_ENDED
                     , collectedEvent.getEventID());

        //make sure that the call objects are in an ENDED state.
        assertEquals("A call did not change its state to CallState.CALL_ENDED "
                     +"when it ended."
                     , CallState.CALL_ENDED
                     , callAtP1.getCallState());
        assertEquals("A call did not change its state to CallState.CALL_ENDED "
                     +"when it ended."
                     , CallState.CALL_ENDED
                     , callAtP2.getCallState());
    }

    /**
     * Creates a call from provider1 to provider2 then rejects the call from the
     * side of provider2 (provider2 replies with busy-tone).
     *
     * @throws ParseException if we hand a malformed URI to someone
     * @throws OperationFailedException if something weird happens.
     */
    public void testCreateRejectCall()
        throws ParseException, OperationFailedException
    {
        OperationSetBasicTelephony<?> basicTelephonyP1
            = fixture.provider1.getOperationSet(
                OperationSetBasicTelephony.class);
        OperationSetBasicTelephony<?> basicTelephonyP2
            = fixture.provider2.getOperationSet(
                OperationSetBasicTelephony.class);

        CallEventCollector call1Listener
            = new CallEventCollector(basicTelephonyP1);
        CallEventCollector call2Listener
            = new CallEventCollector(basicTelephonyP2);

        //Provider1 calls Provider2
        String provider2Address
            = fixture.provider2.getAccountID().getAccountAddress();

        Call callAtP1 = basicTelephonyP1.createCall(provider2Address);

        call1Listener.waitForEvent(10000);
        call2Listener.waitForEvent(10000);

        //make sure that both listeners have received their events.
        assertEquals("The provider that created the call did not dispatch "
                     + "an event that it has done so."
                     , 1, call1Listener.collectedEvents.size());
        //call1 listener checks
        CallEvent callCreatedEvent
            = (CallEvent)call1Listener.collectedEvents.get(0);

        assertEquals("CallEvent.getEventID()"
                     ,CallEvent.CALL_INITIATED, callCreatedEvent.getEventID());
        assertSame("CallEvent.getSource()"
                     , callAtP1, callCreatedEvent.getSource());

        //call2 listener checks
        assertTrue("The callee provider did not receive a call or did "
                     +"not issue an event."
                     , call2Listener.collectedEvents.size() > 0);
        CallEvent callReceivedEvent
            = (CallEvent)call2Listener.collectedEvents.get(0);
        Call callAtP2 = callReceivedEvent.getSourceCall();

        assertEquals("CallEvent.getEventID()"
                     ,CallEvent.CALL_RECEIVED, callReceivedEvent.getEventID());
        assertNotNull("CallEvent.getSource()", callAtP2);

        //verify that call peers are properly created
        assertEquals("callAtP1.getCallPeerCount()"
                     , 1, callAtP1.getCallPeerCount());
        assertEquals("callAtP2.getCallPeerCount()"
                     , 1, callAtP2.getCallPeerCount());

        CallPeer peerAtP1
            = callAtP1.getCallPeers().next();
        CallPeer peerAtP2
            = callAtP2.getCallPeers().next();

        //now add listeners to the peers and make sure they have entered
        //the states they were expected to.
        //check states for call peers at both parties
        CallPeerStateEventCollector stateCollectorForPp1
            = new CallPeerStateEventCollector(
                peerAtP1, CallPeerState.ALERTING_REMOTE_SIDE);
        CallPeerStateEventCollector stateCollectorForPp2
            = new CallPeerStateEventCollector(
                peerAtP2, CallPeerState.INCOMING_CALL);

        stateCollectorForPp1.waitForEvent(10000, true);
        stateCollectorForPp2.waitForEvent(10000, true);

        assertSame("peerAtP1.getCall"
                   , peerAtP1.getCall(), callAtP1);
        assertSame("peerAtP2.getCall"
                   , peerAtP2.getCall(), callAtP2);

        //make sure that the peers are in the proper state
        assertEquals("The peer at provider one was not in the "
                     +"right state."
                    , CallPeerState.ALERTING_REMOTE_SIDE
                    , peerAtP1.getState());
        assertEquals("The peer at provider two was not in the "
                     +"right state."
                    , CallPeerState.INCOMING_CALL
                    , peerAtP2.getState());


        //test whether caller/callee info is properly distributed in case
        //the server is said to support it.
        if(Boolean.getBoolean("accounts.sip.PRESERVE_PEER_INFO"))
        {
            //check properties on the remote call peer for the party that
            //initiated the call.
            String expectedPeer1Address
                = fixture.provider2.getAccountID().getAccountAddress();
            String expectedPeer1DisplayName
                = System.getProperty(
                    SipProtocolProviderServiceLick.ACCOUNT_2_PREFIX
                    + ProtocolProviderFactory.DISPLAY_NAME);

            //do not asser equals here since one of the addresses may contain a
            //display name or something of the kind
            assertTrue("Provider 2 did not advertise their "
                       + "accountID.getAccoutAddress() address."
                       , expectedPeer1Address.indexOf(
                           peerAtP1.getAddress()) != -1
                       || peerAtP1.getAddress().indexOf(
                           expectedPeer1Address) != -1);
            assertEquals("Provider 2 did not properly advertise their "
                         + "display name."
                         , expectedPeer1DisplayName
                         , peerAtP1.getDisplayName());

            //check properties on the remote call peer for the party that
            //receives the call.
            String expectedPeer2Address
                = fixture.provider1.getAccountID().getAccountAddress();
            String expectedPeer2DisplayName
                = System.getProperty(
                    SipProtocolProviderServiceLick.ACCOUNT_1_PREFIX
                    + ProtocolProviderFactory.DISPLAY_NAME);

            //do not asser equals here since one of the addresses may contain a
            //display name or something of the kind
            assertTrue("Provider 1 did not advertise their "
                       + "accountID.getAccoutAddress() address."
                       , expectedPeer2Address.indexOf(
                           peerAtP2.getAddress()) != -1
                       || peerAtP2.getAddress().indexOf(
                           expectedPeer2Address) != -1);
            assertEquals("Provider 1 did not properly advertise their "
                         + "display name."
                         , expectedPeer2DisplayName
                         , peerAtP2.getDisplayName());
        }

        //we'll now try to send busy tone.

        //listeners monitoring state change of the peer
        CallPeerStateEventCollector busyStateCollectorForPp1
            = new CallPeerStateEventCollector(
                peerAtP1, CallPeerState.BUSY);
        stateCollectorForPp1 = new CallPeerStateEventCollector(
                peerAtP1, CallPeerState.DISCONNECTED);
        stateCollectorForPp2 = new CallPeerStateEventCollector(
                peerAtP2, CallPeerState.DISCONNECTED);

        //listeners waiting for the op set to announce the end of the call
        call1Listener = new CallEventCollector(basicTelephonyP1);
        call2Listener = new CallEventCollector(basicTelephonyP2);

        //listeners monitoring the state of the call
        CallStateEventCollector call1StateCollector
            = new CallStateEventCollector(callAtP1, CallState.CALL_ENDED);
        CallStateEventCollector call2StateCollector
            = new CallStateEventCollector(callAtP2, CallState.CALL_ENDED);

        //Now make the caller CANCEL the call.
        basicTelephonyP2.hangupCallPeer(peerAtP2);
        busyStateCollectorForPp1.waitForEvent(10000);
        basicTelephonyP1.hangupCallPeer(peerAtP1);

        //wait for everything to happen
        call1Listener.waitForEvent(10000);
        call2Listener.waitForEvent(10000);
        stateCollectorForPp1.waitForEvent(10000);
        stateCollectorForPp2.waitForEvent(10000);
        call1StateCollector.waitForEvent(10000);
        call2StateCollector.waitForEvent(10000);


        //make sure that the peer is disconnected
        assertEquals("The peer at provider one was not in the "
                     +"right state."
                    , CallPeerState.DISCONNECTED
                    , peerAtP1.getState());



        //make sure the telephony operation set distributed an event for the end
        //of the call
        assertEquals("The basic telephony operation set did not distribute "
                     +"an event to notify us that a call has been ended."
                    , 1
                    , call1Listener.collectedEvents.size());

        CallEvent collectedEvent
            = (CallEvent)call1Listener.collectedEvents.get(0);

        assertEquals("The basic telephony operation set did not distribute "
                     +"an event to notify us that a call has been ended."
                     , CallEvent.CALL_ENDED
                     , collectedEvent.getEventID());

        //same for provider 2

        //make sure that the peer is disconnected
        assertEquals("The peer at provider one was not in the "
                     +"right state."
                    , CallPeerState.DISCONNECTED
                    , peerAtP2.getState());

        //make sure the telephony operation set distributed an event for the end
        //of the call
        assertEquals("The basic telephony operation set did not distribute "
                     +"an event to notify us that a call has been ended."
                    , 1
                    , call2Listener.collectedEvents.size());

        collectedEvent
            = (CallEvent)call2Listener.collectedEvents.get(0);

        assertEquals("The basic telephony operation set did not distribute "
                     +"an event to notify us that a call has been ended."
                     , CallEvent.CALL_ENDED
                     , collectedEvent.getEventID());

        //make sure that the call objects are in an ENDED state.
        assertEquals("A call did not change its state to CallState.CALL_ENDED "
                     +"when it ended."
                     , CallState.CALL_ENDED
                     , callAtP1.getCallState());
        assertEquals("A call did not change its state to CallState.CALL_ENDED "
                     +"when it ended."
                     , CallState.CALL_ENDED
                     , callAtP2.getCallState());
    }

    /**
     * Creates a call from provider1 to provider2, makes provider2 answer it
     * and then reject it.
     *
     * @throws ParseException if we hand a malformed URI to someone
     * @throws OperationFailedException if something weird happens.
     */
    public void aTestCreateAnswerHangupCall()
        throws ParseException, OperationFailedException
    {
        OperationSetBasicTelephony<?> basicTelephonyP1
            = fixture.provider1.getOperationSet(
                OperationSetBasicTelephony.class);
        OperationSetBasicTelephony<?> basicTelephonyP2
            = fixture.provider2.getOperationSet(
                OperationSetBasicTelephony.class);

        CallEventCollector call1Listener
            = new CallEventCollector(basicTelephonyP1);
        CallEventCollector call2Listener
            = new CallEventCollector(basicTelephonyP2);

        //Provider1 calls Provider2
        String provider2Address
            = fixture.provider2.getAccountID().getAccountAddress();

        Call callAtP1 = basicTelephonyP1.createCall(provider2Address);

        call1Listener.waitForEvent(10000);
        call2Listener.waitForEvent(10000);

        //make sure that both listeners have received their events.
        assertEquals("The provider that created the call did not dispatch "
                     + "an event that it has done so."
                     , 1, call1Listener.collectedEvents.size());
        //call1 listener checks
        CallEvent callCreatedEvent
            = (CallEvent)call1Listener.collectedEvents.get(0);

        assertEquals("CallEvent.getEventID()"
                     ,CallEvent.CALL_INITIATED, callCreatedEvent.getEventID());
        assertSame("CallEvent.getSource()"
                     , callAtP1, callCreatedEvent.getSource());

        //call2 listener checks
        assertTrue("The callee provider did not receive a call or did "
                     +"not issue an event."
                     , call2Listener.collectedEvents.size() > 0);
        CallEvent callReceivedEvent
            = (CallEvent)call2Listener.collectedEvents.get(0);
        Call callAtP2 = callReceivedEvent.getSourceCall();

        assertEquals("CallEvent.getEventID()"
                     ,CallEvent.CALL_RECEIVED, callReceivedEvent.getEventID());
        assertNotNull("CallEvent.getSource()", callAtP2);

        //verify that call peers are properly created
        assertEquals("callAtP1.getCallpeersCount()"
                     , 1, callAtP1.getCallPeerCount());
        assertEquals("callAtP2.getCallpeersCount()"
                     , 1, callAtP2.getCallPeerCount());

        CallPeer peerAtP1
            = callAtP1.getCallPeers().next();
        CallPeer peerAtP2
            = callAtP2.getCallPeers().next();

        //now add listeners to the peers and make sure they have entered
        //the states they were expected to.
        //check states for call peers at both parties
        CallPeerStateEventCollector stateCollectorForPp1
            = new CallPeerStateEventCollector(
                peerAtP1, CallPeerState.ALERTING_REMOTE_SIDE);
        CallPeerStateEventCollector stateCollectorForPp2
            = new CallPeerStateEventCollector(
                peerAtP2, CallPeerState.INCOMING_CALL);

        stateCollectorForPp1.waitForEvent(10000, true);
        stateCollectorForPp2.waitForEvent(10000, true);

        assertSame("peerAtP1.getCall"
                   , peerAtP1.getCall(), callAtP1);
        assertSame("peerAtP2.getCall"
                   , peerAtP2.getCall(), callAtP2);

        //make sure that the peers are in the proper state
        assertEquals("The peer at provider one was not in the "
                     +"right state."
                    , CallPeerState.ALERTING_REMOTE_SIDE
                    , peerAtP1.getState());
        assertEquals("The peer at provider two was not in the "
                     +"right state."
                    , CallPeerState.INCOMING_CALL
                    , peerAtP2.getState());


        //test whether caller/callee info is properly distributed in case
        //the server is said to support it.
        if(Boolean.getBoolean("accounts.sip.PRESERVE_PEER_INFO"))
        {
            //check properties on the remote call peer for the party that
            //initiated the call.
            String expectedPeer1Address
                = fixture.provider2.getAccountID().getAccountAddress();
            String expectedPeer1DisplayName
                = System.getProperty(
                    SipProtocolProviderServiceLick.ACCOUNT_2_PREFIX
                    + ProtocolProviderFactory.DISPLAY_NAME);

            //do not asser equals here since one of the addresses may contain a
            //display name or something of the kind
            assertTrue("Provider 2 did not advertise their "
                       + "accountID.getAccoutAddress() address."
                       , expectedPeer1Address.indexOf(
                           peerAtP1.getAddress()) != -1
                       || peerAtP1.getAddress().indexOf(
                           expectedPeer1Address) != -1);
            assertEquals("Provider 2 did not properly advertise their "
                         + "display name."
                         , expectedPeer1DisplayName
                         , peerAtP1.getDisplayName());

            //check properties on the remote call peer for the party that
            //receives the call.
            String expectedPeer2Address
                = fixture.provider1.getAccountID().getAccountAddress();
            String expectedPeer2DisplayName
                = System.getProperty(
                    SipProtocolProviderServiceLick.ACCOUNT_1_PREFIX
                    + ProtocolProviderFactory.DISPLAY_NAME);

            //do not asser equals here since one of the addresses may contain a
            //display name or something of the kind
            assertTrue("Provider 1 did not advertise their "
                       + "accountID.getAccoutAddress() address."
                       , expectedPeer2Address.indexOf(
                           peerAtP2.getAddress()) != -1
                       || peerAtP2.getAddress().indexOf(
                           expectedPeer2Address) != -1);
            assertEquals("Provider 1 did not properly advertise their "
                         + "display name."
                         , expectedPeer2DisplayName
                         , peerAtP2.getDisplayName());
        }

        //add listeners to the peers and make sure enter
        //a connected state after we answer
        stateCollectorForPp1
            = new CallPeerStateEventCollector(
                peerAtP1, CallPeerState.CONNECTED);
        stateCollectorForPp2
            = new CallPeerStateEventCollector(
                peerAtP2, CallPeerState.CONNECTED);

        //we will now anser the call and verify that both parties change states
        //accordingly.
        basicTelephonyP2.answerCallPeer(peerAtP2);

        stateCollectorForPp1.waitForEvent(10000);
        stateCollectorForPp2.waitForEvent(10000);

        //make sure that the peers are in the proper state
        assertEquals("The peer at provider one was not in the "
                     +"right state."
                    , CallPeerState.CONNECTED
                    , peerAtP1.getState());
        assertEquals("The peer at provider two was not in the "
                     +"right state."
                    , CallPeerState.CONNECTED
                    , peerAtP2.getState());

        //make sure that events have been distributed when states were changed.
        assertEquals("No event was dispatched when a call peer changed "
                     +"its state."
                    , 1
                    , stateCollectorForPp1.collectedEvents.size());
        assertEquals("No event was dispatched when a call peer changed "
                     +"its state."
                     , 1
                     , stateCollectorForPp2.collectedEvents.size());

        //add listeners to the peers and make sure they have entered
        //the states they are expected to.
        stateCollectorForPp1
            = new CallPeerStateEventCollector(
                peerAtP1, CallPeerState.DISCONNECTED);
        stateCollectorForPp2
            = new CallPeerStateEventCollector(
                peerAtP2, CallPeerState.DISCONNECTED);

        //we will now end the call and verify that both parties change states
        //accordingly.
        basicTelephonyP2.hangupCallPeer(peerAtP2);

        stateCollectorForPp1.waitForEvent(10000);
        stateCollectorForPp2.waitForEvent(10000);

        //make sure that the peers are in the proper state
        assertEquals("The peer at provider one was not in the "
                     +"right state."
                    , CallPeerState.DISCONNECTED
                    , peerAtP1.getState());
        assertEquals("The peer at provider two was not in the "
                     +"right state."
                    , CallPeerState.DISCONNECTED
                    , peerAtP2.getState());

        //make sure that the corresponding events were delivered.
        assertEquals("a provider did not distribute an event when a call "
                     +"peer changed states."
                    , 1
                    , stateCollectorForPp1.collectedEvents.size());
        assertEquals("a provider did not distribute an event when a call "
                     +"peer changed states."
                    , 1
                    , stateCollectorForPp2.collectedEvents.size());

    }

    /**
     * Allows tests to wait for and collect events issued upon creation and
     * reception of calls.
     */
    public class CallEventCollector implements CallListener
    {
        public ArrayList<EventObject> collectedEvents = new ArrayList<EventObject>();
        public OperationSetBasicTelephony<?> listenedOpSet = null;

        /**
         * Creates an instance of this call event collector and registers it
         * with listenedOpSet.
         * @param listenedOpSet the operation set that we will be scanning for
         * new calls.
         */
        public CallEventCollector(OperationSetBasicTelephony<?> listenedOpSet)
        {
            this.listenedOpSet = listenedOpSet;
            this.listenedOpSet.addCallListener(this);
        }

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

            synchronized(this)
            {
                if(collectedEvents.size() > 0){
                    logger.trace("Event already received. " + collectedEvents);
                    listenedOpSet.removeCallListener(this);
                    return;
                }

                try{
                    wait(waitFor);
                    if(collectedEvents.size() > 0)
                        logger.trace("Received a CallEvent.");
                    else
                        logger.trace("No CallEvent received for "+waitFor+"ms.");

                    listenedOpSet.removeCallListener(this);
                }
                catch (InterruptedException ex)
                {
                    logger.debug(
                        "Interrupted while waiting for a call event", ex);
                }
            }
        }

        /**
         * Stores the received event and notifies all waiting on this object
         * @param event the event containing the source call.
         */
        public void incomingCallReceived(CallEvent event)
        {
            synchronized(this)
            {
                logger.debug(
                    "Collected evt("+collectedEvents.size()+")= "+event);

                this.collectedEvents.add(event);
                notifyAll();
            }
        }

        /**
         * Stores the received event and notifies all waiting on this object
         * @param event the event containing the source call.
         */
        public void outgoingCallCreated(CallEvent event)
        {
            synchronized(this)
            {
                logger.debug(
                    "Collected evt("+collectedEvents.size()+")= "+event);

                this.collectedEvents.add(event);
                notifyAll();
            }

        }

        /**
         * Stores the received event and notifies all waiting on this object
         * @param event the event containing the source call.
         */
        public void callEnded(CallEvent event)
        {
            synchronized(this)
            {
                logger.debug(
                    "Collected evt("+collectedEvents.size()+")= "+event);

                this.collectedEvents.add(event);
                notifyAll();
            }
        }
    }

    /**
     * Allows tests to wait for and collect events issued upon call peer
     * status changes.
     */
    public class CallPeerStateEventCollector
        extends CallPeerAdapter
    {
        public ArrayList<EventObject> collectedEvents = new ArrayList<EventObject>();
        private CallPeer listenedCallPeer = null;
        public CallPeerState awaitedState = null;

        /**
         * Creates an instance of this collector and adds it as a listener
         * to <tt>callPeer</tt>.
         * @param callPeer the CallPeer that we will be listening
         * to.
         * @param awaitedState the state that we will be waiting for inside
         * this collector.
         */
        public CallPeerStateEventCollector(
                                            CallPeer      callPeer,
                                            CallPeerState awaitedState)
        {
            this.listenedCallPeer = callPeer;
            this.listenedCallPeer.addCallPeerListener(this);
            this.awaitedState = awaitedState;
        }

        /**
         * Stores the received event and notifies all waiting on this object
         *
         * @param event the event containing the source call.
         */
        @Override
        public void peerStateChanged(CallPeerChangeEvent event)
        {
            synchronized(this)
            {
                logger.debug(
                    "Collected evt("+collectedEvents.size()+")= "+event);

                if(((CallPeerState)event.getNewValue())
                    .equals(awaitedState))
                {
                    this.collectedEvents.add(event);
                    notifyAll();
                }
            }
        }

        /**
         * Blocks until an event notifying us of the awaited state change is
         * received or until waitFor miliseconds pass (whichever happens first).
         *
         * @param waitFor the number of miliseconds that we should be waiting
         * for an event before simply bailing out.
         */
        public void waitForEvent(long waitFor)
        {
            waitForEvent(waitFor, false);
        }

        /**
         * Blocks until an event notifying us of the awaited state change is
         * received or until waitFor miliseconds pass (whichever happens first).
         *
         * @param waitFor the number of miliseconds that we should be waiting
         * for an event before simply bailing out.
         * @param exitIfAlreadyInState specifies whether the method is to return
         * if the call peer is already in such a state even if no event
         * has been received for the sate change.
         */
        public void waitForEvent(long waitFor, boolean exitIfAlreadyInState)
        {
            logger.trace("Waiting for a CallPeerEvent with newState="
                            + awaitedState + " for peer "
                            + this.listenedCallPeer);
            synchronized (this)
            {
                if(exitIfAlreadyInState
                   && listenedCallPeer.getState().equals(awaitedState))
                {
                    logger.trace("Src peer is already in the awaited "
                                 + "state."
                                 + collectedEvents);
                    listenedCallPeer.removeCallPeerListener(this);
                    return;
                }
                if(collectedEvents.size() > 0)
                {
                    CallPeerChangeEvent lastEvent
                        = (CallPeerChangeEvent) collectedEvents
                        .get(collectedEvents.size() - 1);

                    if (lastEvent.getNewValue().equals(awaitedState))
                    {
                        logger.trace("Event already received. " +
                                     collectedEvents);
                        listenedCallPeer
                            .removeCallPeerListener(this);
                        return;
                    }
                }
                try
                {
                    wait(waitFor);

                    if (collectedEvents.size() > 0)
                        logger.trace("Received a CallParticpantStateEvent.");
                    else
                        logger.trace("No CallParticpantStateEvent received for "
                                     + waitFor + "ms.");

                    listenedCallPeer
                        .removeCallPeerListener(this);
                }
                catch (InterruptedException ex)
                {
                    logger.debug("Interrupted while waiting for a "
                                 + "CallParticpantEvent"
                                 , ex);
                }
            }
        }
    }

    /**
     * Allows tests to wait for and collect events issued upon call state
     * changes.
     */
    public class CallStateEventCollector
        extends CallChangeAdapter
    {
        public ArrayList<EventObject> collectedEvents = new ArrayList<EventObject>();
        private Call listenedCall = null;
        public CallState awaitedState = null;

        /**
         * Creates an instance of this collector and adds it as a listener
         * to <tt>call</tt>.
         * @param call the Call that we will be listening to.
         * @param awaitedState the state that we will be waiting for inside
         * this collector.
         */
        public CallStateEventCollector(Call      call,
                                       CallState awaitedState)
        {
            this.listenedCall = call;
            this.listenedCall.addCallChangeListener(this);
            this.awaitedState = awaitedState;
        }

        /**
         * Stores the received event and notifies all waiting on this object
         *
         * @param event the event containing the source call.
         */
        @Override
        public void callStateChanged(CallChangeEvent event)
        {
            synchronized(this)
            {
                logger.debug(
                    "Collected evt("+collectedEvents.size()+")= "+event);

                if(((CallState)event.getNewValue()).equals(awaitedState))
                {
                    this.collectedEvents.add(event);
                    notifyAll();
                }
            }
        }

        /**
         * Blocks until an event notifying us of the awaited state change is
         * received or until waitFor miliseconds pass (whichever happens first).
         *
         * @param waitFor the number of miliseconds that we should be waiting
         * for an event before simply bailing out.
         */
        public void waitForEvent(long waitFor)
        {
            logger.trace("Waiting for a CallParticpantEvent");
            synchronized (this)
            {
                if (listenedCall.getCallState() == awaitedState)
                {
                    logger.trace("Event already received. " +
                                 collectedEvents);
                    listenedCall.removeCallChangeListener(this);
                    return;
                }

                try
                {
                    wait(waitFor);

                    if (collectedEvents.size() > 0)
                        logger.trace("Received a CallChangeEvent.");
                    else
                        logger.trace("No CallChangeEvent received for "
                                     + waitFor + "ms.");

                    listenedCall.removeCallChangeListener(this);
                }
                catch (InterruptedException ex)
                {
                    logger.debug("Interrupted while waiting for a "
                                 + "CallParticpantEvent"
                                 , ex);
                }
            }
        }
    }

}
