using Edge.Core.Processor.Dispatcher.Attributes;
using Edge.Core.Processor.Dispatcher;
using System;
using System.Collections.Generic;
using System.Data;
using System.Linq;
using System.Reflection;
using System.Text;
using System.Text.Json.Serialization;
using System.Text.Json;
using System.Threading.Tasks;
using System.Xml.Linq;
using Edge.Core.UniversalApi;
using Edge.Core.Processor.Communicator;
using Newtonsoft.Json.Schema.Generation;
using Newtonsoft.Json.Linq;
using Newtonsoft.Json.Schema;

namespace Edge.Core.Processor
{
    public enum ProcessorType
    {
        /// <summary>
        /// used to communciator with a device, must have a communicator underlying.
        /// </summary>
        DeviceProcessor,

        /// <summary>
        /// 
        /// </summary>
        Application
    }

    public class ProcessorDescriptor
    {
        public IProcessor Processor { get; }
        /// <summary>
        /// Gets the type of the Processor
        /// </summary>
        public ProcessorType ProcessorType { get; }

        /// <summary>
        /// will be the DeviceHandler instance if processor type is Device Processor.
        /// will be the AppProcessor instance if processor type is IAppProcessor.
        /// </summary>
        public object DeviceHandlerOrApp { get; }

        /// <summary>
        /// Gets all the methodInfo for functions marked with UnversalApi attribute.
        /// </summary>
        public IEnumerable<UniversalApiInfo> UniversalApiInfos { get; }

        /// <summary>
        /// will be null if processor is type AppProcessor.
        /// </summary>
        public object DeviceProcessorContext { get; }

        /// <summary>
        /// will be null if processor is type Application.
        /// </summary>
        public object DeviceProcessorCommunicator { get; }

        public ProcessorDescriptor(IProcessor processor, ProcessorType processorType,
            object deviceHandlerOrApp,
            IEnumerable<UniversalApiInfo> universalApiInfos,
            object deviceProcessorContext,
            object deviceProcessorCommunicator)
        {
            this.Processor = processor;
            this.ProcessorType = processorType;
            this.DeviceProcessorContext = deviceProcessorContext;
            this.DeviceProcessorCommunicator = deviceProcessorCommunicator;
            this.DeviceHandlerOrApp = deviceHandlerOrApp;
            this.UniversalApiInfos = universalApiInfos;
        }
    }

    public class UniversalEventInfo
    {
        public UniversalEventInfo(string eventName, Type eventDataType)
        {
            this.EventName = eventName;
            this.EventDataType = eventDataType;
        }

        public string EventName { get; private set; }

        public Type EventDataType { get; private set; }
    }

    public class UniversalApiInfo
    {
        public IProcessor Processor { get; set; }

        public UniversalApiInfo(MethodInfo serviceApiInfo, UniversalApiAttribute apiAttribute)
        {
            this.ServiceApiInfo = serviceApiInfo;
            this.ApiAttribute = apiAttribute;
        }

        public UniversalApiInfo(PropertyInfo propertyApiInfo, UniversalApiAttribute apiAttribute)
        {
            this.PropertyApiInfo = propertyApiInfo;
            this.ApiAttribute = apiAttribute;
        }

        public UniversalApiInfo(UniversalEventInfo eventApiInfo, UniversalApiAttribute apiAttribute)
        {
            this.EventApiInfo = eventApiInfo;
            this.ApiAttribute = apiAttribute;
        }

        /// <summary>
        /// </summary>
        public MethodInfo ServiceApiInfo { get; private set; }

        public PropertyInfo PropertyApiInfo { get; private set; }

        public UniversalEventInfo EventApiInfo { get; private set; }

        /// <summary>
        /// </summary>
        public UniversalApiAttribute ApiAttribute { get; private set; }
    }

    public class UniversalApiInfoDoc
    {
        /// <summary>
        /// the DeviceHanlder or AppProcessor type full name string.
        /// </summary>
        public string ProviderType { get; set; }

        public string[] ProviderTags { get; set; }
        /// <summary>
        /// Name of the MetaConfig that instantiate the processor instance.
        /// </summary>
        public string ProviderConfigName { get; set; }
        /// <summary>
        /// like Service, Property, Event
        /// </summary>
        public string BaseCategory { get; set; }

        /// <summary>
        /// The Service function name, or Property name, or Event name.
        /// </summary>
        public string ApiName { get; set; }

        /// <summary>
        /// The unique string that used to locate the specific API endpoint in a Processor.
        /// </summary>
        public string Path { get; set; }

        public string InputParametersExampleJson { get; set; }

        /// <summary>
        ///
        /// </summary>
        public string[] InputParametersJsonSchemaStrings { get; set; }

        public string OutputParametersExampleJson { get; set; }
        public string OutputParametersJsonSchema { get; set; }

        public string Description { get; set; }
    }
    public static class ExtensionMethods
    {
        /// <summary>
        /// Get the ProcessorDescriptor for a processor.
        /// </summary>
        /// <param name="processor"></param>
        /// <returns>ProcessorDescriptor</returns>
        public static ProcessorDescriptor ProcessorDescriptor(this IProcessor processor)
        {
            if (processor == null)
                throw new ArgumentNullException(nameof(processor));
            if (processor is IAppProcessor application)
            {
                var apiInfos = GetUniversalApiInfos(application.GetType(), processor);
                return new ProcessorDescriptor(processor,
                    ProcessorType.Application, processor, apiInfos, null, null);
            }
            else
            {
                dynamic p = processor;
                var deviceProcessorHandler = p.Context.Handler;
                Type handlerType = deviceProcessorHandler.GetType();
                var methodInfos = GetUniversalApiInfos(handlerType, processor);
                return new ProcessorDescriptor(processor,
                    ProcessorType.DeviceProcessor,
                    deviceProcessorHandler, methodInfos, p.Context, p.Context.Communicator);
            }
        }

        private static IEnumerable<UniversalApiInfo> GetUniversalApiInfos(Type handlerOrAppType, IProcessor processor)
        {
            var serviceUniversalApiInfos = handlerOrAppType.GetMethods()
                .Where(m =>
                    m.CustomAttributes != null
                    && m.CustomAttributes.Any(a => a.AttributeType
                        .IsAssignableFrom(typeof(UniversalApiAttribute)))
                    && m.GetCustomAttribute<UniversalApiAttribute>().IgnoreApi == false
                    && typeof(Task).IsAssignableFrom(m.ReturnType))
                .Select(mi => new UniversalApiInfo(mi, mi.GetCustomAttributes<UniversalApiAttribute>().First())
                {
                    Processor = processor
                });

            var eventUniversalApiAttributes = handlerOrAppType
                    .GetCustomAttributes<UniversalApiAttribute>().Where(att => att.IgnoreApi == false);
            if (eventUniversalApiAttributes.Any()
                && eventUniversalApiAttributes.GroupBy(g => g.Name).Any(g => g.Count() >= 2))
                throw new ArgumentOutOfRangeException("Multiple event UniversalApiAttributes with same Name: " +
                    (eventUniversalApiAttributes.GroupBy(g => g.Name).Where(g => g.Count() >= 2).First().Key ?? "")
                    + " declared on type: " + handlerOrAppType.FullName + ", make sure the event name is unique on this type.");
            var eventUniversalApiInfos = eventUniversalApiAttributes.Select(atti =>
                new UniversalApiInfo(new UniversalEventInfo(atti.Name, atti.EventDataType), atti)
                {
                    Processor = processor
                });

            var propertyUniversalApiInfos = handlerOrAppType.GetProperties()
                .Where(p =>
                    p.CustomAttributes != null
                    && p.CustomAttributes.Any(a => a.AttributeType
                        .IsAssignableFrom(typeof(UniversalApiAttribute)))
                    && p.GetCustomAttribute<UniversalApiAttribute>().IgnoreApi == false)
                .Select(p => new UniversalApiInfo(p, p.GetCustomAttributes<UniversalApiAttribute>().First())
                {
                    Processor = processor
                });

            var apiInfos = serviceUniversalApiInfos.Concat(eventUniversalApiInfos).Concat(propertyUniversalApiInfos);
            return apiInfos;
        }

        /// <summary>
        /// Filtering endpoint(either DeviceHandler or AppProcessor) from processors, and find those matched with its type Assignable for T.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="processors"></param>
        /// <returns>processors that satisfy condition.</returns>
        public static IEnumerable<IProcessor> WithHandlerOrApp<T>(this IEnumerable<IProcessor> processors) where T : class
        {
            if (processors == null) throw new ArgumentNullException(nameof(processors));
            return processors.Where(p => p.IsWithHandlerOrApp<T>());
        }

        /// <summary>
        /// Determine the endpoint(either DeviceHandler or AppProcessor) from target processor satisfies its type Assignable for T.
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="processor"></param>
        /// <returns></returns>
        public static bool IsWithHandlerOrApp<T>(this IProcessor processor) where T : class
        {
            if (processor == null) throw new ArgumentNullException(nameof(processor));
            var endpointType = processor.ProcessorDescriptor()?.DeviceHandlerOrApp?.GetType();
            if (endpointType == null) return false;
            return endpointType.IsAssignableFrom(typeof(T)) || typeof(T).IsAssignableFrom(endpointType);
        }

        /// <summary>
        /// Cast the DeviceHandler or AppProcessor to type T from the processors.
        /// </summary>
        /// <typeparam name="T">the type that will cast the DeviceHandler or AppProcessor to</typeparam>
        /// <param name="processors"></param>
        /// <returns>DeviceHandlers or AppProcessors that casted to type T.</returns>
        public static IEnumerable<T> SelectHandlerOrAppThenCast<T>(this IEnumerable<IProcessor> processors) where T : class
        {
            if (processors == null) throw new ArgumentNullException(nameof(processors));
            return processors.Select(p => p.SelectHandlerOrAppThenCast<T>());
        }

        /// <summary>
        /// Cast the DeviceHandler or AppProcessor to type T from the processor.
        /// </summary>
        /// <typeparam name="T">the type that will cast the DeviceHandler or AppProcessor to</typeparam>
        /// <param name="processor"></param>
        /// <returns>DeviceHandler or AppProcessor that casted to type T.</returns>
        public static T SelectHandlerOrAppThenCast<T>(this IProcessor processor) where T : class
        {
            if (processor == null) throw new ArgumentNullException(nameof(processor));
            var casted = processor.ProcessorDescriptor().DeviceHandlerOrApp as T;
            if (casted == null) throw new InvalidCastException();
            return casted;
        }

        public static IEnumerable<MethodBase> WithAttributeOrAll<T>(this IEnumerable<MethodBase> methodsOrCtors) where T : Attribute
        {
            var memberInfoWithAttri = methodsOrCtors.Where(mi => mi.GetCustomAttribute<T>() != null);
            if (memberInfoWithAttri.Any()) return memberInfoWithAttri;
            return methodsOrCtors;
        }

        public static IEnumerable<UniversalApiInfoDoc> FilterByTags(this IEnumerable<UniversalApiInfoDoc> source, string[] tags)
        {
            if (source == null) throw new ArgumentNullException(nameof(source));
            if (tags == null || !tags.Any() || tags.All(t => string.IsNullOrEmpty(t)))
                return source;
            else
            {
                return source.Where(d => d.ProviderTags != null && d.ProviderTags.Any()).Where(doc => tags.Intersect(doc.ProviderTags).Any());
                //var filtered = new List<UniversalApiInfoDoc>();
                //foreach (var doc in source.Where(d => d.ProviderTags != null && d.ProviderTags.Any()))
                //{
                //    if (doc.ProviderTags.Any(dt => tags.Contains(dt)))
                //        filtered.Add(doc);
                //}

                //return filtered;
            }
        }

        public static IEnumerable<ProcessorMetaDescriptor> ExtractProcessorMetaDescriptor(
            this IEnumerable<Type> targetEndPointTypes, bool includeSystemInternalComponent = false)
        {
            var descriptors = new List<ProcessorMetaDescriptor>();
            foreach (var endPointType in targetEndPointTypes)
            {
                var endpointTypeMetaPartsDescriptor = endPointType.GetCustomAttributes<MetaPartsDescriptor>().FirstOrDefault();
                if ((endpointTypeMetaPartsDescriptor?.IsSystemInternalComponent ?? false) && !includeSystemInternalComponent) continue;
                var pmcDescriptor = new ProcessorMetaDescriptor
                {
                    //Type = endPointType.GetInterfaces().Any(ti => ti.Name == typeof(IDeviceHandler<,>).Name) ?
                    //    ProcessorTypeEnum.DeviceProcessor : ProcessorTypeEnum.AppProcessor,
                    Type = endPointType.GetInterfaces().Any(ti => ti.Name == typeof(IAppProcessor).Name) ?
                        ProcessorTypeEnum.AppProcessor : ProcessorTypeEnum.DeviceProcessor,
                    SourceEndpointFullTypeStr = endPointType.AssemblyQualifiedName,
                    Tags = endpointTypeMetaPartsDescriptor?.Tags,
                    DisplayName = endpointTypeMetaPartsDescriptor?.DisplayName,
                    Description = endpointTypeMetaPartsDescriptor?.Description
                };
                if (pmcDescriptor.Type == ProcessorTypeEnum.AppProcessor)
                {
                    /*App only has one Parts*/
                    pmcDescriptor.MetaPartsGroupDescriptors = new List<ProcessorMetaPartsGroupDescriptor>() {
                        new ProcessorMetaPartsGroupDescriptor()
                        {
                            GroupType = ProcessorMetaPartsTypeEnum.App,
                            MetaPartsDescriptors = new List<ProcessorMetaPartsDescriptor>()
                            {
                                new ProcessorMetaPartsDescriptor() {
                                    FullTypeString = endPointType.AssemblyQualifiedName,
                                    TypeString = endPointType.FullName,
                                    DisplayName = endPointType.GetCustomAttribute<MetaPartsDescriptor>()?.DisplayName,
                                    Description =
                                        endPointType.GetCustomAttribute<MetaPartsDescriptor>()?.Description,
                                    ParametersJsonSchemaStrings =
                                        endPointType.GetConstructors().WithAttributeOrAll<ParamsJsonSchemas>().Select(ctor=>ctor.ResolveParamsJsonSchemas()).ToList(),
                                }
                            }
                        }
                    };
                }
                else
                {
                    var groupDescriptors = new List<ProcessorMetaPartsGroupDescriptor>();
                    var deviceHandlerGroupDescriptor =
                        new ProcessorMetaPartsGroupDescriptor()
                        {
                            GroupType = ProcessorMetaPartsTypeEnum.DeviceHandler,
                            MetaPartsDescriptors = new List<ProcessorMetaPartsDescriptor>()
                            {
                                new ProcessorMetaPartsDescriptor() {
                                    FullTypeString = endPointType.AssemblyQualifiedName,
                                    TypeString = endPointType.FullName,
                                    DisplayName = endPointType.GetCustomAttribute<MetaPartsDescriptor>()?.DisplayName,
                                    Description =
                                        endPointType.GetCustomAttribute<MetaPartsDescriptor>()?.Description,
                                    ParametersJsonSchemaStrings =
                                        endPointType.GetConstructors().WithAttributeOrAll<ParamsJsonSchemas>().Select(ctor=>ctor.ResolveParamsJsonSchemas()).ToList(),
                                }
                            }
                        };
                    groupDescriptors.Add(deviceHandlerGroupDescriptor);

                    var metaPartsRequiredAttributes = endPointType.GetCustomAttributes<MetaPartsRequired>();

                    var communicatorMetaPartsRequiredAttributes = metaPartsRequiredAttributes?.Where(at => at.RequiredPartsType?.GetInterfaces().Any(i => i.Name == typeof(ICommunicator<,>).Name) ?? false);
                    if (communicatorMetaPartsRequiredAttributes == null || !communicatorMetaPartsRequiredAttributes.Any())
                    {
                        var communicatorGroupDescriptor = new ProcessorMetaPartsGroupDescriptor()
                        {
                            GroupType = ProcessorMetaPartsTypeEnum.Communicator,
                            MetaPartsDescriptors = new List<ProcessorMetaPartsDescriptor>(){
                                new ProcessorMetaPartsDescriptor()
                                {
                                    FullTypeString = typeof(ComPortCommunicator<>).AssemblyQualifiedName,
                                    TypeString = typeof(ComPortCommunicator<>).FullName,
                                    DisplayName = typeof(ComPortCommunicator<>).GetCustomAttribute<MetaPartsDescriptor>()?.DisplayName,
                                    Description =
                                        typeof(ComPortCommunicator<>).GetCustomAttribute<MetaPartsDescriptor>()?.Description,
                                    ParametersJsonSchemaStrings =
                                        typeof(ComPortCommunicator<>).GetConstructors().WithAttributeOrAll<ParamsJsonSchemas>().Select(ctor=>ctor.ResolveParamsJsonSchemas()).ToList(),
                                },
                                new ProcessorMetaPartsDescriptor()
                                {
                                    FullTypeString = typeof(TcpClientCommunicator<>).AssemblyQualifiedName,
                                    TypeString = typeof(TcpClientCommunicator<>).FullName,
                                    DisplayName = typeof(TcpClientCommunicator<>).GetCustomAttribute<MetaPartsDescriptor>()?.DisplayName,
                                    Description =
                                        typeof(TcpClientCommunicator<>).GetCustomAttribute<MetaPartsDescriptor>()?.Description,
                                    ParametersJsonSchemaStrings =
                                        typeof(TcpClientCommunicator<>).GetConstructors().WithAttributeOrAll<ParamsJsonSchemas>().Select(ctor=>ctor.ResolveParamsJsonSchemas()).ToList(),
                                }
                            }
                        };
                        groupDescriptors.Add(communicatorGroupDescriptor);
                    }
                    else
                    {
                        groupDescriptors.Add(new ProcessorMetaPartsGroupDescriptor()
                        {
                            GroupType = ProcessorMetaPartsTypeEnum.Communicator,
                            MetaPartsDescriptors = communicatorMetaPartsRequiredAttributes.Select(att =>
                            new ProcessorMetaPartsDescriptor()
                            {
                                FullTypeString = att.RequiredPartsType.AssemblyQualifiedName,
                                TypeString = att.RequiredPartsType.FullName,
                                DisplayName = att.RequiredPartsType.GetCustomAttribute<MetaPartsDescriptor>()?.DisplayName,
                                Description =
                                            att.RequiredPartsType.GetCustomAttribute<MetaPartsDescriptor>()?.Description,
                                ParametersJsonSchemaStrings =
                                            att.RequiredPartsType.GetConstructors().WithAttributeOrAll<ParamsJsonSchemas>().Select(ctor => ctor.ResolveParamsJsonSchemas()).ToList(),
                            }).ToArray()
                        });
                    }

                    var deviceProcessorMetaPartsRequiredAttri = metaPartsRequiredAttributes?.FirstOrDefault(at => at.RequiredPartsType?.GetInterfaces().Any(i => i.Name == typeof(IDeviceProcessor<,>).Name) ?? false);
                    if (deviceProcessorMetaPartsRequiredAttri != null)
                    {
                        var specifiedProcessorGroupDescriptor = new ProcessorMetaPartsGroupDescriptor()
                        {
                            GroupType = ProcessorMetaPartsTypeEnum.DeviceProcessor,
                            MetaPartsDescriptors = new List<ProcessorMetaPartsDescriptor>(){
                                new ProcessorMetaPartsDescriptor()
                                {
                                    FullTypeString = deviceProcessorMetaPartsRequiredAttri.RequiredPartsType.AssemblyQualifiedName,
                                    TypeString = deviceProcessorMetaPartsRequiredAttri.RequiredPartsType.FullName,
                                    DisplayName = deviceProcessorMetaPartsRequiredAttri.RequiredPartsType.GetCustomAttribute<MetaPartsDescriptor>()?.DisplayName,
                                    Description =
                                        deviceProcessorMetaPartsRequiredAttri.RequiredPartsType.GetCustomAttribute<MetaPartsDescriptor>()?.Description,
                                    ParametersJsonSchemaStrings =
                                        deviceProcessorMetaPartsRequiredAttri.RequiredPartsType.GetConstructors().WithAttributeOrAll<ParamsJsonSchemas>().Select(ctor=>ctor.ResolveParamsJsonSchemas()).ToList(),
                                } }
                        };
                        groupDescriptors.Add(specifiedProcessorGroupDescriptor);
                    }
                    else
                    {
                        var processorGroupDescriptor = new ProcessorMetaPartsGroupDescriptor()
                        {
                            GroupType = ProcessorMetaPartsTypeEnum.DeviceProcessor,
                            MetaPartsDescriptors = new List<ProcessorMetaPartsDescriptor>(){
                                new ProcessorMetaPartsDescriptor()
                                {
                                    FullTypeString = typeof(GenericDeviceProcessor<,>).AssemblyQualifiedName,
                                    TypeString= typeof(GenericDeviceProcessor<,>).FullName,
                                    DisplayName = typeof(GenericDeviceProcessor<,>).GetCustomAttribute<MetaPartsDescriptor>()?.DisplayName,
                                    Description =
                                        typeof(GenericDeviceProcessor<,>).GetCustomAttribute<MetaPartsDescriptor>()?.Description,
                                    ParametersJsonSchemaStrings =
                                        typeof(GenericDeviceProcessor<,>).GetConstructors().WithAttributeOrAll<ParamsJsonSchemas>().Select(ctor=>ctor.ResolveParamsJsonSchemas()).ToList(),
                                },
                                new ProcessorMetaPartsDescriptor()
                                {
                                    FullTypeString = typeof(HalfDuplexActivePollingDeviceProcessor<,>).AssemblyQualifiedName,
                                    TypeString= typeof(HalfDuplexActivePollingDeviceProcessor<,>).FullName,
                                    DisplayName = typeof(HalfDuplexActivePollingDeviceProcessor<,>).GetCustomAttribute<MetaPartsDescriptor>()?.DisplayName,
                                    Description =
                                        typeof(HalfDuplexActivePollingDeviceProcessor<,>).GetCustomAttribute<MetaPartsDescriptor>()?.Description,
                                    ParametersJsonSchemaStrings =
                                        typeof(HalfDuplexActivePollingDeviceProcessor<,>).GetConstructors().WithAttributeOrAll<ParamsJsonSchemas>().Select(ctor=>ctor.ResolveParamsJsonSchemas()).ToList(),
                                }
                            }
                        };
                        groupDescriptors.Add(processorGroupDescriptor);
                    }

                    pmcDescriptor.MetaPartsGroupDescriptors = groupDescriptors;
                }

                descriptors.Add(pmcDescriptor);
            }

            return descriptors;
        }

        /// <summary>
        /// call the fallback config resolve method from target metaparts type.
        /// </summary>
        /// <param name="metaPartsConfig"></param>
        /// <returns>return true for called the method and updated the ParametersJsonArrayStr successfully, 
        ///     false indicates no fallback config resolve method defined, and no update.</returns>
        public static bool TryCallMetaPartsConfigCompatibilityMethodAndUpdate(this ProcessorMetaPartsConfig metaPartsConfig)
        {
            var metaPartsType = Type.GetType(metaPartsConfig.FullTypeString);// ObjectInstanceCreator.CurrentDomainProcessorEndPointTypes;
            if (metaPartsType == null) return false;// throw new ArgumentException("Could not find any metaPartsType that has type full name to: " + metaPartsConfig.FullTypeString);
            string methodName = "ResolveCtorMetaPartsConfigCompatibility";
            var resolveCompatibleMethodInfo =
                metaPartsType.GetMethods(BindingFlags.Static | BindingFlags.NonPublic | BindingFlags.Public)
                    .Where(mi => mi.Name == methodName
                        && mi.ReturnType == typeof(List<object>)).FirstOrDefault();
            if (resolveCompatibleMethodInfo == null)
            {
                // no resolve method defined in metaParts type, unable to resolve.
                return false;
            }
            else
            {
                try
                {
                    var reformatParams = resolveCompatibleMethodInfo.Invoke(null,
                        new object[] { metaPartsConfig.ParametersJsonArrayStr }
                        ) as List<object>;
                    var jsonSerializerOptions = new JsonSerializerOptions()
                    {
                        WriteIndented = true,
                        PropertyNameCaseInsensitive = true,
                    };
                    jsonSerializerOptions.Converters.Add(new JsonStringEnumConverter());
                    var reformatParamsStr = "[" + reformatParams.Select(p => JsonSerializer.Serialize(p)).Aggregate((acc, n) => acc + ", " + n) + "]";
                    metaPartsConfig.ParametersJsonArrayStr = reformatParamsStr;
                    return true;
                }
                catch { return false; }
            }
        }

        public static List<string> ResolveParamsJsonSchemas(this MethodBase methodOrCtor)
        {
            Type declaringType = methodOrCtor.DeclaringType;
            var apiDesc = methodOrCtor.GetCustomAttribute<ParamsJsonSchemas>();
            if (apiDesc != null)
            {
                if (!string.IsNullOrEmpty(apiDesc.SchemaEmbededResourceName))
                {
                    var resourceName = declaringType.Assembly.GetManifestResourceNames().FirstOrDefault(n => n.EndsWith("." + apiDesc.SchemaEmbededResourceName));
                    if (resourceName != null)
                    {
                        var resourceStream = declaringType.Assembly.GetManifestResourceStream(resourceName);
                        if (resourceStream != null)
                            using (var sr = new StreamReader(resourceStream))
                            {
                                var content = sr.ReadToEnd();
                                try
                                {
                                    var jsonSchemaRootElement = JsonDocument.Parse(content).RootElement;
                                    if (jsonSchemaRootElement.ValueKind == JsonValueKind.Array)
                                        return jsonSchemaRootElement.EnumerateArray().Select(jse => jse.GetRawText()).ToList();
                                    return new List<string>() { content };
                                }
                                catch (Exception exxx) { }
                            }
                    }
                }

                if (apiDesc.SchemaStrings != null && apiDesc.SchemaStrings.Any()
                    && apiDesc.SchemaStrings.Length ==
                        methodOrCtor.GetParameters().Where(p =>
                            !p.ParameterType.IsInterface && !p.ParameterType.IsAbstract).Count())
                    return apiDesc.SchemaStrings.ToList();
            }

            var generator = new JSchemaGenerator();
            generator.GenerationProviders.Add(new StringEnumGenerationProvider());

            return methodOrCtor.GetParameters().Where(p => !p.ParameterType.IsInterface && !p.ParameterType.IsAbstract)
                .Select(p =>
                {
                    int serializeDepth = 0;
                    if (IsTypeDepthTooHighForSerialize(p.ParameterType, ref serializeDepth))
                        return "{}".Insert(1, "\"title\": \"" + p.Name + " ----> of type: " + p.ParameterType.Name + " 's Depth is Too High for Serialize and Generate Json Sample, try avoid recursive properties\", \"type\": \"object\"");
                    else
                        return generator.Generate(p.ParameterType).ToString().Insert(1, "\"title\": \"" + p.Name + "\",");
                }).ToList();
        }

        /// <summary>
        /// Test the target type for if its serialize depth excceed 64.
        /// </summary>
        /// <param name="targetType"></param>
        /// <param name="depth">must set to 0 when call the method</param>
        /// <returns>true indicates this type is invalid for json serialize.</returns>
        private static bool IsTypeDepthTooHighForSerialize(Type targetType, ref int depth)
        {
            var targetProperties = targetType.GetProperties(BindingFlags.Public | BindingFlags.Instance).Where(p => p.PropertyType.IsClass && !p.PropertyType.IsPrimitive);
            foreach (var p in targetProperties)
            {
                depth++;
                if (depth >= 64) return true;
                if (IsTypeDepthTooHighForSerialize(p.PropertyType, ref depth))
                    return true;
                else
                    continue;
            }

            return false;
        }

        public static string ResolveParamsJsonSchemas(this PropertyInfo propertyInfo)
        {
            Type declaringType = propertyInfo.PropertyType;//.DeclaringType;
            var generator = new JSchemaGenerator();
            generator.GenerationProviders.Add(new StringEnumGenerationProvider());
            return generator.Generate(declaringType).ToString();//.Insert(1, "\"title\": \"" + propertyInfo.Name + "\",");
        }

        public static string ResolveParamsJsonSchema(this Type type)
        {
            var generator = new JSchemaGenerator();
            generator.GenerationProviders.Add(new StringEnumGenerationProvider());
            return generator.Generate(type).ToString();
        }

        /// <summary>
        /// Generate a random Json sample based on json schema
        /// </summary>
        /// <param name="schema"></param>
        /// <returns>a random Json sample</returns>
        public static JToken GenerateJsonSample(this JSchema schema)
        {
            JToken output;
            switch (schema.Type)
            {
                case JSchemaType.Object:
                    var jObject = new JObject();
                    if (schema.Properties != null)
                    {
                        foreach (var prop in schema.Properties)
                        {
                            jObject.Add(LowerCaseFirstChar(prop.Key), GenerateJsonSample(prop.Value));
                        }
                    }

                    output = jObject;
                    break;
                case JSchemaType.Object | JSchemaType.Null:
                    var jObject2 = new JObject();
                    if (schema.Properties != null)
                    {
                        foreach (var prop in schema.Properties)
                        {
                            jObject2.Add(LowerCaseFirstChar(prop.Key), GenerateJsonSample(prop.Value));
                        }
                    }

                    output = jObject2;
                    break;
                case JSchemaType.Array:
                    var jArray = new JArray();
                    foreach (var item in schema.Items)
                    {
                        jArray.Add(GenerateJsonSample(item));
                    }

                    output = jArray;
                    break;
                case JSchemaType.Array | JSchemaType.Null:
                    var jArray2 = new JArray();
                    foreach (var item in schema.Items)
                    {
                        jArray2.Add(GenerateJsonSample(item));
                    }

                    output = jArray2;
                    break;

                case JSchemaType.String:
                    if (schema.Format == "date-time")
                        output = new JValue("2020-04-01T00:01:02.8514872+08:00");
                    else if (schema.Enum != null && schema.Enum.Any())
                    {
                        if (string.IsNullOrEmpty(schema.Enum.First().ToString()))
                        {
                            if (schema.Enum.Count >= 2)
                                output = new JValue(schema.Enum[1].ToString());
                            else
                                output = Newtonsoft.Json.Linq.JValue.CreateNull();
                        }
                        else
                            output = new JValue(schema.Enum.First().ToString());
                    }
                    else
                        output = new JValue("string_sample");
                    break;
                case JSchemaType.String | JSchemaType.Null:
                    if (schema.Format == "date-time")
                        output = new JValue("2020-04-01T00:01:02.8514872+08:00");
                    else if (schema.Enum != null && schema.Enum.Any())
                    {
                        if (string.IsNullOrEmpty(schema.Enum.First().ToString()))
                        {
                            if (schema.Enum.Count >= 2)
                                output = new JValue(schema.Enum[1].ToString());
                            else
                                output = JValue.CreateNull();
                        }
                        else
                            output = new JValue(schema.Enum.First().ToString());
                    }
                    else
                        output = new JValue("nullable_string_sample");
                    break;
                case JSchemaType.Number:
                    output = new JValue(1.0);
                    break;
                case JSchemaType.Integer:
                    output = new JValue(1);
                    break;
                case JSchemaType.Boolean:
                    output = new JValue(false);
                    break;
                case JSchemaType.Null:
                    output = JValue.CreateNull();
                    break;
                // it's a type of object
                case Newtonsoft.Json.Schema.JSchemaType.String | Newtonsoft.Json.Schema.JSchemaType.Number | Newtonsoft.Json.Schema.JSchemaType.Integer | Newtonsoft.Json.Schema.JSchemaType.Boolean | Newtonsoft.Json.Schema.JSchemaType.Object | Newtonsoft.Json.Schema.JSchemaType.Array:
                    output = JValue.CreateNull();
                    break;
                default:
                    output = null;
                    break;
            }

            return output;
        }

        private static string LowerCaseFirstChar(string name)
        {
            return name.Substring(0, 1).ToLower() + name.Substring(1);
        }

        public static string GenerateParametersJsonSample(this string[] parametersJsonSchemaStrings)
        {
            if (parametersJsonSchemaStrings == null || !parametersJsonSchemaStrings.Any()) return "[]";
            var jTokens = parametersJsonSchemaStrings.Select(s => JSchema.Parse(s)).Select(js => js.GenerateJsonSample());
            return "[" + jTokens.Select(jt => jt.Type == JTokenType.String ? "\"" + jt.ToString() + "\"" : jt.ToString()).Aggregate((acc, n) => acc + ", " + n) + "]";
        }

        public static string GenerateParameterJsonSample(this string parameterJsonSchemaString)
        {
            if (string.IsNullOrEmpty(parameterJsonSchemaString)) return "";
            var jToken = JSchema.Parse(parameterJsonSchemaString).GenerateJsonSample();
            return jToken.ToString();
        }

    }
}
