using System;
using System.Collections.Generic;
using System.Reflection;
using System.Text;

namespace Apewer.Web
{

    /// <summary>入口集合。</summary>
    public sealed class ApiEntries
    {

        #region 实例。

        object locker = new object();

        Dictionary<string, ApiApplication> Applications = null;
        List<ApiApplication> Items = null;

        internal ApiApplication Get(string name)
        {
            if (string.IsNullOrEmpty(name)) return null;
            if (Applications == null) return null;
            var lower = name.ToLower();
            ApiApplication app;
            var found = false;
            lock (locker) { found = Applications.TryGetValue(lower, out app); }
            return found ? app : null;
        }

        internal List<ApiApplication> Enumerate() => Items;

        /// <summary>清空当前实例。</summary>
        public void Clear()
        {
            lock (locker)
            {
                Applications = new Dictionary<string, ApiApplication>();
                Items = new List<ApiApplication>();
            }
        }

        /// <summary>追加指定的集合,指定 replace 参数将替换当前实例中的同名的入口。</summary>
        public void Append(ApiEntries entries, bool replace = false)
        {
            if (entries == null) return;
            lock (locker)
            {
                var dict = Applications ?? new Dictionary<string, ApiApplication>();
                foreach (var app in entries.Applications)
                {
                    var key = app.Key;
                    if (dict.ContainsKey(key))
                    {
                        if (replace) dict[key] = app.Value;
                        continue;
                    }
                    dict.Add(app.Key, app.Value);
                }
                var list = new List<ApiApplication>(dict.Values);
                list.Sort(new Comparison<ApiApplication>((a, b) => a.Lower.CompareTo(b.Lower)));
                Applications = dict;
                Items = list;
            }
        }

        #endregion

        #region 静态方法。

        /// <summary>从指定的程序集获取入口。</summary>
        public static ApiEntries From(Assembly assembly)
        {
            if (assembly == null) return null;
            var types = RuntimeUtility.GetTypes(assembly, false);
            var dict = new Dictionary<string, ApiApplication>();
            foreach (var type in types)
            {
                var app = Application(type, true);
                if (app == null) continue;
                var lower = app.Lower;
                if (dict.ContainsKey(lower)) continue;
                dict.Add(lower, app);

                var funcs = new Dictionary<string, ApiFunction>();
                var methods = type.GetMethods(BindingFlags.DeclaredOnly | BindingFlags.Instance | BindingFlags.Public);
                foreach (var method in methods)
                {
                    var func = Function(app, method);
                    if (func == null) continue;
                    if (funcs.ContainsKey(func.Lower)) continue;
                    funcs.Add(func.Lower, func);
                }
                app.Functions = funcs;
                app.Items = new List<ApiFunction>(funcs.Values);
                app.Items.Sort(new Comparison<ApiFunction>((a, b) => a.Name.CompareTo(b.Name)));
            }
            var entries = new ApiEntries();
            entries.Applications = dict;
            var list = new List<ApiApplication>(dict.Values);
            list.Sort(new Comparison<ApiApplication>((a, b) => a.Lower.CompareTo(b.Lower)));
            entries.Items = list;
            return entries;
        }

        /// <summary>从多个程序集中获取入口。</summary>
        public static ApiEntries From(IEnumerable<Assembly> assemblies, bool replace = false)
        {
            if (assemblies == null) return null;
            var entries = new ApiEntries();
            foreach (var assembly in assemblies) entries.Append(From(assembly), replace);
            return entries;
        }

        /// <summary>从当前程序中获取入口。 </summary>
        public static ApiEntries Calling() => From(Assembly.GetCallingAssembly());

        /// <summary>从当前 AppDomain 中获取入口。</summary>
        public static ApiEntries AppDomain(bool replace = false) => From(System.AppDomain.CurrentDomain.GetAssemblies(), replace);

        static ApiApplication Application(Type type, bool requireAttribute)
        {
            if (type == null) return null;

            // 检查类型的属性。
            if (!type.IsClass) return null;
            if (type.IsAbstract) return null;
            if (type.IsGenericType) return null;
            if (type.GetGenericArguments().NotEmpty()) return null;
            if (!RuntimeUtility.CanNew(type)) return null;

            // 检查类型的特性。
            var apis = type.GetCustomAttributes(typeof(ApiAttribute), false);
            var api = apis.Length > 0 ? (ApiAttribute)apis[0] : null;
            if (requireAttribute && api == null) return null;

            // 检查基类。
            if (!RuntimeUtility.IsInherits(type, typeof(ApiController))) return null;

            // Entry
            var entry = new ApiApplication();
            entry.Type = type;
            entry.Attribute = api;

            // Attribute
            if (api != null)
            {
                entry.Name = string.IsNullOrEmpty(api.Name) ? type.Name : api.Name;
                entry.Lower = entry.Name.ToLower();
                var name = api.Name;
                if (string.IsNullOrEmpty(name)) name = type.Name;


                entry.Caption = api.Caption;
                entry.Description = api.Description;
            }
            else
            {
                entry.Name = type.Name;
                entry.Lower = entry.Name.ToLower();
                entry.Caption = null;
                entry.Description = null;
                entry.Hidden = true;
            }

            // Caption
            if (string.IsNullOrEmpty(entry.Caption))
            {
                var captions = type.GetCustomAttributes(typeof(CaptionAttribute), true);
                if (captions.Length > 0)
                {
                    var caption = (CaptionAttribute)captions[0];
                    entry.Caption = caption.Title;
                    entry.Description = caption.Description;
                }
            }

            // Hidden
            if (type.Contains<HiddenAttribute>(false)) entry.Hidden = true;

            // Independent
            if (type.Contains<IndependentAttribute>(false)) entry.Independent = true;

            // Module
            var assemblyName = type.Assembly.GetName();
            entry.Module = TextUtility.Join("-", assemblyName.Name, assemblyName.Version.ToString());

            return entry;
        }

        static ApiFunction Function(ApiApplication application, MethodInfo method)
        {
            if (application == null) return null;
            if (method == null) return null;

            // 滤除构造函数、抽象方法、泛型和非本类定义方法。
            if (method.IsConstructor) return null;
            if (method.IsAbstract) return null;
            if (method.GetGenericArguments().NotEmpty()) return null;
            if (!method.DeclaringType.Equals(application.Type)) return null;

            // 检查 ApiAttribute 特性。
            var apis = method.GetCustomAttributes(typeof(ApiAttribute), false);
            var api = apis.Length > 0 ? (ApiAttribute)apis[0] : null;

            // Entry
            var entry = new ApiFunction();
            entry.Application = application;
            entry.Method = method;

            // 返回值。
            var returnable = method.ReturnType;
            if (returnable.Equals(typeof(void))) returnable = null;
            entry.Returnable = returnable;

            // 参数。
            var pis = method.GetParameters();
            if (pis != null && pis.Length > 0)
            {
                var pisc = pis.Length;
                for (var i = 0; i < pisc; i++)
                {
                    var pi = pis[i];
                    if (pi.IsIn) return null;
                    if (pi.IsOut) return null;
                }
                entry.Parameters = pis;

                if (pisc == 1)
                {
                    var pi = pis[0];
                    var pt = pi.ParameterType;
                    if (RuntimeUtility.IsInherits(pt, typeof(Source.Record))) entry.ParamIsRecord = true;
                }
            }

            if (api != null)
            {
                entry.Name = string.IsNullOrEmpty(api.Name) ? method.Name : api.Name;
                entry.Lower = entry.Name.ToLower();
                entry.Caption = api.Caption;
                entry.Description = api.Description;
            }
            else
            {
                entry.Name = method.Name;
                entry.Lower = entry.Name.ToLower();
                entry.Caption = null;
                entry.Description = null;
            }
            entry.Name = method.Name;
            entry.Lower = entry.Name.ToLower();

            // Caption
            var captions = method.GetCustomAttributes(typeof(CaptionAttribute), true);
            if (captions.Length > 0)
            {
                var caption = (CaptionAttribute)captions[0];
                entry.Caption = caption.Title;
                entry.Description = caption.Description;
            }

            // Hidden
            entry.Hidden = application.Hidden;
            if (!entry.Hidden && method.Contains<HiddenAttribute>(false)) entry.Hidden = true;

            return entry;
        }

        #endregion

    }

}