using Edge.Core.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Logging.Abstractions;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;

namespace Edge.Core.Processor.Dispatcher
{
    public class LegacySettingsXmlFileLoader : IProcessorLoader
    {
        protected IServiceProvider services;
        protected static ILogger mainLogger = NullLogger.Instance;

        public LegacySettingsXmlFileLoader(IServiceProvider services)
        {
            this.services = services;
            var loggerFactory = services.GetRequiredService<ILoggerFactory>();
            mainLogger = loggerFactory.CreateLogger("Main");
        }

        public IEnumerable<ProcessorInstanceOperatingResult> CreateAll()
        {
            mainLogger.LogInformation("Use LegacySettingsXmlFileLoader for Processor Meta Config.");
            Console.WriteLine("Use LegacySettingsXmlFileLoader for Processor Meta Config.");

            var configurator = this.services.GetRequiredService<Configurator>();
            var processorConfigs = configurator.DeviceProcessorConfiguration.Processor.OrderBy(pc => pc.Name).ToList();
            mainLogger.LogInformation("==============Instantiate Processors (total: " + processorConfigs.Count() + ")==============");
            Console.WriteLine();
            Console.WriteLine("==============Instantiate Processors (total: " + processorConfigs.Count() + ")==============");

            //List<IProcessor> processorInstances = new List<IProcessor>();
            var createResults = new List<ProcessorInstanceOperatingResult>();
            for (int i = 0; i < processorConfigs.Count; i++)
            {
                var pc = processorConfigs[i];
                try
                {
                    mainLogger.LogInformation("[" + i + "]: " + pc.Name + ", " + pc.DeviceProcessorType);
                    Console.WriteLine("[" + i + "]: " + pc.Name + ", " + pc.DeviceProcessorType);
                    var parameters = pc.Parameter;//.Cast<DeviceProcessorParameter>();
                    foreach (var para in parameters)
                    {
                        mainLogger.LogInformation("            " + para.Name + " : " + para.Value);
                        //Console.WriteLine("            " + para.Name + " : " + para.Value);
                    }

                    var processorInstance =
                        ConstructComplexType_Xml_String(pc.DeviceProcessorType, pc.ConstructorArg, parameters, services) as IProcessor;
                    processorInstance.MetaConfigName = pc.Name;
                    createResults.Add(new ProcessorInstanceOperatingResult()
                    {
                        MetaConfig = new ProcessorMetaConfig()
                        {
                            Name = pc.Name,
                            Activated = true,
                            SourceEndpointFullTypeStr = pc.DeviceProcessorType,
                            Type = processorInstance is IAppProcessor ? ProcessorTypeEnum.AppProcessor : ProcessorTypeEnum.DeviceProcessor,
                            Description = "Current using Legacy Settings XmlFile mode, so this is a fake ProcessorMetaConfig"
                        },
                        //Description = "Legacy Settings XmlFile mode for instantiate a processor, and succeed",
                        Succeed = true,
                        ProcessorInstance = processorInstance,
                        //MetaConfig = mc,
                    });
                }
                catch (Exception eee)
                {
                    createResults.Add(new ProcessorInstanceOperatingResult()
                    {
                        MetaConfig = new ProcessorMetaConfig()
                        {
                            Name = pc.Name,
                            Activated = true,
                            SourceEndpointFullTypeStr = pc.DeviceProcessorType,
                            Description = "Current using Legacy Settings XmlFile mode, so this is a fake ProcessorMetaConfig"
                        },
                        //Description = "Legacy Settings XmlFile mode for instantiate a processor, but failed",
                        Succeed = false,
                        //MetaConfig = mc,
                        FailedReason = eee.ToString()
                    });
                    mainLogger.LogError(" - Failed for instantiate: " + pc.Name + Environment.NewLine + "   " + eee.ToString());
                    Console.BackgroundColor = ConsoleColor.Red;
                    Console.ForegroundColor = ConsoleColor.Black;
                    Console.WriteLine(" - Failed for instantiate: " + pc.Name + Environment.NewLine + "   " + eee.ToString().Substring(0, 90) + "...");
                    Console.ResetColor();
                }
            }

            mainLogger.LogInformation("==============Instantiate Processors End==============");
            Console.WriteLine("==============Instantiate Processors End==============");

            return createResults;
        }

        public IProcessor Create(string configName)
        {
            if (string.IsNullOrEmpty(configName)) throw new ArgumentException(nameof(configName));
            var configurator = this.services.GetRequiredService<Configurator>();
            var processorConfig = configurator.DeviceProcessorConfiguration.Processor.First(settingsXmlConfig => settingsXmlConfig.Name == configName);
            var p =
                ConstructComplexType_Xml_String(
                    processorConfig.DeviceProcessorType,
                    processorConfig.ConstructorArg,
                    processorConfig.Parameter, services) as IProcessor;
            p.MetaConfigName = processorConfig.Name;
            return p;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="typeFullyQualifiedNameString">nameSpace.ClassA, AssemblyFileName</param>
        /// <param name="constrStrFromConfig"></param>
        /// <param name="parameters"></param>
        /// <param name="services"></param>
        /// <returns></returns>
        protected virtual dynamic ConstructComplexType_Xml_String(string typeFullyQualifiedNameString, string constrStrFromConfig, IEnumerable<ProcessorParameter> parameters, IServiceProvider services)
        {
            Type processorType = Type.GetType(typeFullyQualifiedNameString);
            if (processorType == null)
            {
                var fallbackToVersion2NameString = Fallback_Version2_ResolveType_Compatbility(typeFullyQualifiedNameString);
                processorType = Type.GetType(fallbackToVersion2NameString);
                if (processorType == null)
                    throw new ArgumentException("Could not resolve type: " + typeFullyQualifiedNameString);
                else
                    typeFullyQualifiedNameString = fallbackToVersion2NameString;
            }

            if (string.IsNullOrEmpty(constrStrFromConfig))
            {
                try
                {
                    //now only support injecting one parameter.
                    var injectingCtor = processorType.GetConstructors().FirstOrDefault(c => c.GetParameters().Length == 1);
                    if (injectingCtor != null
                        && injectingCtor.GetParameters().First().ParameterType == typeof(IServiceProvider))
                    {
                        mainLogger.LogTrace("Constructing instance(with 1 param ctor and empty param from config) with type: " + processorType.Name);
                        mainLogger.LogTrace("   - ctor parameter with name: " + injectingCtor.GetParameters().First().Name + " is type IServiceProvider, will injecting");
                        var injectedInstance = Activator.CreateInstance(processorType, new object[] { services });
                        mainLogger.LogTrace("  Done constructing instance(with 1 param ctor and empty param from config) with type: " + processorType.Name);
                        return injectedInstance;
                    }

                    mainLogger.LogTrace("Constructing instance(with empty param ctor and empty param from config) with type: " + processorType.Name);
                    var parameterlessInstance = Activator.CreateInstance(processorType);
                    mainLogger.LogTrace("  Done constructing instance(with empty param ctor and empty param from config) with type: " + processorType.Name);
                    return parameterlessInstance;
                }
                catch (Exception exxx)
                {
                    //mainLogger.LogError();
                    throw new InvalidOperationException("CreateInstance(with empty ctor) for: "
                        + typeFullyQualifiedNameString + " failed, " + Environment.NewLine + " exception detail: " + exxx);
                }
            }
            else
            {
                List<object> cArgsFromConfig = new List<object>();
                var constructorArgStrings = constrStrFromConfig.Replace("\r", "").Replace("\n", "").Split(',').Select(s => s.Trim()).ToArray();
                for (int i = 0; i < constructorArgStrings.Length; i++)
                {
                    var argStr = constructorArgStrings[i];
                    try
                    {
                        if (!argStr.StartsWith("ref:"))
                        {
                            cArgsFromConfig.Add(readPrimitiveParameterValue(argStr));
                        }
                        else if (argStr.StartsWith("ref:"))
                        {
                            var refTo = argStr.Substring(4);
                            // quick ref
                            if (refTo.Contains(':'))
                            {
                                var quickRefValuePart = refTo.Split(':').Last();
                                cArgsFromConfig.Add(readPrimitiveParameterValue(quickRefValuePart));
                            }
                            else
                            {
                                var refParameter = parameters.First(p => p.Name == refTo);
                                if (!refParameter.Complex)
                                {
                                    cArgsFromConfig.Add(readPrimitiveParameterValue(refParameter.Value));
                                }
                                else
                                {
                                    var complexArg = ConstructComplexType_Xml_String(refParameter.Value, refParameter.ConstructorArg, parameters, services);
                                    cArgsFromConfig.Add(complexArg);
                                }
                            }
                        }
                    }
                    catch (Exception exxx)
                    {
                        throw new InvalidOperationException("failed for parse constructor arg[" + i + "]: " + argStr
                            + " " + Environment.NewLine + " for type: " + processorType.Name
                            + " " + Environment.NewLine + " detail: " + exxx);
                    }
                    //else if (argStr.StartsWith("refIdArray("))
                    //{
                    //    var idsString = argStr.Skip("refIdArray(".Length).TakeWhile(p => p != ')')
                    //        .Select(c => c.ToString()).Aggregate((acc, n) => acc + n);
                    //    var output = processors.Where(p => idsString.Trim().Split('|').ToList().Contains(p.Id.ToString()));
                    //    cArgs.Add(output);
                    //}
                }

                //var cc = deviceProcessorType.GetConstructors();
                try
                {
                    mainLogger.LogTrace("Constructing instance(with multiple param ctor and "
                        + cArgsFromConfig.Count + " param from config) with type: " + processorType.Name);
                    //now only support injecting one parameter.
                    var injectingCtor = processorType.GetConstructors().FirstOrDefault(c => c.GetParameters().Length == cArgsFromConfig.Count + 1);
                    //for(int c=0;c<injectingCtors.Count())
                    //{
                    if (injectingCtor != null)
                    {
                        var ctParams = injectingCtor.GetParameters();
                        for (int i = 0; i < ctParams.Length; i++)
                        {
                            var param = ctParams[i];
                            if (param.ParameterType == typeof(IServiceProvider))
                            {
                                mainLogger.LogTrace("   - ctor parameter with name: " + ctParams[i].Name + " is type IServiceProvider, will injecting");
                                cArgsFromConfig.Insert(i, services);
                            }
                        }
                    }
                    //}

                    var s = Activator.CreateInstance(processorType, cArgsFromConfig.ToArray());
                    mainLogger.LogTrace("  Done constructing instance with type: " + processorType.Name);
                    return s;
                }
                catch (Exception exxx)
                {
                    throw new InvalidOperationException("CreateInstance(with ctor args: "
                        + cArgsFromConfig.Select(c => c.ToString()).Aggregate((acc, n) => acc + ", " + n) + ") for: " + processorType.Name + " failed, \r\n exception detail: " + exxx);
                }
            }
        }

        protected virtual object readPrimitiveParameterValue(string valueStr)
        {
            if (valueStr.StartsWith("ref:")) throw new ArgumentException("ONLY can read primitive parameter value");
            if (valueStr == "") return "";
            if (valueStr.StartsWith("'")) return valueStr.Substring(1, valueStr.Length - 2);
            int intResult = -1;
            var intParseResult = int.TryParse(valueStr, out intResult);
            if (intParseResult) return intResult;
            else return valueStr;
        }

        /// <summary>
        /// version 3 combind DeviceProcessor, Communicator, Parser, Common into one project and namespace also adjusted.
        /// it'll break the old setting.xml, so here try to recover it by overwirte the old namespace and assembly name.
        /// </summary>
        /// <param name="version2TypeFullyQualifiedNameString"></param>
        /// <returns></returns>
        internal static string Fallback_Version2_ResolveType_Compatbility(string version2TypeFullyQualifiedNameString)
        {
            if (Regex.IsMatch(version2TypeFullyQualifiedNameString,
                "^\\s{0,}(DeviceProcessor|Communicator)(\\..+)(\\1)\\s{0,}$"))
            {
                /* the old one likes(the content between the ""):
                 *      deviceProcessorType="DeviceProcessor.GenericDeviceProcessor`2[[System.Byte[]], [ProGauge_StartItaliana_Probe.MessageEntity.MessageBase, ProGauge_StartItaliana_Probe]], DeviceProcessor"
                 *      deviceProcessorType="DeviceProcessor.HalfDuplexActivePollingDeviceProcessor`2[[System.Byte[]], [OPW_VaporRecoveryOnlineWatch_DataCollector.DataCollectorMessageBase,OPW_VaporRecoveryOnlineWatch_DataCollector]], DeviceProcessor"
                 *      name="deviceHandlerType" value="ProGauge_StartItaliana_Probe.Handler,ProGauge_StartItaliana_Probe"
                 *      name="deviceHandlerType" value="LanTian_Pump_664_Or_886.PumpGroupHandler,LanTian_Pump_664_Or_886"
                 *      name="communicatorType"  value="Communicator.ComPortCommunicator`1[[ProGauge_StartItaliana_Probe.MessageEntity.MessageBase,ProGauge_StartItaliana_Probe]],Communicator"
                 *      name="communicatorType" value="Communicator.TcpClientCommunicator`1[[VeederRoot_ATG_Console.MessageEntity.MessageBaseGeneric,VeederRoot_ATG_Console]],Communicator"
                 */
                version2TypeFullyQualifiedNameString = version2TypeFullyQualifiedNameString.Replace(" ", "");
                return version2TypeFullyQualifiedNameString
                    .Replace("DeviceProcessor.", "Edge.Core.Processor.")
                    .Replace(",DeviceProcessor", ",Edge.Core")
                    .Replace("Communicator.", "Edge.Core.Processor.Communicator.")
                    .Replace(",Communicator", ",Edge.Core");
            }
            else if (Regex.IsMatch(version2TypeFullyQualifiedNameString,
                "(DeviceProcessor|Communicator|Parser|Common),\\sCulture=\\w+?,\\sPublicKeyToken=\\w+"))
            {
                /* the old one likes: DeviceProcessor, Culture=neutral, PublicKeyToken=null */
                return version2TypeFullyQualifiedNameString
                        .Replace("DeviceProcessor", "Edge.Core")
                        .Replace("Communicator", "Edge.Core")
                        .Replace("Parser", "Edge.Core")
                        .Replace("Common", "Edge.Core");
            }
            else
                return version2TypeFullyQualifiedNameString;
        }
    }
}
