///////////////////////////////////////////////////////////////////////////////
//// MultiChanBroadcastMediaListener.java
////Description:: MultiChanBroadcastMediaListener plays an announcement to the recipients, collects the DTMF digits and logs call details.
////
//// Copyright 2013 Avaya Inc. All rights reserved.
//// Usage of this source is bound to the terms described
//// in http://www.avaya.com/devconnect
//// Avaya - Confidential & Proprietary. Use pursuant to your signed agreement or Avaya Policy.
/////////////////////////////////////////////////////////////////////////////////

package com.avaya.zephyr.services.multichanbroadcast.announcementcall;

import java.net.URISyntaxException;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

import com.avaya.collaboration.call.Call;
import com.avaya.collaboration.call.CallFactory;
import com.avaya.collaboration.call.Participant;
import com.avaya.collaboration.call.media.DigitCollectorOperationCause;
import com.avaya.collaboration.call.media.DigitOptions;
import com.avaya.collaboration.call.media.MediaFactory;
import com.avaya.collaboration.call.media.MediaListenerAbstract;
import com.avaya.collaboration.call.media.MediaService;
import com.avaya.collaboration.call.media.PlayItem;
import com.avaya.collaboration.call.media.PlayOperationCause;
import com.avaya.collaboration.util.logger.Logger;

public class MultiChanBroadcastMediaListener extends MediaListenerAbstract
{

    private Logger logger;

    // Map used to map UUID from media responses to get related call.
    // This mapping is needed in order to tie the called party to the
    // DTMF acknowledgment when logging.
    private ConcurrentMap<String, String> uuidCallMap;
    // Map used to find the UUID when call listener passes in a call from
    // when terminating the call. Use the UUID to remove the entry from
    // the uuidCallMap.
    private ConcurrentMap<String, String> callUuidMap;
    private static final String ANNOUNCEMENT_CALL_ATTRIBUTE_FILE_URIS =
            "announcementFileUris";
    private static final String ANNOUNCEMENT_CALL_ATTRIBUTE_PROMPTANDCOLLECT_TIMEOUT =
            "announcementPromptAndCollectTimeout";
    private static final String CALL_ATTRIBUTE_DIGITSCOLLECTED = "digitsCollected";

    public MultiChanBroadcastMediaListener()
    {
        this.logger = Logger.getLogger(MultiChanBroadcastCallListener.class);
        this.uuidCallMap = new ConcurrentHashMap<String, String>();
        this.callUuidMap = new ConcurrentHashMap<String, String>();
    }

    // JUnit constructor
    public MultiChanBroadcastMediaListener(final Logger logger, final ConcurrentMap<String, String> uuidCallMap,
            final ConcurrentMap<String, String> callUuidMap)
    {
        this.logger = logger;
        this.uuidCallMap = uuidCallMap;
        this.callUuidMap = callUuidMap;
    }

    // Plays announcement to the recipient and collects the DTMF digits entered.
    public void playAnnouncementAndCollectDigits(final Call callToPlayAnnouncementTo)
    {
        try
        {
            final MediaService mediaService = MediaFactory.createMediaService();

            // Prepare a PlayItem for a media file URI which would be played
            // during a call.
            final PlayItem playItem = MediaFactory.createPlayItem();
            @SuppressWarnings("unchecked")
            final
            List<String> announcementFileUrisList = ((List<String>)callToPlayAnnouncementTo
                  .getAttribute(ANNOUNCEMENT_CALL_ATTRIBUTE_FILE_URIS));
            if (announcementFileUrisList != null)
            {
                playItem.setSource(announcementFileUrisList.toArray(new String[announcementFileUrisList.size()]));
            }
            else
            {
                logger.error("playAnnouncementAndCollectDigits: Null file URI");
                return;
            }
            playItem.setInterruptible(true);
            playItem.setIterateCount(1);

            // Prepare to collect the DTMF digits
            final DigitOptions digitOptions = MediaFactory.createDigitOptions();
            digitOptions.setNumberOfDigits(1);
            digitOptions.setTimeout((Integer) callToPlayAnnouncementTo
                    .getAttribute(ANNOUNCEMENT_CALL_ATTRIBUTE_PROMPTANDCOLLECT_TIMEOUT));

            final Participant answeringPartyParticipant = callToPlayAnnouncementTo.getAnsweringParty();
            final String answeringPartyAddress;
            if (answeringPartyParticipant == null)
            {
                answeringPartyAddress = "null";
            }
            else
            {
                answeringPartyAddress = answeringPartyParticipant.getAddress();
            }

            final String uuid = mediaService.promptAndCollect(answeringPartyParticipant, playItem,
                    digitOptions, this).toString();
            logger.fine("playAnnouncementAndCollectDigits: UUID: " + uuid + " AnsweringParty: " +
                    answeringPartyAddress);

            // Alternative could have been to call 'play', wait for play to
            // complete and then call 'collect' in the 'playCompleted' method
            // below
            //
            // uuid = mediaService.play(answeringParty, playItem, this);
            // logger.fine("PlayUUID:" + uuid + " - answeringParty:" +
            // answeringParty.getAddress());

            // Save UUID/CallId so that we can find the call when
            // processing media responses (playCompleted, DigitsCollected) and
            // only given UUID.
            uuidCallMap.put(uuid, callToPlayAnnouncementTo.getId());
            // Save CallId/UUID so that we can find the UUID when the call
            // listener gives us the call when terminating the call. We need the
            // UUID as the key to remove the entry from the UUID/CallId map.
            callUuidMap.put(callToPlayAnnouncementTo.getId(), uuid);
        }
        catch (final URISyntaxException e)
        {
            logger.error("playAnnouncementAndCollectDigits: Bad file URI ", e);
        }
    }

    // Log success or failure announcement for a call after play is completed
    @Override
    public void playCompleted(final UUID uuid, final PlayOperationCause cause)
    {
        if (uuid == null)
        {
            logger.error("playCompleted: UUID is null");
            return;
        }

        final String answeringPartyAddress;

        final String callId = uuidCallMap.get(uuid.toString());
        if (callId == null)
        {
            logger.error("playCompleted: UUID: " + uuid + " Could not find call");
            return;
        }

        final Call call = CallFactory.getCall(callId);
        if (call == null)
        {
            logger.error("playCompleted: UUID: " + uuid + " Could not find call");
            return;
        }

        final Participant answeringParty = call.getAnsweringParty();
        if (answeringParty == null)
        {
            logger.error("playCompleted: UUID: " + uuid + " answeringParty is null");
            return;
        }

        answeringPartyAddress = answeringParty.getAddress();
        if (answeringPartyAddress == null)
        {
            logger.error("playCompleted: UUID: " + uuid + " answeringPartyAddress is null");
            return;
        }

        logger.fine("playCompleted: UUID: " + uuid + " - answeringParty: " + answeringPartyAddress +
                " - with cause: " + cause);

        // Alternatively, if 'play' (instead of 'promptAndCollect') were called
        // in playAnnouncement then we could call 'collect' after the
        // announcement had finished playing here.
        //
        // final MediaService mediaService = MediaFactory.createMediaService();
        // DigitOptions digitOptions =
        // MediaFactory.createDigitOptions().setNumberOfDigits(1).setTimeout(30000);
        // uuid = mediaService.collect(calledParty, digitOptions, this);
        // logger.fine("DigitsCollectUUID:" + uuid + " - CalledParty:" +
        // calledParty.getAddress());
    }

    // Log the DTMF digits collected
    @Override
    public void digitsCollected(final UUID digitsCollectedUUID, final String digits,
            final DigitCollectorOperationCause cause)
    {
        if (digitsCollectedUUID == null)
        {
            logger.error("digitsCollected: UUID: is null");
            return;
        }

        final String uuid = digitsCollectedUUID.toString();
        logger.fine("digitsCollected: UUID: " + uuid);

        final String callId = uuidCallMap.get(uuid);

        // Find answeringPartyAddress
        final String callAnsweringPartyAddress;
        if (callId == null)
        {
            logger.error("digitsCollected: UUID: " + uuid + " Could not find callId");
            return;
        }

        final Call call = CallFactory.getCall(callId);
        if (call == null)
        {
            logger.error("digitsCollected: UUID: " + uuid + " Could not find call");
            return;
        }

        if (call.getAttribute(CALL_ATTRIBUTE_DIGITSCOLLECTED) != null &&
                (Boolean) call.getAttribute(CALL_ATTRIBUTE_DIGITSCOLLECTED))
        {
            logger.error("digitsCollected: UUID: " + uuid + " Digits already collected for this call");
            return;
        }

        final Participant answering = call.getAnsweringParty();
        if (answering == null)
        {
            logger.error("digitsCollected: UUID: " + uuid + " Could not find answeringParty");
            return;
        }

        callAnsweringPartyAddress = answering.getAddress();

        logger.fine("digitsCollected: UUID: " + uuid + " answeringParty: " + callAnsweringPartyAddress);

        // Log DTMF acknowledgment
        if ((digits != null) && (digits.length() > 0))
        {
            call.setAttribute(CALL_ATTRIBUTE_DIGITSCOLLECTED, true);

            final String digitsCollectedDtmfAckLog =
                    "digitsCollected: UUID: " + uuid + " For call to " + callAnsweringPartyAddress +
                            " acknowledged with DTMF - digit " + digits;

            logger.info(digitsCollectedDtmfAckLog);
        }
        else
        {
            call.setAttribute(CALL_ATTRIBUTE_DIGITSCOLLECTED, false);

            logger.info("digitCollected: UUID: " + uuid + " Timer expire: Call to " +
                    callAnsweringPartyAddress +
                    " NOT acknowledged with DTMF");
        }

        // Application could choose to drop the call after receiving all the
        // digits it expected
        // callDigitsCollected.drop();
    }

    public synchronized void callTerminated(final Call terminatedCall)
    {
        if (terminatedCall == null)
        {
            logger.error("callTerminated: TerminatedCall is null");
            return;
        }

        // Get the calledPartyAddress of the terminatedCall
        final Participant calledPartyParticipant = terminatedCall.getCalledParty();
        String calledPartyAddress;
        if (calledPartyParticipant == null)
        {
            logger.error("callTerminated: Could not find calledParty for terminatedCall");
            calledPartyAddress = "null";
        }
        else
        {
            calledPartyAddress = calledPartyParticipant.getAddress();
        }

        // Remove Call/UUID entry
        logger.fine("callTerminated: Removing CallId: " + terminatedCall.getId() + " for calledParty: " +
                calledPartyAddress);
        final String uuid = callUuidMap.remove(terminatedCall.getId());
        if (uuid == null)
        {
            logger.error("callTerminated: Could not find UUID for calledParty: " + calledPartyAddress);
            return;
        }

        // Remove UUID/Call entry
        logger.fine("callTerminated: Removing UUID: " + uuid + " for calledParty: " + calledPartyAddress);
        final String removedCall = uuidCallMap.remove(uuid);
        if (removedCall == null)
        {
            logger.error("callTerminated: Could not find callId for UUID: " + uuid + " calledParty: " +
                    calledPartyAddress);
        }
    }
}
