private void OnCallingEvent_Receive(Client client, BroadcastParams broadcastParams, CallingEventParams callEventParams)
        {
            CallingEventParams.ReceiveParams receiveParams = null;
            try { receiveParams = callEventParams.ParametersAs <CallingEventParams.ReceiveParams>(); }
            catch (Exception exc)
            {
                mLogger.LogWarning(exc, "Failed to parse ReceiveParams");
                return;
            }

            // @note A received call should only ever receive one receive event regardless of the state of the call, but we act as though
            // we could receive multiple just for sanity here, but the callbacks will still only be called when first created, which makes
            // this effectively a no-op on additional receive events
            Call call = null;
            Call tmp  = null;

            switch (receiveParams.Device.Type)
            {
            case CallDevice.DeviceType.phone:
            {
                CallDevice.PhoneParams phoneParams = null;
                try { phoneParams = receiveParams.Device.ParametersAs <CallDevice.PhoneParams>(); }
                catch (Exception exc)
                {
                    mLogger.LogWarning(exc, "Failed to parse PhoneParams");
                    return;
                }

                // If the call already exists under the real call id simply obtain the call, otherwise create a new call
                call = mCalls.GetOrAdd(receiveParams.CallID, k => (tmp = new PhoneCall(this, receiveParams.NodeID, receiveParams.CallID)
                    {
                        To = phoneParams.ToNumber,
                        From = phoneParams.FromNumber,
                        Timeout = phoneParams.Timeout,
                    }));
                break;
            }

            // @TODO: sip and webrtc
            default:
                mLogger.LogWarning("Unknown device type: {0}", receiveParams.Device.Type);
                return;
            }

            if (tmp == call)
            {
                call.Context = receiveParams.Context;

                OnCallCreated?.Invoke(this, call);
                OnCallReceived?.Invoke(this, call, receiveParams);
            }

            call.ReceiveHandler(callEventParams, receiveParams);
        }
        private void OnCallingEvent_SendDigits(Client client, BroadcastParams broadcastParams, CallingEventParams callEventParams)
        {
            CallingEventParams.SendDigitsParams sendDigitsParams = null;
            try { sendDigitsParams = callEventParams.ParametersAs <CallingEventParams.SendDigitsParams>(); }
            catch (Exception exc)
            {
                Log(LogLevel.Warning, exc, "Failed to parse SendDigitsParams");
                return;
            }
            if (!mCalls.TryGetValue(sendDigitsParams.CallID, out Call call))
            {
                Log(LogLevel.Warning, string.Format("Received SendDigitsParams with unknown CallID: {0}", sendDigitsParams.CallID));
                return;
            }

            call.SendDigitsStateChangeHandler(callEventParams, sendDigitsParams);
        }
        private void OnCallingEvent_Fax(Client client, BroadcastParams broadcastParams, CallingEventParams callEventParams)
        {
            CallingEventParams.FaxParams faxParams = null;
            try { faxParams = callEventParams.ParametersAs <CallingEventParams.FaxParams>(); }
            catch (Exception exc)
            {
                Log(LogLevel.Warning, exc, "Failed to parse FaxParams");
                return;
            }
            if (!mCalls.TryGetValue(faxParams.CallID, out Call call))
            {
                Log(LogLevel.Warning, string.Format("Received FaxParams with unknown CallID: {0}", faxParams.CallID));
                return;
            }

            call.FaxHandler(callEventParams, faxParams);
        }
        private void OnCallingEvent_Connect(Client client, BroadcastParams broadcastParams, CallingEventParams callEventParams)
        {
            CallingEventParams.ConnectParams connectParams = null;
            try { connectParams = callEventParams.ParametersAs <CallingEventParams.ConnectParams>(); }
            catch (Exception exc)
            {
                Log(LogLevel.Warning, exc, "Failed to parse ConnectParams");
                return;
            }
            if (!mCalls.TryGetValue(connectParams.CallID, out Call call))
            {
                Log(LogLevel.Warning, string.Format("Received ConnectParams with unknown CallID: {0}, {1}", connectParams.CallID, connectParams.State));
                return;
            }

            call.ConnectHandler(callEventParams, connectParams);
        }
        private void OnCallingEvent_Detect(Client client, BroadcastParams broadcastParams, CallingEventParams callEventParams)
        {
            CallingEventParams.DetectParams detectParams = null;
            try { detectParams = callEventParams.ParametersAs <CallingEventParams.DetectParams>(); }
            catch (Exception exc)
            {
                mLogger.LogWarning(exc, "Failed to parse DetectParams");
                return;
            }
            if (!mCalls.TryGetValue(detectParams.CallID, out Call call))
            {
                mLogger.LogWarning("Received DetectParams with unknown CallID: {0}", detectParams.CallID);
                return;
            }

            call.DetectHandler(callEventParams, detectParams);
        }
        private void OnCallingEvent_Dial(Client client, BroadcastParams broadcastParams, CallingEventParams callEventParams)
        {
            CallingEventParams.DialParams dialParams = null;
            try { dialParams = callEventParams.ParametersAs <CallingEventParams.DialParams>(); }
            catch (Exception exc)
            {
                Log(LogLevel.Warning, exc, "Failed to parse DialParams");
                return;
            }

            if (!mCalls.TryGetValue(dialParams.Tag, out Call call))
            {
                Log(LogLevel.Warning, string.Format("Received DialParams with unknown Tag: {0}, {1}", dialParams.Tag, dialParams.State));
                return;
            }

            call.DialHandler(callEventParams, dialParams);
        }
        private void OnCallingEvent_State(Client client, BroadcastParams broadcastParams, CallingEventParams callEventParams)
        {
            CallingEventParams.StateParams stateParams = null;
            try { stateParams = callEventParams.ParametersAs <CallingEventParams.StateParams>(); }
            catch (Exception exc)
            {
                Log(LogLevel.Warning, exc, "Failed to parse StateParams");
                return;
            }

            Call call = null;
            Call tmp  = null;

            switch (stateParams.Device.Type)
            {
            case CallDevice.DeviceType.phone:
            {
                CallDevice.PhoneParams phoneParams = null;
                try { phoneParams = stateParams.Device.ParametersAs <CallDevice.PhoneParams>(); }
                catch (Exception exc)
                {
                    Log(LogLevel.Warning, exc, "Failed to parse PhoneParams");
                    return;
                }

                // If the call already exists under the real call id simply obtain the call, however if the call was found under
                // a temporary call id then readd it here under the real call id, otherwise create a new call
                call = mCalls.GetOrAdd(stateParams.CallID, k => call ?? (tmp = new PhoneCall(this, stateParams.NodeID, stateParams.CallID)
                    {
                        To = phoneParams.ToNumber,
                        From = phoneParams.FromNumber,
                        Timeout = phoneParams.Timeout,
                        // Capture the state, it may not always be created the first time we see the call
                        State = stateParams.CallState,
                    }));
                break;
            }

            case CallDevice.DeviceType.sip:
            {
                CallDevice.SipParams sipParams = null;
                try { sipParams = stateParams.Device.ParametersAs <CallDevice.SipParams>(); }
                catch (Exception exc)
                {
                    Log(LogLevel.Warning, exc, "Failed to parse SipParams");
                    return;
                }

                // If the call already exists under the real call id simply obtain the call, however if the call was found under
                // a temporary call id then readd it here under the real call id, otherwise create a new call
                call = mCalls.GetOrAdd(stateParams.CallID, k => call ?? (tmp = new SipCall(this, stateParams.NodeID, stateParams.CallID)
                    {
                        To = sipParams.To,
                        From = sipParams.From,
                        FromName = sipParams.FromName,
                        Headers = sipParams.Headers,

                        // Capture the state, it may not always be created the first time we see the call
                        State = stateParams.CallState,
                    }));
                break;
            }

            // @TODO: webrtc
            default:
                Log(LogLevel.Warning, string.Format("Unknown device type: {0}", stateParams.Device.Type));
                return;
            }

            if (tmp == call)
            {
                OnCallCreated?.Invoke(this, call);
            }

            call.StateChangeHandler(callEventParams, stateParams);
        }
        private void OnCallingEvent_State(Client client, BroadcastParams broadcastParams, CallingEventParams callEventParams)
        {
            CallingEventParams.StateParams stateParams = null;
            try { stateParams = callEventParams.ParametersAs <CallingEventParams.StateParams>(); }
            catch (Exception exc)
            {
                mLogger.LogWarning(exc, "Failed to parse StateParams");
                return;
            }

            Call call = null;

            if (!string.IsNullOrWhiteSpace(stateParams.TemporaryCallID))
            {
                // Remove the call keyed by the temporary call id if it exists
                if (mCalls.TryRemove(stateParams.TemporaryCallID, out call))
                {
                    // Update the internal details for the call, including the real call id
                    call.NodeID = stateParams.NodeID;
                    call.ID     = stateParams.CallID;
                }
            }
            // If call is not null at this point, it means this is the first event for a call that was started with a temporary call id
            // and the call should be readded under the real call id

            Call tmp = null;

            switch (stateParams.Device.Type)
            {
            case CallDevice.DeviceType.phone:
            {
                CallDevice.PhoneParams phoneParams = null;
                try { phoneParams = stateParams.Device.ParametersAs <CallDevice.PhoneParams>(); }
                catch (Exception exc)
                {
                    mLogger.LogWarning(exc, "Failed to parse PhoneParams");
                    return;
                }

                // If the call already exists under the real call id simply obtain the call, however if the call was found under
                // a temporary call id then readd it here under the real call id, otherwise create a new call
                call = mCalls.GetOrAdd(stateParams.CallID, k => call ?? (tmp = new PhoneCall(this, stateParams.NodeID, stateParams.CallID)
                    {
                        To = phoneParams.ToNumber,
                        From = phoneParams.FromNumber,
                        Timeout = phoneParams.Timeout,
                        // Capture the state, it may not always be created the first time we see the call
                        State = stateParams.CallState,
                    }));
                break;
            }

            // @TODO: sip and webrtc
            default:
                mLogger.LogWarning("Unknown device type: {0}", stateParams.Device.Type);
                return;
            }

            if (tmp == call)
            {
                OnCallCreated?.Invoke(this, call);
            }

            call.StateChangeHandler(callEventParams, stateParams);
        }