using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Net.Http;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Text.RegularExpressions;
using System.Threading;
using System.Threading.Tasks;
using System.Threading.Tasks.Schedulers;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Interop;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Resources;
using dw = System.Drawing;

namespace SJ.Controls
{
    public class BAsyncImage : Control
    {
        #region DependencyProperty
        public static readonly DependencyProperty DecodePixelWidthProperty = DependencyProperty.Register("DecodePixelWidth",
           typeof(double), typeof(BAsyncImage), new PropertyMetadata(0.0));

        public static readonly DependencyProperty LoadingTextProperty =
            DependencyProperty.Register("LoadingText", typeof(string), typeof(BAsyncImage), new PropertyMetadata("Loading..."));

        public static readonly DependencyProperty IsLoadingProperty =
            DependencyProperty.Register("IsLoading", typeof(bool), typeof(BAsyncImage), new PropertyMetadata(false));

        public static readonly DependencyProperty ImageSourceProperty = DependencyProperty.Register("ImageSource", typeof(ImageSource), typeof(BAsyncImage));

        public static readonly DependencyProperty DefaultUrlSourceProperty = DependencyProperty.Register("DefaultUrlSource", typeof(string), typeof(BAsyncImage), new PropertyMetadata(""));

        public static readonly DependencyProperty UrlSourceProperty =
            DependencyProperty.Register("UrlSource", typeof(string), typeof(BAsyncImage), new PropertyMetadata(string.Empty, new PropertyChangedCallback((s, e) =>
            {
                var asyncImg = s as BAsyncImage;
                if (asyncImg.LoadEventFlag)
                {
                    asyncImg.Load();
                }
            })));

        public static readonly DependencyProperty FailUrlSourceProperty =
            DependencyProperty.Register("FailUrlSource", typeof(string), typeof(BAsyncImage), new PropertyMetadata(string.Empty));

        public static readonly DependencyProperty IsCacheProperty = DependencyProperty.Register("IsCache", typeof(bool), typeof(BAsyncImage), new PropertyMetadata(true));

        public static readonly DependencyProperty StretchProperty = DependencyProperty.Register("Stretch", typeof(Stretch), typeof(BAsyncImage), new PropertyMetadata(Stretch.Uniform));

        public static readonly DependencyProperty CacheGroupProperty = DependencyProperty.Register("CacheGroup", typeof(string), typeof(BAsyncImage), new PropertyMetadata("QLAsyncImage_Default"));
        #endregion

        #region Property
        private static readonly HttpClient httpClient = new HttpClient();

        public double StaticImageActualPixelWidth { get; set; } = 0;

        public double StaticImageActualPixelHeight { get; set; } = 0;

        public const string LocalRegex = @"^([C-J]):\\([^:&]+\\)*([^:&]+).(jpg|jpeg|png|gif)$";
        public const string HttpRegex = @"^((https|http):\/\/)?([^\\*+@]+)$";

        private Image _image;
        private dw.Bitmap gifBitmap;
        private bool LoadEventFlag = false;
        private static ConcurrentDictionary<string, ImageSource> ImageCacheList;
        private static ConcurrentDictionary<string, byte[]> GifImageCacheList;

        private static LimitedConcurrencyLevelTaskScheduler httpTaskScheduler;

        public double DecodePixelWidth
        {
            get { return (double)GetValue(DecodePixelWidthProperty); }
            set { SetValue(DecodePixelWidthProperty, value); }
        }
        public string LoadingText
        {
            get { return GetValue(LoadingTextProperty) as string; }
            set { SetValue(LoadingTextProperty, value); }
        }

        public bool IsLoading
        {
            get { return (bool)GetValue(IsLoadingProperty); }
            set { SetValue(IsLoadingProperty, value); }
        }

        public string UrlSource
        {
            get { return GetValue(UrlSourceProperty) as string; }
            set { SetValue(UrlSourceProperty, value); }
        }

        /// <summary>
        /// 仅限Resourcesl路径
        /// </summary>
        public string FailUrlSource
        {
            get { return GetValue(FailUrlSourceProperty) as string; }
            set { SetValue(FailUrlSourceProperty, value); }
        }

        public ImageSource ImageSource
        {
            get { return GetValue(ImageSourceProperty) as ImageSource; }
            set { SetValue(ImageSourceProperty, value); }
        }

        public bool IsCache
        {
            get { return (bool)GetValue(IsCacheProperty); }
            set { SetValue(IsCacheProperty, value); }
        }

        public Stretch Stretch
        {
            get { return (Stretch)GetValue(StretchProperty); }
            set { SetValue(StretchProperty, value); }
        }

        public string CacheGroup
        {
            get { return GetValue(CacheGroupProperty) as string; }
            set { SetValue(CacheGroupProperty, value); }
        }

        #endregion

        #region RouteEvent
        public delegate void QLAsyncImageLoadCompleteHandler(object sender, QLAsyncImageLoadCompleteEventArgs e);

        public static readonly RoutedEvent OnLoadCompleteEvent = EventManager.RegisterRoutedEvent("OnLoadComplete", RoutingStrategy.Bubble, typeof(QLAsyncImageLoadCompleteHandler), typeof(BAsyncImage));

        public event QLAsyncImageLoadCompleteHandler OnLoadComplete
        {
            add { AddHandler(OnLoadCompleteEvent, value); }
            remove { RemoveHandler(OnLoadCompleteEvent, value); }
        }
        #endregion

        #region Method
        static BAsyncImage()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(BAsyncImage), new FrameworkPropertyMetadata(typeof(BAsyncImage)));
            ImageCacheList = new ConcurrentDictionary<string, ImageSource>();
            GifImageCacheList = new ConcurrentDictionary<string, byte[]>();
            httpTaskScheduler = new LimitedConcurrencyLevelTaskScheduler(10);
        }

        public BAsyncImage()
        {
            Loaded += QLAsyncImage_Loaded;
            Unloaded += QLAsyncImage_Unloaded;
        }

        private void QLAsyncImage_Unloaded(object sender, RoutedEventArgs e)
        {
            Reset();
            LoadEventFlag = false;
        }

        private void QLAsyncImage_Loaded(object sender, RoutedEventArgs e)
        {
            Init();
        }

        public override void OnApplyTemplate()
        {
            Init();
        }

        private void Init([CallerMemberName] string eventName = "")
        {
            if (LoadEventFlag)
                return;

            _image = GetTemplateChild("image") as Image;

            if (_image == null)
                return;

            LoadEventFlag = true;
            Load();
        }

        /// <summary>
        /// Delete local bitmap resource
        /// Reference: http://msdn.microsoft.com/en-us/library/dd183539(VS.85).aspx
        /// </summary>
        [DllImport("gdi32.dll", CharSet = CharSet.Auto, SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        static extern bool DeleteObject(IntPtr hObject);

        /// <summary>
        /// 重置图像
        /// </summary>
        private void Reset()
        {
            //停止播放Gif动画
            if (gifBitmap != null)
                StopGif();
            SetSource(null);
        }

        private void Load()
        {
            if (_image == null)
                return;

            Reset();

            if (!string.IsNullOrEmpty(UrlSource))
            {
                var url = UrlSource;
                var failUrl = FailUrlSource;
                var pixelWidth = (int)DecodePixelWidth;
                var isCache = IsCache;
                var cacheKey = string.Format("{0}_{1}", CacheGroup, url);
                IsLoading = !ImageCacheList.ContainsKey(cacheKey) && !GifImageCacheList.ContainsKey(cacheKey);

                #region 读取缓存
                if (ImageCacheList.ContainsKey(cacheKey))
                {
                    var source = ImageCacheList[cacheKey];
                    SetSource(source);
                    SetStaticImageActualPixelSize(source.Width, source.Height);
                    LoadComplete(string.Empty, url);
                    return;
                }
                else if (GifImageCacheList.ContainsKey(cacheKey))
                {
                    PlayGif(GifImageCacheList[cacheKey]);
                    LoadComplete(string.Empty, url);
                    return;
                }
                #endregion

                this.Load(url, failUrl, cacheKey, isCache, pixelWidth);
            }
        }

        private void Load(string url, string failUrl, string cacheKey, bool isCache, int pixelWidth)
        {
            var errorMessage = string.Empty;

            //解析路径类型
            var pathType = ValidatePathType(url);
            if (pathType == PathType.Invalid)
            {
                LoadFail(failUrl);
                LoadComplete(errorMessage, url);
                return;
            }

            if (pathType == PathType.Http)
            {
                ////先加载默认图
                //if (!string.IsNullOrEmpty(defaultUrl))
                //    LoadLocal(defaultUrl, failUrl, PathType.Resources, defaultUrl, true, 0, excuteComplete: false);  //默认图不触发加载完毕事件
                LoadHttp(url, failUrl, cacheKey, isCache, pixelWidth);
            }
            else
                LoadLocal(url, failUrl, pathType, cacheKey, isCache, pixelWidth);
        }

        /// <summary>
        /// 加载失败图像
        /// </summary>
        /// <param name="failUrl"></param>
        private void LoadFail(string failUrl)
        {
            byte[] imgBytes = null;
            string errorMessage = string.Empty;
            var pathType = ValidatePathType(failUrl);
            if (pathType == PathType.Invalid)
            {
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine($"LoadFail 无效的路径 {failUrl}");
                Console.ResetColor();
                return;
            }
            if (pathType == PathType.Local)
                imgBytes = LoadBytesFromLocal(failUrl, out errorMessage);
            else if (pathType == PathType.Resources)
                imgBytes = LoadBytesFromApplicationResource(failUrl, out errorMessage);


            if (string.IsNullOrEmpty(errorMessage) && imgBytes != null)
                AnalysisBytes(imgBytes, failUrl, true, 0, out errorMessage);

            if (string.IsNullOrEmpty(errorMessage))
                return;

            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine($"LoadFail {errorMessage} {failUrl}");
            Console.ResetColor();
        }

        private void LoadLocal(string url, string failUrl, PathType pathType, string cacheKey, bool isCache, int pixelWidth, bool excuteComplete = true)
        {
            byte[] imgBytes = null;
            var errorMessage = string.Empty;
            if (pathType == PathType.Local)
                imgBytes = LoadBytesFromLocal(url, out errorMessage);
            else if (pathType == PathType.Resources)
                imgBytes = LoadBytesFromApplicationResource(url, out errorMessage);

            if (string.IsNullOrEmpty(errorMessage) && imgBytes != null)
                AnalysisBytes(imgBytes, cacheKey, isCache, pixelWidth, out errorMessage);

            if (!string.IsNullOrEmpty(errorMessage))
            {
                LoadFail(failUrl);
                Console.ForegroundColor = ConsoleColor.Red;
            }
            Console.WriteLine($"LoadLocal {errorMessage} {url}");
            Console.ResetColor();
            if (excuteComplete)
                LoadComplete(errorMessage, url);
            return;
        }

        private void LoadHttp(string url, string failUrl, string cacheKey, bool isCache, int pixelWidth)
        {
            Task.Factory.StartNew(() =>
            {
                //Thread.Sleep(2000);
                Console.WriteLine($"LoadHttp Start {url}");
                var errorMessage = string.Empty;
                var imgBytes = LoadBytesFromHttp(url, out errorMessage);

                if (string.IsNullOrEmpty(errorMessage) && imgBytes != null)
                    AnalysisBytes(imgBytes, cacheKey, isCache, pixelWidth, out errorMessage);

                if (!string.IsNullOrEmpty(errorMessage))
                {
                    LoadFail(failUrl);
                    Console.ForegroundColor = ConsoleColor.Red;
                }
                Console.WriteLine($"LoadHttp Completed {errorMessage} {url}");
                Console.ResetColor();
                LoadComplete(errorMessage, url);
                return;
            }, CancellationToken.None, TaskCreationOptions.None, httpTaskScheduler);
        }

        private void AnalysisBytes(byte[] imgBytes, string cacheKey, bool isCache, int pixelWidth, out string errorMessage)
        {
            errorMessage = string.Empty;

            #region 读取文件类型
            var imgType = GetImageType(imgBytes);
            if (imgType == ImageType.Invalid)
            {
                imgBytes = null;
                errorMessage = "Invalid ImageFile";
                return;
            }
            #endregion

            #region 加载图像
            if (imgType != ImageType.Gif)
            {
                //加载静态图像    
                var imgSource = LoadStaticImage(cacheKey, imgBytes, pixelWidth, isCache, out errorMessage);
                if (imgSource == null)
                    return;
                SetStaticImageActualPixelSize(imgSource.Width, imgSource.Height);
                SetSource(imgSource);
            }
            else
            {
                var frameCount = 0;
                using (var memoryStream = new System.IO.MemoryStream(imgBytes))
                {
                    //读取gif帧数
                    var decoder = BitmapDecoder.Create(memoryStream, BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad);
                    frameCount = decoder.Frames.Count;
                    decoder = null;
                }
                if (frameCount > 1)
                {
                    CacheGifBytes(cacheKey, imgBytes, isCache);
                    PlayGif(imgBytes);
                }
                else
                {
                    //gif只有1帧,视为静态图处理 
                    var imgSource = LoadStaticImage(cacheKey, imgBytes, pixelWidth, isCache, out errorMessage);
                    if (imgSource == null)
                        return;
                    SetStaticImageActualPixelSize(imgSource.Width, imgSource.Height);
                    SetSource(imgSource);
                }
            }
            #endregion
        }


        /// <summary>
        /// 加载静态图像
        /// </summary>
        /// <param name="cacheKey"></param>
        /// <param name="imgBytes"></param>
        /// <param name="pixelWidth"></param>
        /// <param name="isCache"></param>
        /// <returns></returns> 
        private ImageSource LoadStaticImage(string cacheKey, byte[] imgBytes, int pixelWidth, bool isCache, out string errorMessage)
        {
            errorMessage = string.Empty;
            if (ImageCacheList.ContainsKey(cacheKey))
                return ImageCacheList[cacheKey];
            var bit = new BitmapImage() { CacheOption = BitmapCacheOption.OnLoad };
            try
            {
                bit.BeginInit();
                if (pixelWidth != 0)
                {
                    bit.DecodePixelWidth = pixelWidth;
                }
                bit.StreamSource = new System.IO.MemoryStream(imgBytes);
                bit.EndInit();
                bit.Freeze();
                if (isCache && !ImageCacheList.ContainsKey(cacheKey))
                    ImageCacheList.TryAdd(cacheKey, bit);
            }
            catch (Exception ex)
            {
                errorMessage = $"LoadStaticImage Error {ex.Message}";
                bit = null;
            }
            return bit;
        }

        /// <summary>
        /// 加载Gif图像动画
        /// </summary>
        /// <param name="cacheKey"></param>
        /// <param name="imgBytes"></param>
        /// <param name="pixelWidth"></param>
        /// <param name="isCache"></param>
        /// <returns></returns>
        private void CacheGifBytes(string cacheKey, byte[] imgBytes, bool isCache)
        {
            if (isCache && !GifImageCacheList.ContainsKey(cacheKey))
                GifImageCacheList.TryAdd(cacheKey, imgBytes);
        }

        private byte[] LoadBytesFromHttp(string url, out string errorMessage)
        {
            errorMessage = string.Empty;
            try
            {
                return httpClient.GetByteArrayAsync(url).Result;
            }
            catch (Exception ex)
            {
                errorMessage = $"Dowdload Error {ex.Message}";
            }
            return null;
        }

        private byte[] LoadBytesFromLocal(string path, out string errorMessage)
        {
            errorMessage = string.Empty;
            if (!System.IO.File.Exists(path))
            {
                errorMessage = "File No Exists";
                return null;
            }
            try
            {
                return System.IO.File.ReadAllBytes(path);
            }
            catch (Exception ex)
            {
                errorMessage = $"Load Local Error {ex.Message}";
                return null;
            }
        }

        private byte[] LoadBytesFromApplicationResource(string path, out string errorMessage)
        {
            errorMessage = string.Empty;
            try
            {
                StreamResourceInfo streamInfo = Application.GetResourceStream(new Uri(path, UriKind.RelativeOrAbsolute));
                if (streamInfo.Stream.CanRead)
                {
                    using (streamInfo.Stream)
                    {
                        var bytes = new byte[streamInfo.Stream.Length];
                        streamInfo.Stream.Read(bytes, 0, bytes.Length);
                        return bytes;
                    }
                }
            }
            catch (Exception ex)
            {
                errorMessage = $"Load Resource Error {ex.Message}";
                return null;
            }
            return null;
        }

        private void SetSource(ImageSource source)
        {
            Dispatcher.BeginInvoke((Action)delegate
            {
                if (_image != null)
                    _image.Source = source;
            });
        }

        /// <summary>
        /// 更新图像实际像素
        /// </summary>
        /// <param name="pixelWidth"></param>
        private void SetStaticImageActualPixelSize(double pixelWidth, double pixelHeight)
        {
            Dispatcher.Invoke(() =>
            {
                StaticImageActualPixelWidth = pixelWidth;
                StaticImageActualPixelHeight = pixelHeight;
            });
        }

        private void PlayGif(byte[] imgBytes)
        {
            gifBitmap = new dw.Bitmap(new System.IO.MemoryStream(imgBytes));
            if (dw.ImageAnimator.CanAnimate(gifBitmap))
            {
                SetStaticImageActualPixelSize(gifBitmap.Width, gifBitmap.Height);
                dw.ImageAnimator.Animate(gifBitmap, OnGifFrameChanged);
            }
            else
            {
                gifBitmap.Dispose();
            }
        }

        private void StopGif()
        {
            dw.ImageAnimator.StopAnimate(gifBitmap, OnGifFrameChanged);
            gifBitmap.Dispose();
        }

        private void OnGifFrameChanged(object sender, EventArgs e)
        {
            dw.ImageAnimator.UpdateFrames();
            var currentFrameImageSource = GetBitmapSource();
            if (currentFrameImageSource != null)
                currentFrameImageSource.Freeze();
            SetSource(currentFrameImageSource);
        }

        /// <summary>
        /// 加载完成
        /// </summary>
        /// <param name="errorMessage"></param>
        /// <param name="url"></param>
        private void LoadComplete(string errorMessage, string url)
        {
            this.Dispatcher.BeginInvoke((Action)delegate
            {
                IsLoading = false;
                var args = new QLAsyncImageLoadCompleteEventArgs(OnLoadCompleteEvent, this)
                {
                    IsSuccess = string.IsNullOrEmpty(errorMessage),
                    UrlSource = url,
                    ErrorMessage = errorMessage,
                    StaticImageActualPixelWidth = StaticImageActualPixelWidth,
                    StaticImageActualPixelHeight = StaticImageActualPixelHeight
                };
                this.RaiseEvent(args);
            });
        }

        /// <summary>
        /// 从System.Drawing.Bitmap中获得当前帧图像的BitmapSource
        /// </summary>
        /// <returns></returns>
        private ImageSource GetBitmapSource()
        {
            IntPtr handle = IntPtr.Zero;
            try
            {
                handle = gifBitmap.GetHbitmap();
                return Imaging.CreateBitmapSourceFromHBitmap(handle, IntPtr.Zero, System.Windows.Int32Rect.Empty, BitmapSizeOptions.FromEmptyOptions());
            }
            catch
            {
                return null;
            }
            finally
            {
                if (handle != IntPtr.Zero)
                {
                    DeleteObject(handle);
                }
            }
        }

        public void Refresh()
        {
            Load();
        }

        private PathType ValidatePathType(string path)
        {
            if (path.StartsWith("pack://"))
                return PathType.Resources;
            else if (Regex.IsMatch(path, BAsyncImage.LocalRegex, RegexOptions.IgnoreCase))
                return PathType.Local;
            else if (Regex.IsMatch(path, BAsyncImage.HttpRegex, RegexOptions.IgnoreCase))
                return PathType.Http;
            else
                return PathType.Invalid;
        }

        private ImageType GetImageType(byte[] bytes)
        {
            var type = ImageType.Invalid;
            try
            {
                var fileHead = Convert.ToInt32($"{bytes[0]}{bytes[1]}");
                if (!Enum.IsDefined(typeof(ImageType), fileHead))
                {
                    type = ImageType.Invalid;
                }
                else
                {
                    type = (ImageType)fileHead;
                }
            }
            catch (Exception ex)
            {
                type = ImageType.Invalid;
                Console.WriteLine($"获取图片类型失败 {ex.Message}");
            }
            return type;
        }

        /// <summary>
        /// 清楚缓存
        /// </summary>
        /// <param name="cacheKey">缓存Key,格式: CacheGroup_UrlSource</param>
        public static void ClearCache(string cacheKey = "")
        {
            if (string.IsNullOrEmpty(cacheKey))
            {
                ImageCacheList.Clear();
                GifImageCacheList.Clear();
                return;
            }

            ImageCacheList.Remove(cacheKey, out _);
            GifImageCacheList.Remove(cacheKey, out _);
        }

        public static ImageSource GetImageCache(string cacheKey)
        {
            if (ImageCacheList.ContainsKey(cacheKey))
                return ImageCacheList[cacheKey];
            return null;
        }
        #endregion
    }


    public enum PathType
    {
        Invalid = 0, Local = 1, Http = 2, Resources = 3
    }

    public enum ImageType
    {
        Invalid = 0, Gif = 7173, Jpg = 255216, Png = 13780, Bmp = 6677
    }

    public class QLAsyncImageLoadCompleteEventArgs : RoutedEventArgs
    {
        public QLAsyncImageLoadCompleteEventArgs(RoutedEvent routedEvent, object source) : base(routedEvent, source) { }

        public bool IsSuccess { get; set; }

        public string UrlSource { get; set; }

        public string ErrorMessage { get; set; }

        /// <summary>
        /// 当加载静态图时的实际像素宽度
        /// </summary>
        public double StaticImageActualPixelWidth { get; set; }

        /// <summary>
        /// 当加载静态图时的实际像素高度
        /// </summary>
        public double StaticImageActualPixelHeight { get; set; }
    }
}