using Apewer;
using Apewer.Internals;
using System;
using System.Collections.Generic;
using System.IO;
using System.Net;
using System.Net.Security;
using System.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text;

namespace Apewer.Network
{

    /// <summary></summary>
    public class HttpClient
    {

        private string _key = TextGenerator.NewGuid();
        internal bool _locked = false;

        private TextSet _properties = new TextSet(true);
        private HttpRequest _request = new HttpRequest();
        private HttpResponse _response = new HttpResponse();
        private Exception _lastexception = null;
        private HttpWebRequest _lastrequest = null;
        private HttpWebResponse _lastresponse = null;

        /// <summary></summary>
        public HttpRequest Request { get { return _request; } }

        /// <summary></summary>
        public HttpResponse Response { get { return _response; } }

        /// <summary></summary>
        public string Key { get { return _properties["Key"]; } }

        /// <exception cref="System.ArgumentException"></exception>
        /// <exception cref="System.ArgumentNullException"></exception>
        /// <exception cref="System.InvalidOperationException"></exception>
        /// <exception cref="System.NotSupportedException"></exception>
        /// <exception cref="System.ObjectDisposedException"></exception>
        /// <exception cref="System.Security.SecurityException"></exception>
        /// <exception cref="System.Net.ProtocolViolationException"></exception>
        /// <exception cref="System.Net.WebException"></exception>
        private static HttpWebRequest CreateRequest(HttpRequest request)
        {
            var error = (Exception)null;

            if (request == null) throw new ArgumentNullException("Request", "参数无效。");

            var properties = request._properties;
            var certificates = request._certificates;
            var headers = request._headers;
            var cookies = request._cookies;
            var timeout = request._timeout;
            var url = properties["Url"];

            // 全局证书验证。
            var https = url.ToLower().StartsWith("https");
            if (https) SslUtility.ApproveValidation();

            // 创建对象。
            var http = (HttpWebRequest)WebRequest.Create(properties["Url"]);

            // 证书。
            foreach (var certificate in certificates)
            {
                if (certificate == null) continue;
                if (certificate.LongLength < 1L) continue;
                try
                {
                    var x509 = new X509Certificate(certificate);
                    http.ClientCertificates.Add(x509);
                }
                finally { }
            }

            // 请求超时。
            if (timeout > 0) http.Timeout = timeout;

            // 方法。
            http.Method = request._method.ToString();

            // 允许重定向。
            http.AllowAutoRedirect = request._redirect;

            // 头。
            foreach (var header in headers.Origin)
            {
                var key = header.Key.SafeTrim(); ;
                var value = header.Value.SafeTrim();
                if (TextVerifier.IsBlank(key)) continue;
                if (TextVerifier.IsBlank(value)) continue;
                try
                {
                    switch (key.SafeLower())
                    {
                        case "accept":
                            http.Accept = value;
                            break;
                        case "connection":
                            http.Connection = value;
                            break;
                        case "content-length":
                            http.ContentLength = TextUtility.GetInt64(value);
                            break;
                        case "content-type":
                            http.ContentType = value;
                            break;
                        case "date":
                            // request.Date = DateTimeUtility.Parse(value);
                            break;
                        case "expect":
                            http.Expect = value;
                            break;
#if !NET20
                        case "host":
                            http.Host = value;
                            break;
#endif
                        case "if-modified-since":
                            // request.IfModifiedSince = DateTimeUtility.Parse(value);
                            break;
                        case "referer":
                        case "referrer":
                            http.Referer = value;
                            break;
                        case "transfer-encoding":
                            http.TransferEncoding = value;
                            break;
                        case "user-agent":
                            http.UserAgent = value;
                            break;
                        default:
                            http.Headers.Add(header.Key, header.Value);
                            break;
                    }
                }
                catch { }
            }
            if (!TextVerifier.IsBlank(properties["TransferEncoding"])) http.TransferEncoding = properties["TransferEncoding"];
            if (!TextVerifier.IsBlank(properties["ContentType"])) http.ContentType = properties["ContentType"];
            if (!TextVerifier.IsBlank(properties["UserAgent"])) http.UserAgent = properties["UserAgent"];
            if (!TextVerifier.IsBlank(properties["Referer"])) http.Referer = properties["Referer"];

            // Cookies。
            if (cookies != null) http.CookieContainer = cookies;

            // 对 POST 请求加入内容数据。
            if (request._method == HttpMethod.POST)
            {
                if (request._stream != null)
                {
                    http.ContentLength = request._contentlength;
                    var rs = http.GetRequestStream();
                    try
                    {
                        StreamHelper.Read(request._stream, rs, 1024, request._progress);
                    }
                    catch { }
                }
                else if (request._data != null && request._data.LongLength > 0L)
                {
                    http.ContentLength = request._data.Length;
                    var rs = http.GetRequestStream();
                    try
                    {
                        rs.Write(request._data, 0, request._data.Length);
                    }
                    catch (Exception exception) { error = exception; }
                }
            }

            if (error == null) return http;
            throw error;
        }

        /// <exception cref="System.ArgumentNullException"></exception>
        /// <exception cref="System.InvalidOperationException"></exception>
        /// <exception cref="System.NotSupportedException"></exception>
        /// <exception cref="System.ObjectDisposedException"></exception>
        /// <exception cref="System.Net.ProtocolViolationException"></exception>
        /// <exception cref="System.Net.WebException"></exception>
        private static HttpWebResponse CreateResponse(HttpWebRequest request, HttpResponse response)
        {
            var error = (Exception)null;

            // 发起请求。
            var http = (HttpWebResponse)request.GetResponse();

            // 读取头。
            foreach (var key in http.Headers.AllKeys) response._headers[key] = http.Headers[key];
            response._cached = http.IsFromCache;
            response._cookies = http.Cookies;
            response._contentlength = http.ContentLength;
            response._statuscode = http.StatusCode;
            response._properties["CharacterSet"] = http.CharacterSet;
            response._properties["ContentEncoding"] = http.ContentEncoding;
            response._properties["ContentType"] = http.ContentType;
            response._properties["LastModified"] = ClockValue.Create(http.LastModified).Lucid;
            response._properties["Method"] = http.Method;
            response._properties["ProtocolVersion"] = http.ProtocolVersion.ToString();
            response._properties["Url"] = http.ResponseUri.OriginalString;
            response._properties["Server"] = http.Server;
            response._properties["StatusDescription"] = http.StatusDescription;

            // 读取内容。
            var rs = http.GetResponseStream();
            if (response._stream == null)
            {
                var memory = new MemoryStream();
                try
                {
                    StreamHelper.Read(rs, memory, Constant.DefaultBufferCapacity, response.ProgressCallback);
                }
                catch (Exception exception) { error = exception; }
                response._data = memory.ToArray();
                memory.Dispose();
            }
            else
            {
                StreamHelper.Read(rs, response._stream, 1024, response.ProgressCallback);
            }

            http.Close();

            if (error == null) return http;
            throw error;
        }

        /// <summary></summary>
        public Exception LatestException { get { return _lastexception; } }

        /// <summary></summary>
        public WebException WebException { get { return _lastexception as WebException; } }

        /// <summary></summary>
        public Exception Send()
        {
            var error = null as Exception;
            if (_request._locked || _response._locked)
            {
                error = new InvalidOperationException("存在已启动的请求,无法继续此次请求。");
            }
            else
            {
                _request._locked = true;
                _response._locked = true;

                try
                {
                    _lastrequest = CreateRequest(_request);
                    _lastresponse = CreateResponse(_lastrequest, _response);
                }
                catch (Exception exception)
                {
                    error = exception;
                }

                _request._locked = false;
                _response._locked = false;
            }
            _lastexception = error;
            return error;
        }

        /// <summary>GET</summary>
        public static HttpClient SimpleGet(string url, int timeout = 30000)
        {
            var http = new HttpClient();
            http.Request.Url = url;
            http.Request.Timeout = timeout;
            http.Request.Method = HttpMethod.GET;
            http.Send();
            return http;
        }

        /// <summary>POST</summary>
        public static HttpClient SimplePost(string url, byte[] data, int timeout = 30000, string type = "application/octet-stream")
        {
            var http = new HttpClient();
            http.Request.Url = url;
            http.Request.Timeout = timeout;
            http.Request.Method = HttpMethod.POST;
            if (type.NotBlank()) http.Request.ContentType = type;
            http.Request.Data = data ?? BinaryUtility.EmptyBytes;
            http.Send();
            return http;
        }

        /// <summary>POST text/plain</summary>
        public static HttpClient SimpleText(string url, string text, int timeout = 30000, string type = "text/plain")
        {
            return SimplePost(url, TextUtility.ToBinary(text), timeout, type);
        }

        /// <summary>POST application/x-www-form-urlencoded</summary>
        public static HttpClient SimpleForm(string url, IDictionary<string, string> form, int timeout = 30000)
        {
            if (form == null) return SimplePost(url, BinaryUtility.EmptyBytes, timeout, "application/x-www-form-urlencoded");

            var cache = new List<string>();
            foreach (var i in form)
            {
                var key = TextUtility.EncodeUrl(i.Key);
                var value = TextUtility.EncodeUrl(i.Value);
                cache.Add(key + "=" + value);
            }
            var text = string.Join("&", cache.ToArray());
            var data = TextUtility.ToBinary(text);
            return SimplePost(url, data, timeout, "application/x-www-form-urlencoded");
        }

        /// <summary>POST application/x-www-form-urlencoded</summary>
        public static HttpClient SimpleForm(string url, TextSet form, int timeout = 30000)
        {
            return SimpleForm(url, ClassUtility.GetOrigin(form), timeout);
        }

        /// <summary>合并表单参数,不包含 Query 的 ? 符号。</summary>
        public static string MergeForm(IDictionary<string, string> form)
        {
            if (form == null) return "";
            if (form.Count < 1) return "";
            var cache = new List<string>();
            foreach (var i in form)
            {
                var key = TextUtility.EncodeUrl(i.Key);
                var value = TextUtility.EncodeUrl(i.Value);
                cache.Add(key + "=" + value);
            }
            var text = string.Join("&", cache.ToArray());
            return text;
        }

        /// <summary>合并表单参数,不包含 Query 的 ? 符号。</summary>
        public static string MergeForm(TextSet form)
        {
            if (form == null) return "";
            return MergeForm(form.GetOrigin());
        }

    }

}