using System;
using System.Collections.Generic;
using System.Threading;
using Ozeki.Media;
using Ozeki.VoIP;

namespace _09_PredictiveDialer
{
    internal class CallHandler
    {
        private Softphone _softphone;
        private CallInfo _callInfo;
        private IPhoneCall _call;
        private IPhoneCall _agentCall;

        private MediaConnector _connectorFromAgent;
        private MediaConnector _connectorFromClient;
        private PhoneCallAudioSender _mediaSenderToAgent;
        private PhoneCallAudioSender _mediaSenderToClient;
        private PhoneCallAudioReceiver _mediaReceiverFromAgent;
        private PhoneCallAudioReceiver _mediaReceiverFromClient;

        private AutoResetEvent _autoResetEvent;

        private List<IPhoneCall> _freeAgentChecks;

        private bool _needToHangUp;

        private int _freeAgents;

        private object _sync;
        private ConfigStore _configStore;

        public CallHandler(Softphone softphone, CallInfo callInfo)
        {
            _softphone = softphone;
            _callInfo = callInfo;
            _configStore = ConfigStore.Instance;

            _connectorFromAgent = new MediaConnector();
            _connectorFromClient = new MediaConnector();
            _mediaSenderToAgent = new PhoneCallAudioSender();
            _mediaSenderToClient = new PhoneCallAudioSender();
            _mediaReceiverFromAgent = new PhoneCallAudioReceiver();
            _mediaReceiverFromClient = new PhoneCallAudioReceiver();

            _freeAgentChecks = new List<IPhoneCall>();

            _autoResetEvent = new AutoResetEvent(false);

            _needToHangUp = true;

            _sync = new object();
        }

        public void Start()
        {
            lock (_sync)
            {
                _call = _softphone.CreateCall(_callInfo.PhoneNumber);
                _call.CallStateChanged += ClientCallStateChanged;
                _mediaReceiverFromClient.AttachToCall(_call);
                _mediaSenderToAgent.AttachToCall(_call);
                _connectorFromClient.Connect(_mediaReceiverFromClient, _mediaSenderToAgent);
                _call.Start();
            }
        }

        public event EventHandler Completed;
        public event EventHandler ReadyToCall;

        private void CallRingGroup()
        {
            if (_configStore.GetFreeAgents().Count > 0)
            {
                CheckAgents();
            }
            else
            {
                _autoResetEvent.WaitOne();
                CheckAgents();
            }
        }

        private void CheckAgents()
        {
            lock (_sync)
            {
                _freeAgents = 0;
                foreach (var freeAgent in _configStore.GetFreeAgents())
                {
                    var callAgent = _softphone.CreateCall(freeAgent);
                    callAgent.CallStateChanged += AgentCallStateChanged;
                    _freeAgentChecks.Add(callAgent);
                    _freeAgents++;
                }

                foreach (var freeAgentCheck in _freeAgentChecks)
                {
                    freeAgentCheck.Start();
                }
            }
        }

        private void SetupAgentDevices(IPhoneCall call)
        {
            lock (_sync)
            {
                _agentCall = call;
                _mediaReceiverFromAgent.AttachToCall(call);
                _mediaSenderToClient.AttachToCall(call);
                _connectorFromAgent.Connect(_mediaReceiverFromAgent, _mediaSenderToClient);
            }
        }

        private void DestructClientDevices()
        {
            lock (_sync)
            {
                _call.CallStateChanged -= ClientCallStateChanged;
                _mediaReceiverFromClient.Detach();
                _mediaSenderToAgent.Detach();
                _connectorFromClient.Dispose();
                _call = null;
            }
        }

        private void DestructAgentDevices(IPhoneCall call)
        {
            lock (_sync)
            {
                call.CallStateChanged -= AgentCallStateChanged;
                call.HangUp();

                if (_agentCall == call)
                {
                    _mediaReceiverFromAgent.Detach();
                    _mediaSenderToClient.Detach();
                    _connectorFromAgent.Dispose();
                }
            }
        }

        private void ClientCallStateChanged(object sender, CallStateChangedArgs e)
        {
            if (e.State == CallState.Answered)
            {
                lock (_sync)
                {
                    CallRingGroup();
                }
            }
            else if (e.State == CallState.RemoteHeld && _agentCall != null)
            {
                _agentCall.Hold();
            }
            else if (e.State.IsInCall() && _agentCall != null)
            {
                if (_agentCall.CallState == CallState.LocalHeld || _agentCall.CallState == CallState.InactiveHeld)
                    _agentCall.Unhold();
            }
            else if (e.State.IsCallEnded())
            {
                lock (_sync)
                {
                    DestructClientDevices();

                    if (_needToHangUp && _agentCall != null)
                    {
                        _needToHangUp = false;
                        _agentCall.HangUp();
                    }

                    var handler = Completed;
                    if (handler != null)
                        handler(this, EventArgs.Empty);

                    HangUpAgents();
                }
            }
        }

        private void AgentCallStateChanged(object sender, CallStateChangedArgs e)
        {
            var currentCall = (IPhoneCall) sender;

            if (e.State == CallState.Answered)
            {
                lock (_sync)
                {
                    SetupAgentDevices(currentCall);
                    _agentCall = currentCall;

                    _configStore.RemoveFreeAgent(currentCall.DialInfo.Dialed);
                    _freeAgentChecks.Remove(currentCall);

                    HangUpAgents();

                    var handler = ReadyToCall;
                    if (handler != null)
                        handler(this, EventArgs.Empty);
                }
            }
            else if (e.State == CallState.RemoteHeld)
            {
                _call.Hold();
            }
            else if (e.State.IsInCall())
            {
                if (_call.CallState == CallState.LocalHeld || _call.CallState == CallState.InactiveHeld)
                    _call.Unhold();
            }
            else if (e.State.IsCallEnded())
            {
                lock (_sync)
                {
                    _freeAgents--;

                    if (!_configStore.GetFreeAgents().Contains(currentCall.DialInfo.Dialed))
                    {
                        if (_needToHangUp)
                        {
                            _needToHangUp = false;
                            _call.HangUp();
                        }
                        _configStore.AddFreeAgent(currentCall.DialInfo.Dialed);
                        _autoResetEvent.Set();
                    }
                    DestructAgentDevices(currentCall);

                    if (_freeAgents == 0)
                    {
                        HangUpClient();
                        var handler = ReadyToCall;
                        if (handler != null)
                            handler(this, EventArgs.Empty);
                    }
                }
            }
        }

        private void HangUpClient()
        {
            if (_needToHangUp)
            {
                _needToHangUp = false;
                _call.HangUp();
            }
        }

        private void HangUpAgents()
        {
            lock (_sync)
            {
                foreach (var checkedAgent in _freeAgentChecks)
                {
                    checkedAgent.HangUp();
                    DestructAgentDevices(checkedAgent);
                }
                _freeAgentChecks.Clear();
            }
        }
    }
}