// Copyright © 2015 Genesys. All Rights Reserved.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Genesyslab.Desktop.Infrastructure.DependencyInjection;
using Genesyslab.Desktop.Modules.Core.SDK.Protocol;
using Genesyslab.Platform.ApplicationBlocks.Commons.Broker;
using Genesyslab.Platform.ApplicationBlocks.Commons.Protocols;
using Genesyslab.Platform.Commons.Protocols;
using Genesyslab.Platform.Commons.Logging;
using Genesyslab.Platform.Voice.Protocols.TServer.Requests.Dn;
using Genesyslab.Platform.Voice.Protocols;
using Genesyslab.Platform.Voice.Protocols.TServer.Events;
using Genesyslab.Platform.Commons.Collections;
using Genesyslab.Desktop.Modules.Gms.CallbackInvitation.Generic;
using System.Windows;
using System.Windows.Threading;
using Genesyslab.Desktop.Infrastructure.Commands;
using Genesyslab.Desktop.Modules.Voice.Model.Agents;
using Genesyslab.Desktop.Modules.Gms.CallbackInvitation.CallbackDisposition;

namespace Genesyslab.Desktop.Modules.Gms.CallbackInvitation.CallbackInvitation {
    class UserEventListener: IUserEventListener {
        private readonly IObjectContainer container = null;
        private readonly ICfgReader config = null;
        private readonly ILogger log = null;

        private String _agentDN = "";
        private IMediaVoice _mediaVoice = null;

        private const String clientName = "Workspace_CallbackInvitation";
        private const int eventUserEventId = 96;
        private const int releaseEventId = 65;
        private const String eventExtensionSourceKey = "source";
        private const String eventExtensionEventKey = "event";
        private const String source = "ORS";    // Source of the event
        private const String eventCallbackInvitationEventName = "CallbackInvitationEvent";
        private const String eventCallbackDispositionEventName = "CallbackDispositionEvent";
        private const String eventExtensionInvitationTimeoutKey = "invitation-timeout";
        private const String eventExtensionDispositionTimeoutKey = "disposition-timeout";
        private const String eventUserDisplayDataKey = "display-data";
        private const String eventUserResponseOptionsKey = "response-options";
        private const String eventUserConfigKey = "config";
        private const String configWorkspaceDetectsCallEndKey = "_workspace_detects_call_end";
        private const int defaultTimeout = 30;

        // Saved data for deferred dispositin dialog
        private int dispositionTimeout = 0;
        KeyValueCollection dispositionDisplayData = null;
        KeyValueCollection dispositionResponseOptions = null;
        KeyValueCollection dispositionConfig = null;

        public UserEventListener(IObjectContainer container) {
            this.container = container;
            this.config = this.container.Resolve<ICfgReader>();
            this.log = this.container.Resolve<ILogger>().CreateChildLogger("UserEventListener");
        }

        private void EventHandlerTServerReceived(IMessage message)
        {
            if (message != null)
            {
                if (message.Id.Equals(eventUserEventId))
                {
                    EventUserEvent userEventData = message as EventUserEvent;

                    // Determine if to continue parsing of this UserEvent (depends on AttributeExtensions and AttributeUserData set)
                    // event user contains relevant keys
                    if (userEventData.Extensions != null && userEventData.Extensions.ContainsKey(eventExtensionSourceKey) && userEventData.Extensions.ContainsKey(eventExtensionEventKey)
                        && userEventData.UserData != null && userEventData.UserData.ContainsKey(eventUserDisplayDataKey) && userEventData.UserData.ContainsKey(eventUserResponseOptionsKey))
                    {
                        // check source is ORS ?
                        if (userEventData.Extensions[eventExtensionSourceKey].Equals(source))
                        {
                            this.log.Info(String.Format("{0} Processing event [{1}] with contents: {2}", GConst.LOG_PREFIX, message.Name, message.ToString()));

                            // common values
                            // Retrieve the relevant information of the UserEvent
                            KeyValueCollection displayData = userEventData.UserData.GetAsKeyValueCollection(eventUserDisplayDataKey);
                            KeyValueCollection responseOptions = userEventData.UserData.GetAsKeyValueCollection(eventUserResponseOptionsKey);
                            KeyValueCollection config = userEventData.UserData.GetAsKeyValueCollection(eventUserConfigKey);

                            // check eventCustomName is "CallbackInvitationEvent"
                            if (userEventData.Extensions[eventExtensionEventKey].Equals(eventCallbackInvitationEventName))
                            {
                                // retrieve timeout from event or set default value
                                int invitationTimeout = (userEventData.Extensions.ContainsKey(eventExtensionInvitationTimeoutKey)) ?
                                                        Int16.Parse(userEventData.Extensions.GetAsString(eventExtensionInvitationTimeoutKey)) : defaultTimeout;
                                // The InvitationDialog can only be invoked on the main thread
                                Application.Current.Dispatcher.Invoke((Action)delegate
                                {
                                    InvitationDialog invitationDialog = new InvitationDialog(this.container, displayData, responseOptions, invitationTimeout);
                                    bool? dialogResult = invitationDialog.ShowDialog();
                                });
                            }
                            else if (userEventData.Extensions[eventExtensionEventKey].Equals(eventCallbackDispositionEventName))
                            {
                                // retrieve timeout from event or set default value
                                dispositionTimeout = (userEventData.Extensions.ContainsKey(eventExtensionDispositionTimeoutKey)) ?
                                                        Int16.Parse(userEventData.Extensions.GetAsString(eventExtensionDispositionTimeoutKey)) : defaultTimeout;
                                string workspaceDetectsCallEnd = config.GetAsString(configWorkspaceDetectsCallEndKey);
                                if (workspaceDetectsCallEnd != null && workspaceDetectsCallEnd.Equals("true"))
                                {
                                    // Defer displaying the dialog until EventReleased occurs
                                    dispositionDisplayData = displayData;
                                    dispositionResponseOptions = responseOptions;
                                    dispositionConfig = config;
                                }
                                else
                                {
                                    // The DispositionDialog can only be invoked on the main thread
                                    Application.Current.Dispatcher.Invoke((Action)delegate
                                    {
                                        DispositionDialog dispositionDialog = new DispositionDialog(this.container, displayData, responseOptions, config, dispositionTimeout);
                                        bool? dialogResult = dispositionDialog.ShowDialog();
                                    });
                                }
                            }
                            else
                            {
                                this.log.Debug(String.Format("{0} Ignoring event, not yet implemented [{1}]", GConst.LOG_PREFIX, message.Name));
                            }
                        }
                        else
                        {
                            this.log.Debug(String.Format("{0} Ignoring event from wrong source [{1}]", GConst.LOG_PREFIX, message.Name));
                        }
                    }
                    else {
                        this.log.Debug(String.Format("{0} Ignoring non-relevant UserEvent (doesn't have the necessary AttributeExtensions / AttributeUserData)", GConst.LOG_PREFIX));
                    }
                }
                else if (message.Id.Equals(releaseEventId))
                {
                    EventReleased releasedEvent = message as EventReleased;
                    this.log.Info(String.Format("{0} Processing event [{1}] with contents: {2}", GConst.LOG_PREFIX, message.Name, message.ToString()));
                    if (dispositionDisplayData != null && releasedEvent.CallType.Equals(Genesyslab.Platform.Voice.Protocols.TServer.CallType.Outbound))
                    {
                        // Dispatch the deferred disposition dialog on the main thread
                        Application.Current.Dispatcher.Invoke((Action)delegate
                        {
                            DispositionDialog dispositionDialog = new DispositionDialog(this.container, dispositionDisplayData, dispositionResponseOptions,
                                dispositionConfig, dispositionTimeout);
                            bool? dialogResult = dispositionDialog.ShowDialog();
                            dispositionDisplayData = null;
                            dispositionResponseOptions = null;
                            dispositionConfig = null;
                        });
                    }
                }
                else
                {
                    this.log.Debug(String.Format("{0} Ignoring event [{1}]", GConst.LOG_PREFIX, message.Name));
                }
            }
        }

        public void SetupHandler()
        {
            this.log.Debug(String.Format("{0} Setting up connection to T-Server", GConst.LOG_PREFIX));

            if (this._mediaVoice != null)
            {
                // listen to all events from media voice
                _mediaVoice.Channel.RawSubscriber.Register(EventHandlerTServerReceived);
                this.log.Debug(String.Format("{0} Registered for events sent to DN [{1}]", GConst.LOG_PREFIX, this._agentDN));
            }
            else
                throw new Exception("T-Server connection cannot be set up: no Media Voice DN has been set");
        }

        public void RemoveHandler()
        {
            this.log.Debug(String.Format("{0} Remove connection to T-Server", GConst.LOG_PREFIX));

            if (this._mediaVoice != null)
            {
                // remove listener
                _mediaVoice.Channel.RawSubscriber.Unregister(EventHandlerTServerReceived);
                this.log.Debug(String.Format("{0} Un-Registered for events sent to DN [{1}]", GConst.LOG_PREFIX, this._agentDN));
            }
            else
                throw new Exception("T-Server connection cannot be set up: no Media Voice DN has been set");
        }

        public String AgentDN {
            get { return this._agentDN; }
            set { if (this._agentDN != value) { this._agentDN = value; } }
        }

        public IMediaVoice MediaVoice
        {
            get { return this._mediaVoice; }
            set { if (this._mediaVoice != value) { this._mediaVoice = value; } }
        }

        /// <summary>
        /// This delegate allows to go to the main thread.
        /// </summary>
        delegate bool ExecuteDelegate(IDictionary<string, object> parameters, IProgressUpdater progressUpdater);
    }
}
