using System;
using System.IO;
using System.Net;
using System.Net.Http;
using System.Threading;

namespace Binance.TradeRobot.Common.Http
{
    /// <summary>
    /// Http下载器
    /// </summary>
    public class HttpDownloader
    {

        private IHttpClientFactory httpClientFactory;

        public HttpDownloader(IHttpClientFactory httpClientFactory, DownloadSetting dwSetting = null)
        {
            this.httpClientFactory = httpClientFactory;
            complateArgs = new DownloadCompletedEventArgs();
            changeArgs = new DownloadProgressChangedEventArgs();
            if (dwSetting == null)
                dwSetting = new DownloadSetting();
            downloadSetting = dwSetting;
            buffer = new byte[downloadSetting.BufferSize];
        }

        ~HttpDownloader()
        {
            this.buffer = null;
            this.downloadSetting = null;
            this.complateArgs.Error = null;
            this.complateArgs = null;
            this.changeArgs = null;
        }

        #region Property

        /// <summary>
        /// 下载进度变化参数
        /// </summary>
        private DownloadProgressChangedEventArgs changeArgs;

        /// <summary>
        /// 下载完成参数
        /// </summary>
        private DownloadCompletedEventArgs complateArgs;

        /// <summary>
        /// 下载参数配置类
        /// </summary>
        private DownloadSetting downloadSetting;

        /// <summary>
        /// 下载缓冲区
        /// </summary>
        private byte[] buffer;

        #endregion

        #region Delegate
        public delegate void DownloadProgressChangedEventHandler(object sender, DownloadProgressChangedEventArgs e);

        public delegate void DownloadCompletedEventHandler(object sender, DownloadCompletedEventArgs e);

        public delegate void ReDownloadEventHandler(object sender, DownloadCompletedEventArgs e);
        #endregion

        #region Event
        /// <summary>
        /// 下载进度变化事件
        /// </summary>
        public event DownloadProgressChangedEventHandler OnDownloadProgressChanged;

        /// <summary>
        /// 下载完成事件
        /// </summary>
        public event DownloadCompletedEventHandler OnDownloadComplated;

        /// <summary>
        /// 自动重下事件
        /// </summary>
        public event ReDownloadEventHandler OnReDownload;
        #endregion

        #region Method
        /// <summary>
        /// 设置下载参数
        /// </summary>
        /// <param name="dwSetting"></param>
        public void SetDownloadSetting(DownloadSetting dwSetting)
        {
            if (dwSetting != null)
                downloadSetting = dwSetting;
        }

        private void DoProcessChangedEvent(DownloadProgressChangedEventArgs args)
        {
            OnDownloadProgressChanged?.Invoke(this, args);
        }

        private void DoCompletedEvent(DownloadCompletedEventArgs args)
        {
            OnDownloadComplated?.Invoke(this, args);
        }

        private void DoReDownloadEvent(DownloadCompletedEventArgs args)
        {
            OnReDownload?.Invoke(this, args);
        }

        private void ArgsInit()
        {
            changeArgs.Cancel = false;
            complateArgs.Cancelled = false;
            complateArgs.Error = null;
        }

        /// <summary>
        /// 获取文件总大小
        /// </summary>
        /// <param name="url"></param>
        /// <returns></returns>
        public long GetTotalLength(string url)
        {
            long length = 0;
            try
            {
                using (var httpClient = httpClientFactory.CreateClient())
                {
                    var requestMessage = new HttpRequestMessage(HttpMethod.Head, url);
                    var httpResponse = httpClient.SendAsync(requestMessage, HttpCompletionOption.ResponseHeadersRead).Result;
                    if (httpResponse.IsSuccessStatusCode)
                        length = httpResponse.Content.Headers.ContentLength ?? 0;
                }

                //var req = (HttpWebRequest)WebRequest.Create(new Uri(url));
                //req.Method = "HEAD";
                //req.Timeout = 5000;
                //var res = (HttpWebResponse)req.GetResponse();
                //if (res.StatusCode == HttpStatusCode.OK)
                //{
                //    length = res.ContentLength;
                //}
                //res.Close();
                return length;
            }
            catch (WebException wex)
            {
                throw wex;
            }
        }


        private bool DownloadFile(string url, string savePath, long startPosition, long totalSize)
        {
            long currentReadSize = 0; //当前已经读取的字节数
            if (startPosition != 0)
                currentReadSize = startPosition;
            using (var httpClient = httpClientFactory.CreateClient())
            {
                try
                {
                    httpClient.Timeout = new TimeSpan(0, 0, 20);
                    if (currentReadSize != 0 && currentReadSize == totalSize)
                    {
                        changeArgs.CurrentSize = changeArgs.TotalSize = totalSize;
                        return true;
                    }
                    if (currentReadSize != 0)
                    {
                        try
                        {
                            httpClient.DefaultRequestHeaders.Range = new System.Net.Http.Headers.RangeHeaderValue(currentReadSize, totalSize);
                        }
                        catch (Exception ex)
                        {
                            //http 416
                            currentReadSize = 0;
                        }
                    }

                    using (var response = httpClient.GetAsync(url, HttpCompletionOption.ResponseHeadersRead).Result)
                    {
                        using (var responseStream = response.Content.ReadAsStreamAsync().Result)
                        {
                            changeArgs.TotalSize = totalSize;
                            using (var fs = new FileStream(savePath, currentReadSize == 0 ? FileMode.Create : FileMode.Append, FileAccess.Write))
                            {
                                //判断服务器是否支持断点续下
                                if (currentReadSize != 0 &&
                                    response.StatusCode == HttpStatusCode.PartialContent &&
                                    response.Content.Headers.ContentLength != totalSize)
                                    fs.Seek(startPosition, SeekOrigin.Begin);  //支持,从本地文件流末尾进行写入
                                else
                                    currentReadSize = 0;  //不支持,从本地文件流开始进行写入

                                if (buffer.Length != downloadSetting.BufferSize)
                                    buffer = new byte[downloadSetting.BufferSize];
                                int size = responseStream.Read(buffer, 0, buffer.Length);
                                while (size != 0)
                                {
                                    fs.Write(buffer, 0, size);
                                    if (buffer.Length != downloadSetting.BufferSize)
                                        buffer = new byte[downloadSetting.BufferSize];
                                    size = responseStream.Read(buffer, 0, buffer.Length);
                                    currentReadSize += size; //累计下载大小
                                                             //执行下载进度变化事件
                                    changeArgs.CurrentSize = currentReadSize;
                                    DoProcessChangedEvent(changeArgs);
                                    //判断挂起状态
                                    if (changeArgs.Cancel)
                                        return true;
                                }

                                //执行下载进度变化事件
                                changeArgs.CurrentSize = changeArgs.TotalSize;
                                DoProcessChangedEvent(changeArgs);
                                fs.Flush(true);
                            }

                            //验证远程服务器文件字节数和本地文件字节数是否一致
                            long localFileSize = 0;
                            try
                            {
                                localFileSize = new FileInfo(savePath).Length;
                            }
                            catch (Exception ex)
                            {
                                localFileSize = changeArgs.CurrentSize;
                            }

                            if (totalSize != localFileSize)
                            {
                                complateArgs.Error = new Exception(string.Format("远程文件字节:[{0}]与本地文件字节:[{1}]不一致", totalSize, localFileSize));
                            }
                        }
                    }
                }
                catch (IOException ex)
                {
                    complateArgs.Error = ex;
                }
                catch (WebException ex)
                {
                    complateArgs.Error = ex;
                }
                catch (Exception ex)
                {
                    complateArgs.Error = ex;
                }
            }
            return complateArgs.Error == null;
        }


        public bool DownloadFile(string url, string saveFolderPath, string saveFileName, long? totalSize)
        {
            ArgsInit();
            var result = false;
            //验证文件夹
            try
            {
                if (!Directory.Exists(saveFolderPath))
                    Directory.CreateDirectory(saveFolderPath);
            }
            catch (Exception ex)
            {
                complateArgs.Error = ex;
                DoCompletedEvent(complateArgs);
                return result;
            }

            if (totalSize == null || totalSize.Value == 0)
            {
                try
                {
                    totalSize = GetTotalLength(url);
                }
                catch (Exception ex)
                {
                    Console.WriteLine("获取远程服务器字节数失败 {0}", ex.Message);
                    complateArgs.Error = ex;
                    DoCompletedEvent(complateArgs);
                    return result;
                }
            }

            string saveFilePath = Path.Combine(saveFolderPath, saveFileName);
            long startPosition = 0;
            for (var i = 1; i <= downloadSetting.TryCount; i++)
            {
                if (File.Exists(saveFilePath))
                {
                    try
                    {
                        startPosition = new FileInfo(saveFilePath).Length;
                    }
                    catch
                    {
                        startPosition = 0;
                    }
                }

                result = DownloadFile(url, saveFilePath, startPosition, totalSize.Value);
                if (result)
                {
                    break;
                }
                else
                {
                    if (i != downloadSetting.TryCount)
                    {
                        complateArgs.TryCount = i + 1;
                        DoReDownloadEvent(complateArgs);
                        Thread.Sleep(downloadSetting.SleepTime);
                        if (complateArgs.Cancelled)
                            break;
                        ArgsInit();
                    }
                }
                if (complateArgs.Cancelled)
                    break;
            }
            DoCompletedEvent(complateArgs);
            return result;
        }

        public byte[] DwonloadBytes(string url)
        {
            ArgsInit();
            using (var httpClient = httpClientFactory.CreateClient())
            {
                return httpClient.GetByteArrayAsync(url).Result;
            }

        }

        /// <summary>
        /// 取消/暂停下载
        /// </summary>
        public void CancelDownload()
        {
            this.changeArgs.Cancel = true;
            this.complateArgs.Cancelled = true;
        }
        #endregion
    }


    /// <summary>
    /// 下载进度变化参数
    /// </summary>
    public class DownloadProgressChangedEventArgs
    {
        public DownloadProgressChangedEventArgs()
        {
            ProgressPercentage = 0.0;
            Cancel = false;
        }
        /// <summary>
        /// 下载进度百分比
        /// </summary>
        public double ProgressPercentage;


        private long currentSize;

        /// <summary>
        /// 当前下载总大小
        /// </summary>
        public long CurrentSize
        {
            get { return currentSize; }
            set
            {
                currentSize = value;
                this.ProgressPercentage = Math.Round((CurrentSize * 1.0 / TotalSize) * 100, 2);
            }
        }

        /// <summary>
        /// 文件总大小
        /// </summary>
        public long TotalSize;

        /// <summary>
        /// 取消状态
        /// </summary>
        public bool Cancel;
    }

    /// <summary>
    /// 下载完成参数
    /// </summary>
    public class DownloadCompletedEventArgs
    {
        public DownloadCompletedEventArgs()
        {
            Cancelled = false;
            Error = null;
        }
        /// <summary>
        /// 下载异常
        /// </summary>
        public Exception Error;

        /// <summary>
        /// 重试次数
        /// </summary>
        public int TryCount;

        /// <summary>
        /// 下载操作是否被取消 【取消则为true;默认false】
        /// </summary>
        public bool Cancelled;
    }

    public class DownloadSetting
    {
        public DownloadSetting()
        {
            this.BufferSize = 1024 * 1024;
            this.TryCount = 5;
            this.SleepTime = 5000;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="bufferSize"></param>
        /// <param name="tryCount"></param>
        /// <param name="sleepTime">毫秒</param>
        public DownloadSetting(int bufferSize, int tryCount, int sleepTime)
        {
            this.BufferSize = bufferSize;
            this.TryCount = tryCount;
            this.SleepTime = sleepTime;
        }

        /// <summary>
        /// 下载缓冲区大小
        /// </summary>
        public int BufferSize;

        /// <summary>
        /// 重试次数
        /// </summary>
        public int TryCount;

        /// <summary>
        /// 自动重下休眠时间, 毫秒
        /// </summary>
        public int SleepTime;
    }
}