/// <summary>
        /// Saas application Joins the adhoc meeting as trusted entity, won't be seen by other conference participants
        /// </summary>
        /// <param name="loggingContext"><see cref="LoggingContext"/> to be used for logging all related events</param>
        /// <param name="callbackContext">A state/context object which will be provided by SfB in all related events</param>
        /// <returns><see cref="IOnlineMeetingInvitation"/> which can be used to wait for the meeting join to complete</returns>
        public async Task <IOnlineMeetingInvitation> JoinAdhocMeeting(LoggingContext loggingContext, string callbackContext)
        {
            string href = PlatformResource?.JoinAdhocMeetingLink?.Href;

            if (string.IsNullOrWhiteSpace(href))
            {
                throw new CapabilityNotAvailableException("Link to join adhoc meeting is not available.");
            }

            var callbackUrl = (Parent as Application).GetCustomizedCallbackUrl();

            if (callbackUrl != null && callbackContext != null)
            {
                // We need to append callbackContext as a query paramter to callbackUrl
                callbackUrl += callbackUrl.Contains("?") ? "&" : "?";
                callbackUrl += "callbackContext=" + callbackContext;

                // We don't want to pass callbackContext if callbackUrl is being passed
                callbackContext = null;
            }

            var joinMeetingInput = new JoinMeetingInvitationInput()
            {
                CallbackContext  = callbackContext,
                OperationContext = Guid.NewGuid().ToString(),
                CallbackUrl      = callbackUrl
            };

            var         communication = (Parent as Application).Communication as Communication;
            IInvitation invite        = null;

            //Adding current invitation to collection for tracking purpose.
            var tcs = new TaskCompletionSource <IInvitation>();

            communication.HandleNewInviteOperationKickedOff(joinMeetingInput.OperationContext, tcs);

            Logger.Instance.Information("Joining adhoc meeting " + href);

            var adhocMeetingUri = UriHelper.CreateAbsoluteUri(BaseUri, href);

            await PostRelatedPlatformResourceAsync(adhocMeetingUri, joinMeetingInput, new ResourceJsonMediaTypeFormatter(), loggingContext).ConfigureAwait(false);

            Task finishedTask = await Task.WhenAny(Task.Delay(WaitForEvents), tcs.Task).ConfigureAwait(false);

            if (finishedTask != tcs.Task)
            {
                throw new RemotePlatformServiceException("Timeout to get OnlinemeetingInvitation started event from platformservice");
            }
            else
            {
                invite = await tcs.Task.ConfigureAwait(false); // Incase need to throw exception
            }

            // We know for sure that the invitation is there now.
            IOnlineMeetingInvitation result = invite as IOnlineMeetingInvitation;

            if (result == null)
            {
                throw new RemotePlatformServiceException("Platformservice did not deliver a OnlinemeetingInvitation resource with operationId " + joinMeetingInput.OperationContext);
            }

            return(result);
        }
        private async Task StartInstantMessagingBridgeFlowAsync(IncomingInviteEventArgs <IMessagingInvitation> e)
        {
            Logger.Instance.Information(string.Format("[InstantMessagingBridgeFlow] StartInstantMessagingBridgeFlow: LoggingContext: {0}", LoggingContext));

            CallbackContext callbackcontext = new CallbackContext {
                InstanceId = this.InstanceId, JobId = this.JobId
            };
            string callbackContextJsonString = JsonConvert.SerializeObject(callbackcontext);
            string CallbackUrl = string.Format(CultureInfo.InvariantCulture, AzureApplication.CallbackUriFormat, HttpUtility.UrlEncode(callbackContextJsonString));

            string meetingUrl = string.Empty;

            //There will be two conversation legs for the saas app
            m_p2pConversation  = null;
            m_confConversation = null;

            #region Step 1 Start adhoc meeting
            //Step1:
            Logger.Instance.Information(string.Format("[InstantMessagingBridgeFlow] Step 1: Start adhoc meeting: LoggingContext: {0}", LoggingContext));

            IOnlineMeetingInvitation onlineMeetingInvite = await e.NewInvite.StartAdhocMeetingAsync(m_handleIncomingMessageInput.Subject, CallbackUrl, LoggingContext).ConfigureAwait(false);

            if (string.IsNullOrEmpty(onlineMeetingInvite.MeetingUrl))
            {
                throw new PlatformserviceApplicationException("Do not get valid MeetingUrl on onlineMeetingInvitation resource after startAdhocMeeting!");
            }
            meetingUrl = onlineMeetingInvite.MeetingUrl;

            Logger.Instance.Information(string.Format("[InstantMessagingBridgeFlow] Get meeting uri: {0} LoggingContext: {1}", onlineMeetingInvite.MeetingUrl, LoggingContext));

            //wait on embedded onlinemeetingInvitation to complete, so that we can have valid related conversation
            await onlineMeetingInvite.WaitForInviteCompleteAsync().ConfigureAwait(false);

            //this is conference conversation leg
            m_confConversation = onlineMeetingInvite.RelatedConversation;
            if (m_confConversation == null)
            {
                throw new PlatformserviceApplicationException("onlineMeetingInvite.RelatedConversation is null? this is propably app code bug!");
            }
            #endregion

            #region Step 2 add Messaging modality on conference conversation
            //Step2:
            Logger.Instance.Information(string.Format("[InstantMessagingBridgeFlow] Step2: add Messaging modality on conference conversation: LoggingContext: {0}", LoggingContext));
            IMessagingCall confMessaging = m_confConversation.MessagingCall;
            if (confMessaging == null)
            {
                throw new PlatformserviceApplicationException("[InstantMessagingBridgeFlow] No valid Messaging resource on conference conversation");
            }
            //Hook up the event handler on "MessagingModality" of the conference leg and make sure what ever message anon user , or agent input , the app can all know and note down
            confMessaging.IncomingMessageReceived      += OnIncomingMessageReceived;
            m_confConversation.HandleParticipantChange += this.OnParticipantChange;
            IMessagingInvitation messageInvitation = await confMessaging.EstablishAsync(LoggingContext).ConfigureAwait(false);

            await messageInvitation.WaitForInviteCompleteAsync().ConfigureAwait(false);//messageInvitation cannot be null here

            #endregion

            #region Step 3 Start AcceptAndBridge
            //Step3:
            Logger.Instance.Information(string.Format("[InstantMessagingBridgeFlow] Step3:  Start AcceptAndBridge: LoggingContext: {0}", LoggingContext));
            await e.NewInvite.AcceptAndBridgeAsync(LoggingContext, meetingUrl, m_handleIncomingMessageInput.Subject).ConfigureAwait(false);

            await e.NewInvite.WaitForInviteCompleteAsync().ConfigureAwait(false);

            m_p2pConversation = e.NewInvite.RelatedConversation;

            //This is to clean the conf conversation leg when the p2p conversation is removed
            m_p2pConversation.HandleResourceRemoved += (o, args) =>
            {
                m_p2pConversation.HandleResourceRemoved = null;
                this.OnClientChatDisconnected();
            };

            IMessagingCall p2pMessaging = m_p2pConversation.MessagingCall;
            if (p2pMessaging == null || p2pMessaging.State != Rtc.Internal.Platform.ResourceContract.CallState.Connected)
            {
                Logger.Instance.Error(string.Format("[InstantMessagingBridgeFlow] p2pMessaging is null or not in connected state: LoggingContext: {0}", LoggingContext));
                throw new PlatformserviceApplicationException("[InstantMessagingBridgeFlow] p2pMessaging is null or not in connected state");
            }
            #endregion

            #region Step 4 Send welcome message
            //Step4:
            Logger.Instance.Information(string.Format("[InstantMessagingBridgeFlow] Step4:  Send welcome message: LoggingContext: {0}", LoggingContext));
            await p2pMessaging.SendMessageAsync(m_handleIncomingMessageInput.WelcomeMessage, LoggingContext).ConfigureAwait(false);

            #endregion

            #region Step 5 Add bridged participant to enable agent send message to client chat
            //Step5:
            Logger.Instance.Information(string.Format("[InstantMessagingBridgeFlow] Step5: Add bridged participant to enable agent send message to client chat. LoggingContext: {0}", LoggingContext));
            IConversationBridge conversationBridge = m_p2pConversation.ConversationBridge;
            if (conversationBridge == null)
            {
                Logger.Instance.Error(string.Format("[InstantMessagingBridgeFlow] conversationBridge == null after accept and bridge. LoggingContext: {0}", LoggingContext));
                throw new PlatformserviceApplicationException("[InstantMessagingBridgeFlow]  conversationBridge == null after accept and bridge");
            }
            await conversationBridge.AddBridgedParticipantAsync(LoggingContext, m_handleIncomingMessageInput.InvitedTargetDisplayName, m_handleIncomingMessageInput.InviteTargetUri, false).ConfigureAwait(false);

            #endregion

            #region Step 6 Start addParticipant to conference
            //Step 6:
            Logger.Instance.Information(string.Format("[HandleIncomingMessageJob] Step5: Start addParticipant to conference: LoggingContext: {0}", LoggingContext));
            IParticipantInvitation participantInvitation = await m_confConversation.AddParticipantAsync(m_handleIncomingMessageInput.InviteTargetUri, LoggingContext).ConfigureAwait(false);

            await participantInvitation.WaitForInviteCompleteAsync().ConfigureAwait(false);

            #endregion
        }