using System;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;

namespace AssetManagement
{
    // 2020.01.04 - Blastom:因为当前版本UnityWebRequest不支持下载https
    // 现在下载部分无法重用AsyncLoadHub
    // 快速解决方案是复制AsyncLoadHub 修改为支持BestHttp的流程。
    public class BundleDownloadHub
    {
        // 失败后重新下载等待时间
        public const float failWaitTime = 1f;
        // 失败后重新下载次数
        public const int maxRetryCount = 5;
        // 后台失败后重新下载次数
        public const int maxRetryLowPriority = 1;
        
        // 重新下载优先度,该优先度高于任何其他级别
        public const int retryPriority = 100;
        public readonly List<BundleDownloader> activeUnits = new List<BundleDownloader>();
        public readonly DownloaderQueue waitQueue = new DownloaderQueue();
        protected AssetPathHub pathHub;
        public List<AsyncLoadRule> rules;

        public void StartBackGroundDownload()
        {
            var downloadList = pathHub.GetBackgroundDownloadList();
            foreach (var download in downloadList)
                LoadUnit(download.name, -1);
        }

        public event UnityAction<BundleDownloader> onComplete;

        public void Init(AssetPathHub pathHub, List<AsyncLoadRule> rules)
        {
            this.pathHub = pathHub;
            this.rules = rules;
            this.rules.Sort((a, b) => a.priority.CompareTo(b.priority));
        }

        public BundleDownloader LoadUnit(string assetName, int priority = 0)
        {
            // 如果已经通过其他渠道下载,则直接返回当前下载的单位
            var unit = activeUnits.Find(a => a.name == assetName);
            if (unit == null)
            {
                var waitState = waitQueue.TrySetPriority(assetName, priority, out unit);
                switch (waitState)
                {
                    case SetPriorityResult.NoItem:
                    {
                        unit = new BundleDownloader();
                        unit.Init(pathHub, assetName, priority);
                        // 无需检查是否有重试延迟
                        if (CanActive(unit.priority))
                            ActivateUnit(unit);
                        else
                            waitQueue.AddInQueue(unit);
                        break;
                    }
                    case SetPriorityResult.NoUpdate:
                        // Nothing Happened
                        break;
                    case SetPriorityResult.Update:
                        if (CanActive(priority))
                            ActivateWaitUnits(1);
                        break;

                    default:
                        throw new ArgumentOutOfRangeException();
                }
            }
            else if (unit.priority < priority)
                unit.priority = priority;
            return unit;
        }

        // 注:加载中的单位是不可停止的,Unity机制限制
        /// <summary>
        ///     停止一个加载单位,返回是否可以停止
        /// </summary>
        public bool StopUnit(BundleDownloader unit)
        {
            var result = false;
            if (waitQueue.RemoveInQueue(unit.name))
                result = true;
            else if (activeUnits.Find(a => a.name == unit.name) == null)
                result = true;
            return result;
        }
        // 仅仅在关闭App调用,没有清理Unit,会留下垃圾
        public void StopAll()
        {
            for (var i = 0; i < activeUnits.Count; i++)
                activeUnits[i].Dispose();
        }

        private void OnLoadComplete(BundleDownloader activeUnit)
        {
            activeUnit.onComplete -= OnLoadComplete;
            activeUnits.Remove(activeUnit);
            // 下载成功
            if (string.IsNullOrEmpty(activeUnit.error))
            {
                if (activeUnit.failCount > 0)
                    Debug.LogWarning(string.Format("Successfully downloaded {0} at attempt {1}!", activeUnit.name, activeUnit.failCount + 1));
                if (onComplete != null)
                    onComplete.Invoke(activeUnit);
            }
            // 重试次数超过上限
            else
            {
                var fail = activeUnit.priority > 0
                    ? activeUnit.failCount >= maxRetryCount
                    : activeUnit.failCount >= maxRetryLowPriority;
                if (fail)
                {
                    Debug.LogError(string.Format("Failed to download {0} after all attempts expired!",
                        activeUnit.name));
                    if (onComplete != null)
                        onComplete.Invoke(activeUnit);
                }
                else
                // 重新尝试下载
                {
                    activeUnit.SetRetry(Time.realtimeSinceStartup + failWaitTime);
                    activeUnit.priority = retryPriority;
                    activeUnit.onWaitUp += OnUnitWaitUp;
                    Debug.Log(string.Format("Trying to restart download for bundle {0}, attempt {1}!", activeUnit.name, activeUnit.failCount));
                    waitQueue.AddInQueue(activeUnit);
                }
            }
            ActivateWaitUnits(1);
        }

        private void ActivateWaitUnits(int count)
        {
            var active = 0;
            for (var i = waitQueue.list.Count - 1; i >= 0; i--)
            {
                var unit = waitQueue.list[i];
                if (!CanActive(unit.priority))
                    break;
                if (unit.downloadTimer == null)
                {
                    active++;
                    waitQueue.list.RemoveAt(i);
                    ActivateUnit(unit);
                    if (active >= count)
                        break;
                }
            }
        }
        
        private void OnUnitWaitUp(BundleDownloader unit)
        {
            unit.onWaitUp -= OnUnitWaitUp;
            ActivateWaitUnits(1);
        }
        
        private void ActivateUnit(BundleDownloader unit)
        {
            activeUnits.Add(unit);
            unit.onComplete += OnLoadComplete;
            unit.Load();
        }

        private bool CanActive(int priority)
        {
            return GetMaxActive(priority) > activeUnits.Count;
        }

        private int GetMaxActive(int priority)
        {
            var result = -1;
            foreach (var rule in rules)
                if (rule.priority > priority)
                    result = rule.maxActive;
            return result;
        }
    }

    public class DownloaderQueue
    {
        public readonly List<BundleDownloader> list = new List<BundleDownloader>();

        public bool RemoveInQueue(string name)
        {
            var result = false;
            for (var i = 0; i < list.Count; i++)
                if (list[i].name == name)
                {
                    result = true;
                    list.RemoveAt(i);
                    break;
                }

            return result;
        }

        public void AddInQueue(BundleDownloader actionUnit)
        {
            var add = true;
            for (var i = 0; i < list.Count; i++)
                if (list[i].priority >= actionUnit.priority)
                {
                    add = false;
                    list.Insert(i, actionUnit);
                    break;
                }

            if (add)
                list.Add(actionUnit);
        }

        public SetPriorityResult TrySetPriority(string name, int priority, out BundleDownloader unit)
        {
            unit = null;
            var result = SetPriorityResult.NoItem;
            for (var i = 0; i < list.Count; i++)
                if (list[i].name == name)
                {
                    unit = list[i];
                    if (priority > list[i].priority && i < list.Count - 1)
                    {
                        result = SetPriorityResult.Update;
                        var actionUnit = list[i];
                        actionUnit.priority = priority;
                        list.RemoveAt(i);
                        AddInQueue(actionUnit);
                    }
                    else
                    {
                        result = SetPriorityResult.NoUpdate;
                    }

                    break;
                }

            return result;
        }
    }
}