// Copyright © 2015 Genesys. All Rights Reserved.
using System;
using System.IO;
using System.Linq;
using Genesyslab.Desktop.Infrastructure.Configuration;
using Genesyslab.Desktop.Infrastructure.DependencyInjection;
using Genesyslab.Desktop.Modules.Core.SDK.Configurations;
using Genesyslab.Platform.ApplicationBlocks.ConfigurationObjectModel.CfgObjects;
using Genesyslab.Platform.Commons.Collections;
using Genesyslab.Platform.Commons.Logging;
using Genesyslab.Platform.Commons.Protocols;
using Genesyslab.Platform.Configuration.Protocols.Types;

namespace Genesyslab.Desktop.Modules.Gms.CallbackInvitation.Generic
{
    /// <summary>
    /// Public class which retrieves all custom configuration for the customization from Configuration Layer
    /// </summary>
    public class CfgReader: ICfgReader
    {
        private readonly IObjectContainer container = null;
        private readonly IConfigManager config = null;
        private readonly IConfigurationService configurationService = null;

        private Endpoint _tserverEndpoint = null;

        // Variables for storing option values
        private KeyValueCollection configOptions = new KeyValueCollection();

        // Variables for logging        
        private readonly ILogger log = null;

        // Configuration option elements (prefix, callback-preview, and are concatenated to form full option names)
        private const string CFG_SECTION_NAME = "interaction-workspace";
        private const string CFG_MAIN_PREFIX = "callback-preview";

        public CfgReader(IObjectContainer container)
        {
            this.container = container;
            this.config = container.Resolve<IConfigManager>();
            this.configurationService = this.container.Resolve<IConfigurationService>();

            // Initialize logging
            log = container.Resolve<ILogger>();
            log = log.CreateChildLogger("CfgReader");

            ReadFullConfiguration();
            ReadTServerConnData();
        }

        #region Validation Predicate functions
        static bool ValidateBoolean(string value)
        {
            value = value.ToLower();
            return ((value == Boolean.FalseString.ToLower()) || (value == Boolean.TrueString.ToLower()));
        }
        #endregion

        #region Individual Option Reader Methods
        private void ReadIWOption(String keyName, String defaultValue, Predicate<String> validateFunc)
        {
            String configValue = (string)config.GetValue(keyName);
            if (String.IsNullOrEmpty(configValue))
            {
                configValue = defaultValue;
                log.Warn(String.Format("{0} Customization configuration [{1}] is not set: Default value [{2}] will be used",
                    GConst.LOG_PREFIX, keyName, defaultValue));
            }
            else
            {
                Boolean isValidValue = (validateFunc != null) ? validateFunc(configValue) : true;
                if (!isValidValue)
                {
                    log.Warn(String.Format("{0} Invalid customization configuration [{1}]: Invalid value of [{2}]. " +
                        "Default value of [{3}] is used", GConst.LOG_PREFIX, keyName, configValue, defaultValue));
                }
                else
                {
                    // Locate where the option has been set. Only take the ~first~ occurrence found, as this takes the highest option precedence
                    IConfigDictionary foundDictionary = null;
                    foreach (IConfigDictionary thisDictionary in config.DictionaryChain)
                    {
                        if ((thisDictionary[keyName] != null) && (foundDictionary == null))
                            foundDictionary = thisDictionary;
                    }
                    log.Debug(String.Format("{0} Customization configuration [{1}] with value [{2}] (found at {3} level)", GConst.LOG_PREFIX, keyName,
                        configValue, foundDictionary.Name));
                }

                configValue = (isValidValue) ? configValue : defaultValue;
            }

            configOptions.Add(keyName, configValue);
        }
        #endregion

        #region Public Methods for Exposing Configuration
        public string GetMainConfig(string keyName)
        {
            string fullKeyName = String.Format("{0}.{1}", CFG_MAIN_PREFIX, keyName);
            if (configOptions.ContainsKey(fullKeyName))
            {
                return configOptions.GetAsString(fullKeyName);
            }
            else
                throw new Exception(String.Format("GetMainConfig: key [{0}] not found", fullKeyName));
        }

        public bool GetMainConfigAsBool(string keyName)
        {
            try
            {
                string value = GetMainConfig(keyName);
                return (value.ToLower().Equals(Boolean.TrueString.ToLower()));
            }
            catch (Exception e)
            {
                throw new Exception(e.Message);
            }
        }
        #endregion

        private void UpdateConfig(string fullKeyName, string newValue)
        {
            if (configOptions.ContainsKey(fullKeyName))
            {
                configOptions[fullKeyName] = newValue;
            }
            else
                throw new Exception(String.Format("UpdateConfig: key [{0}] not found", fullKeyName));
        }

        /// <summary>
        /// Reads the options from CME for this customization
        /// </summary>
        /// <returns></returns>
        private void ReadFullConfiguration()
        {
            log.Debug(String.Format("{0} Reading callback preview configuration from Configuration Layer...", GConst.LOG_PREFIX));

            try
            {
                // Generic options: callback-preview.* options (on IW options)
                ReadIWOption(String.Format("{0}.{1}", CFG_MAIN_PREFIX, GConst.CFG_ENABLED), Boolean.FalseString.ToLower(),
                    ValidateBoolean);
            }
            catch (Exception ex)
            {
                log.Error(GConst.LOG_PREFIX + ex.Message, ex);
            }

            log.Debug(String.Format("{0} Finished reading callback preview configuration from Configuration Layer", GConst.LOG_PREFIX));
        }

        private void UpdateConfig(String keyName, Object newValue) {
            if (this.configOptions.ContainsKey(keyName)) {
                object value = this.configOptions[keyName];

                if (value.GetType() == newValue.GetType()) {
                    this.configOptions[keyName] = newValue;
                }
                else {
                    throw new Exception(String.Format("UpdateConfig: key [{0}] cannot be updated with another type. " +
                            "Source type [{1}], attempted target type [{2}]", keyName, value.GetType().ToString(),
                            newValue.GetType().ToString()));
                }
            }
            else
                throw new Exception(String.Format("UpdateConfig: key [{0}] not value", keyName));
        }

        private void ReadTServerConnData() {
            this.log.Debug(String.Format("{0} Reading TServer connection data...", GConst.LOG_PREFIX));

            CfgConnInfo ixnServerConnInfo = this.configurationService.ConnectedApplications.First<CfgConnInfo>(
                delegate(CfgConnInfo connInfo) {
                    return (connInfo.AppServer.Type == CfgAppType.CFGTServer);
                });

            if (ixnServerConnInfo != null) {
                String host = ixnServerConnInfo.AppServer.ServerInfo.Host.Name;
                int port = Int16.Parse(ixnServerConnInfo.AppServer.PortInfos.First<CfgPortInfo>(
                    delegate(CfgPortInfo portInfo) {
                        return (portInfo.Id == "default");
                    }).Port);

                _tserverEndpoint = new Endpoint(host, port);
                this.log.Debug(String.Format("{0} Connection info to TServer found: host [{1}], port [{2}]", GConst.LOG_PREFIX, host, port));
            }
            else {
                this.log.Warn(String.Format("{0} No connection info to TServer found.", GConst.LOG_PREFIX));
                UpdateConfig(GConst.CFG_ENABLED, false);
            }
        }

        public Endpoint EndpointTServer {
            get { return this._tserverEndpoint; }
        }
    }
}
