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

namespace AssetManagement
{
    /// <summary>
    /// 抽象类型:异步加载管理工具
    /// </summary>
    public abstract class AsyncLoadHub<T> where T : AsyncLoadUnit, new()
    {
        public event UnityAction<T> onComplete;
        public List<AsyncLoadRule> rules;
        public readonly List<T> activeUnits = new List<T>();
        public readonly AsyncLoadQueue<T> waitQueue = new AsyncLoadQueue<T>();
        protected AssetPathHub pathHub;
        
        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 T 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 T();
                        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(T 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;
        }

        private void OnLoadComplete(AsyncLoadUnit unit)
        {
            var activeUnit = unit as T;
            if (activeUnit == null)
                Debug.LogError(string.Format("Cannot convert unit {0} to {1}!", unit.GetType(), typeof(T)));
            else
            {
                activeUnits.Remove(activeUnit);
                if (onComplete != null)
                    onComplete.Invoke(activeUnit);
                ActivateWaitUnits(1);
            }
        }

        private void ActivateWaitUnits(int count)
        {
            for (var i = 0; i < count; i++)
            {
                if (waitQueue.list.Count < 1)
                    break;
                var last = waitQueue.list.Count - 1;
                var unit = waitQueue.list[last];
                if (!CanActive(unit.priority))
                    break;
                waitQueue.list.RemoveAt(last);
                ActivateUnit(unit);
            }
        }

        private void ActivateUnit(T 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 AsyncLoadQueue<T> where T : AsyncLoadUnit, new()
    {
        public readonly List<T> list = new List<T>();

        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(T 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 T 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;
        }
    }
    
    public enum SetPriorityResult
    {
        NoItem,
        NoUpdate,
        Update,
    }
    
    /// <summary>
    /// 加载规则:小于该优先的物体,不能同时加载超过maxActive
    /// </summary>
    public class AsyncLoadRule
    {
        public int priority;
        public int maxActive;

        public AsyncLoadRule(int priority, int maxActive)
        {
            this.priority = priority;
            this.maxActive = maxActive;
        }
    }
}