#if NETFX || NETCORE

using Apewer;
using Apewer.Network;
using System;
using System.Collections.Generic;
using System.Collections.Specialized;
using System.IO;
using System.Reflection;
using System.Web;
using Apewer.Models;

#if NETCORE
using Microsoft.AspNetCore.Http;
#endif

namespace Apewer.Web
{

    internal static class ApiInternals
    {

        #region Fields

        private static Dictionary<string, ApiApplication> _entries = null;

        private static List<Assembly> _assemblies = null;

        #endregion

        #region Relection

        public static Dictionary<string, ApiApplication> Entries { get { return _entries; } set { _entries = value; } }

        public static List<Assembly> Assemblies { get { return _assemblies; } set { _assemblies = value; } }

        internal static Dictionary<string, ApiApplication> GetEntries(IEnumerable<Assembly> assemblies, bool sort = false)
        {
            var entries = new Dictionary<string, ApiApplication>();

            var deduplicates = new List<Assembly>();
            foreach (var assembly in assemblies)
            {
                if (assembly == null) continue;
                if (deduplicates.Contains(assembly)) continue;
                deduplicates.Add(assembly);
            }

            var aes = new ObjectSet<ApiApplication>();
            foreach (var assembly in deduplicates)
            {
                var types = ClassUtility.GetTypes(assembly);
                foreach (var type in types)
                {
                    var application = GetApplication(type);
                    if (application == null) continue;
                    aes[application.Name] = application;

                    var methods = type.GetMethods();
                    var fes = new ObjectSet<ApiFunction>(true);
                    foreach (var method in methods)
                    {
                        var function = GetFunction(application, method);
                        if (function == null) continue;
                        fes[function.Name] = function;
                    }

                    var fns = new List<string>();
                    fns.AddRange(ClassUtility.GetOrigin(fes).Keys);
                    if (sort) fns = SortUtility.Ascend(fns);
                    foreach (var i in fns) application.Functions.Add(i, fes[i]);
                }
            }

            var ans = new List<string>();
            ans.AddRange(ClassUtility.GetOrigin(aes).Keys);
            if (sort) ans = SortUtility.Ascend(ans);
            foreach (var i in ans) entries.Add(i, aes[i]);

            return entries;
        }

        internal static ApiApplication GetApplication(Type type)
        {
            if (type == null) return null;

            // Check Type Properties
            if (!type.IsClass) return null;
            if (type.IsAbstract) return null;
            if (type.IsGenericType) return null;

            // Check Type ApiAttributes
            var attributes = type.GetCustomAttributes(typeof(ApiAttribute), false);
            if (attributes.Length < 1) return null;

            // Check Base
            if (!ClassUtility.IsInherits(type, typeof(ApiController))) return null;

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

            // Name
            var api = (ApiAttribute)attributes[0];
            var name = api.Name;
            if (string.IsNullOrEmpty(name)) name = type.Name;
            name = name.ToLower();
            entry.Name = name;

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

            // Visible
            entry.Visible = api.Visible;
            if (entry.Visible)
            {
                if (type.ContainsAttribute<HiddenAttribute>(false))
                {
                    entry.Visible = false;
                }
            }

            // Independent
            if (type.ContainsAttribute<IndependentAttribute>(false))
            {
                entry.Independent = true;
            }

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

            return entry;
        }

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

            // 滤除构造函数。
            if (method.IsConstructor) return null;

            // 滤除继承的方法。
            if (method.DeclaringType.Equals(typeof(object))) return null;
            if (method.DeclaringType.Equals(typeof(ApiController))) return null;

            // 滤除带参数的方法。
            var parameters = method.GetParameters();
            if (parameters.Length > 0) return null;

            // Entry
            var entry = new ApiFunction();
            entry.Type = application.Type;
            entry.Assembly = application.Type.Assembly;
            entry.Method = method;
            entry.Name = method.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;
            }

            // Visible
            entry.Visible = true;
            if (application.Visible)
            {
                var hidden = method.GetCustomAttributes(typeof(HiddenAttribute), false);
                if (hidden.Length > 0) entry.Visible = false;
            }
            else
            {
                entry.Visible = false;
            }

            // Returnable
            if (method.ReturnType.Equals(typeof(string)))
            {
                entry.Returnable = true;
            }

            return entry;
        }

        #endregion

        #region ApiRequest

        internal static StringPairs ParseUrlParameters
#if NETFX
        (System.Web.HttpRequest request) => WebUtility.ParseParameters(request.Url.Query);
#else
        (Microsoft.AspNetCore.Http.HttpRequest request)
        {
            var list = new StringPairs();
            foreach (var key in request.Query.Keys)
            {
                if (request != null)
                {
                    list.Add(new KeyValuePair<string, string>(key, request.Query[key]));
                }
            }
            return list;
        }
#endif

#if NETFX
        internal static ApiRequest GetRequest(System.Web.HttpRequest request)
#else
        internal static ApiRequest GetRequest(Microsoft.AspNetCore.Http.HttpRequest request)
#endif
        {
            if (request == null) return null;
            if (string.IsNullOrEmpty(request.Path)) return null;

            var apiRequest = new ApiRequest();
            apiRequest.IP = WebUtility.GetClientIP(request, true);
            apiRequest.Url = WebUtility.GetUrl(request);
            apiRequest.Parameters = ParseUrlParameters(request);
            apiRequest.UserAgent = WebUtility.GetUserAgent(request);
#if NETFX
            apiRequest.Referrer = request.UrlReferrer;            
#endif

            // 获取 Http Method。
            apiRequest.Method = WebUtility.GetMethod(request);

            // 准备变量。
            var application = null as string;
            var function = null as string;
            var random = null as string;
            var ticket = null as string;
            var session = null as string;
            var page = null as string;

            // 头。
            foreach (var kvp in WebUtility.GetHeaders(request))
            {
                apiRequest.Headers.Add(new KeyValuePair<string, string>(kvp.Key, kvp.Value));
                switch (TextUtility.ToLower(kvp.Key))
                {
                    case "user-agent":
                        apiRequest.UserAgent = kvp.Value;
                        break;
                }
            }

            // 解析 POST 请求。
            if (apiRequest.Method == HttpMethod.POST)
            {
#if NETFX
                var post = BinaryUtility.Read(request.InputStream);
#else
                var post = BinaryUtility.Read(request.Body);
#endif
                var text = TextUtility.FromBinary(post);
                var json = Json.Parse(text) ?? Json.NewObject();

                application = json["application"];
                function = json["function"];
                random = json["random"];
                ticket = json["ticket"];
                session = json["session"];
                page = json["page"];

                var data = json.GetProperty("data");

                apiRequest.PostData = post;
                apiRequest.PostText = text;
                apiRequest.PostJson = json;
                apiRequest.Data = data ?? Json.NewObject();
            }

            // 解析 URL 参数。
            // URL 参数的优先级应高于 URL 路径,以避免反向代理产生的路径问题。
            if (string.IsNullOrEmpty(application)) application = WebUtility.GetParameter(apiRequest.Parameters, "application");
            if (string.IsNullOrEmpty(function)) function = WebUtility.GetParameter(apiRequest.Parameters, "function");
            if (string.IsNullOrEmpty(random)) random = WebUtility.GetParameter(apiRequest.Parameters, "random");
            if (string.IsNullOrEmpty(ticket)) ticket = WebUtility.GetParameter(apiRequest.Parameters, "ticket");
            if (string.IsNullOrEmpty(session)) session = WebUtility.GetParameter(apiRequest.Parameters, "session");
            if (string.IsNullOrEmpty(page)) page = WebUtility.GetParameter(apiRequest.Parameters, "page");

            // 最后检查 URL 路径。
            var paths = request.Path.ToString().Split('/');
            if (string.IsNullOrEmpty(application) && paths.Length >= 2) application = TextUtility.DecodeUrl(paths[1]);
            if (string.IsNullOrEmpty(function) && paths.Length >= 3) function = TextUtility.DecodeUrl(paths[2]);

            // 修正内容。
            application = application.SafeLower().SafeTrim();
            function = function.SafeLower().SafeTrim();
            random = random.SafeLower().SafeTrim();
            ticket = ticket.SafeLower().SafeTrim();
            session = session.SafeLower().SafeTrim();
            page = page.SafeTrim();

            // 设置请求:回传。
            apiRequest.Application = application;
            apiRequest.Function = function;
            apiRequest.Random = random;

            // 设置请求:不回传。
            apiRequest.Ticket = ticket;
            apiRequest.Session = session;
            apiRequest.Page = page;

            // 返回结果。
            return apiRequest;
        }

        #endregion

        #region ApiResponse

        /// <summary>设置 status 为 error,并设置 message 的内容。</summary>
        internal static void RespondError(ApiResponse response, string message = "未知错误。")
        {
            if (response == null) return;
            response.Type = ApiFormat.Json;
            response.Status = "error";
            response.Message = message ?? TextUtility.EmptyString;
        }

        /// <summary>设置 status 为 error,并设置 message 的内容。</summary>
        internal static void RespondError(ApiResponse response, Exception exception)
        {
            if (response == null) return;
            response.Exception = exception;
            response.Type = ApiFormat.Json;
            response.Status = "error";
            try
            {
                response.Message = exception == null ? "无效异常。" : exception.Message;
                response.Data.Reset(Json.NewObject());
                response.Data["message"] = exception.Message;
                response.Data["helplink"] = exception.HelpLink;
                response.Data["source"] = exception.Source;
                response.Data["stacktrace"] = Json.Parse(exception.StackTrace.Split('\n'), true);
            }
            catch { }
        }

        /// <summary>输出 UTF-8 文本。</summary>
        internal static void RespondText(ApiResponse response, string content, string type = "text/plain")
        {
            if (response == null) return;
            response.Type = ApiFormat.Text;
            response.TextString = content;
            response.TextType = type ?? "text/plain";
        }

        /// <summary>输出字节数组。</summary>
        internal static void RespondBinary(ApiResponse response, byte[] content, string type = "application/octet-stream")
        {
            if (response == null) return;
            response.Type = ApiFormat.Binary;
            response.BinaryStream = null;
            response.BinaryBytes = content;
            response.BinaryType = type ?? "application/octet-stream";
        }

        /// <summary>输出二进制。</summary>
        internal static void RespondBinary(ApiResponse response, Stream content, string type = "application/octet-stream")
        {
            if (response == null) return;
            response.Type = ApiFormat.Binary;
            response.BinaryStream = content;
            response.BinaryBytes = null;
            response.BinaryType = type ?? "application/octet-stream";
        }

        /// <summary>输出文件。</summary>
        internal static void RespondFile(ApiResponse response, Stream stream, string name, string type = "application/octet-stream")
        {
            if (response == null) return;
            response.Type = ApiFormat.File;
            response.FileStream = stream;
            response.FileName = name;
            response.FileType = type ?? "application/octet-stream";
        }

        /// <summary>重定向。</summary>
        public static void RespondRedirect(ApiResponse response, string url)
        {
            if (response == null) return;
            response.Type = ApiFormat.Redirect;
            response.RedirectUrl = url;
        }

        internal static string ExportJson(ApiResponse response, bool indented = true, bool exception = false)
        {
            if (response == null) return "{}";
            var json = Json.NewObject();
            json.SetProperty("beginning", response.Beginning ?? TextUtility.EmptyString);
            json.SetProperty("ending", response.Ending ?? TextUtility.EmptyString);
            json.SetProperty("random", response.Random);
            json.SetProperty("application", response.Application);
            json.SetProperty("function", response.Function);
            json.SetProperty("status", (TextUtility.IsBlank(response.Status) ? TextUtility.EmptyString : response.Status.ToLower()));
            json.SetProperty("message", response.Message);
            if (exception)
            {
                if (response.Exception == null) json.SetProperty("exception");
                else
                {
                    var exmessage = null as string;
                    var exstacktrace = null as string;
                    var exsource = null as string;
                    var exhelplink = null as string;
                    try
                    {
                        exmessage = response.Exception.Message;
                        exstacktrace = response.Exception.StackTrace;
                        exsource = response.Exception.Source;
                        exhelplink = response.Exception.HelpLink;
                    }
                    catch { }

                    var exjson = Json.NewObject();
                    exjson.SetProperty("type", response.Exception.GetType().FullName);
                    exjson.SetProperty("message", exmessage);
                    exjson.SetProperty("stack", Json.Parse((exstacktrace ?? "").Replace("\r", "").Split('\n'), false));
                    exjson.SetProperty("source", exsource);
                    exjson.SetProperty("helplink", exhelplink);

                    if (response.Exception is System.Net.WebException)
                    {
                        var webex = response.Exception as System.Net.WebException;

                        {
                            var array = Json.NewArray();
                            foreach (var k in webex.Data.Keys)
                            {
                                var item = Json.NewObject();
                                var v = webex.Data[k];
                                item.SetProperty(k.ToString(), Json.Parse(v.ToString()));
                            }
                            exjson.SetProperty("data", array);
                        }

                        exjson.SetProperty("", Json.Parse(webex.Response, true));
                    }

                    json.SetProperty("exception", exjson);
                }
            }
            json.SetProperty("data", response.Data);

            var text = json.ToString(indented);
            return text;
        }

        #endregion

        #region HttpResponse

        public static void AddHeader
#if NETFX
        (System.Web.HttpResponse response, string name, string value)
        {
            if (response == null || string.IsNullOrEmpty(name) || string.IsNullOrEmpty(value)) return;
#if NET20
            try { response.AddHeader(name, value); } catch { }
#else
            try { response.Headers.Add(name, value); } catch { }
#endif
        }
#else
        (Microsoft.AspNetCore.Http.HttpResponse response, string name, string value)
        {
            if (response == null || string.IsNullOrEmpty(name) || string.IsNullOrEmpty(value)) return;
            try { response.Headers.Add(name, value); } catch { }
        }
#endif

        public static void AddHeaders
#if NETFX
        (System.Web.HttpResponse response, NameValueCollection collection)
        {
            if (response == null || collection == null) return;
            foreach (var key in collection.AllKeys) AddHeader(response, key, collection[key]);
        }
#else
        (Microsoft.AspNetCore.Http.HttpResponse response, NameValueCollection collection)
        {
            if (response == null) return;
            if (collection == null) return;
            foreach (var key in collection.AllKeys) AddHeader(response, key, collection[key]);
        }
#endif

        public static void AddHeaders
#if NETFX
        (System.Web.HttpResponse response, StringPairs headers)
        {
            if (response == null || headers == null) return;
            foreach (var key in headers.GetAllKeys())
            {
                var values = headers.GetValues(key);
                foreach (var value in values) AddHeader(response, key, value);
            }
        }
#else
        (Microsoft.AspNetCore.Http.HttpResponse response, StringPairs headers)
        {
            if (response == null || headers == null) return;
            foreach (var key in headers.GetAllKeys())
            {
                var values = headers.GetValues(key);
                foreach (var value in values) AddHeader(response, key, value);
            }
        }
#endif

        public static void SetTextPlain
#if NETFX
        (System.Web.HttpResponse response, string value = null)
        {
            if (response == null) return;
            if (string.IsNullOrEmpty(value))
            {
                response.ContentType = "text/plain";
                response.Charset = "utf-8";
            }
            else
            {
                response.ContentType = value;
            }
        }
#else
        (Microsoft.AspNetCore.Http.HttpResponse response, string value = null)
        {
            if (response == null) return;
            response.ContentType = string.IsNullOrEmpty(value) ? "text/plain; charset=utf-8" : value;
        }
#endif

        public static void SetContentLength
#if NETFX
        (System.Web.HttpResponse response, long value)
        {
            if (response == null) return;
            if (value < 0L) return;
            response.AddHeader("Content-Length", value.ToString());
        }
#else
        (Microsoft.AspNetCore.Http.HttpResponse response, long value)
        {
            if (response == null) return;
            if (value < 0L) return;
            response.ContentLength = value;
        }

#endif

        public static Stream GetStream
#if NETFX
        (System.Web.HttpResponse response)
        {
            if (response == null) return null;
            return response.OutputStream;
        }
#else
        (Microsoft.AspNetCore.Http.HttpResponse response)
        {
            if (response == null) return null;
            return response.Body;
        }
#endif

        #endregion

    }

}

#endif