using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
#if UNITY_EDITOR
using UnityEditor;
#endif
using UnityEngine;

namespace Giant.Res
{
    public struct AssetRequestHandle : IEnumerator, ICriticalNotifyCompletion
    {
        public AssetRequestHandle(AssetRequest _request)
        {
            request = _request;
            _act = null;
            awaiterContinuation = null;
        }

        public bool error
        {
            get
            {
                return request != null ? request.error : true;
            }
        }

        public bool isDone
        {
            get
            {
                return request != null ? request.isDone : false;
            }
        }

        public AssetRequest request;
        private Action<AssetRequest> _act;
        public event Action<AssetRequest> complete
        {
            add
            {
                if (_act != null)
                {
                    request.completed -= _act;
                }
                _act = value;
                request.completed += value;
            }
            remove
            {
                if (_act == value)
                {
                    request.completed -= _act;
                    _act = null;
                }
                else
                {
                    request.completed -= value;
                }
            }
        }

        public UnityEngine.Object asset
        {
            get
            {
                return request != null ? request.asset : null;
            }
        }

        public void Release()
        {
            if (request != null)
            {
                if (_act != null)
                {
                    complete -= _act;
                    _act = null;
                }
                request.Release();
                request = null;
            }
        }


        public object Current => request.Current;

        public bool MoveNext()
        {
            return request.MoveNext();
        }

        public void Reset()
        {
            request.Reset();
        }

        public bool IsValid()
        {
            return request != null;
        }

#region AwaiterBlock

        public bool IsCompleted { get => false; }
        private Action awaiterContinuation;

        public void UnsafeOnCompleted(Action continuation)
        {
            awaiterContinuation = continuation;
            request.completed += OnRequestComplete;
        }

        public void OnCompleted(Action continuation)
        {
            UnsafeOnCompleted(continuation);
        }

        public void OnRequestComplete(AssetRequest aq)
        {
            request.completed -= OnRequestComplete;
            awaiterContinuation?.Invoke();
        }

        public UnityEngine.Object GetResult()
        {
            return asset;
        }

        public AssetRequestHandle GetAwaiter()
        {
            return this;
        }

#endregion
    }

    public sealed class AssetsMgr : MonoBehaviour
    {
        private static AssetsMgr instance;

        /// the platform to run. 


        #region log
        public enum LogType
        {
            log,
            logerror,
        }
        private static void Log(LogType type, string log)
        {
            if (type == LogType.log)
            {
                Debug.Log(log);
            }
            else
            {
                Debug.LogError(log);
            }

        }
        #endregion

        #region settings
        //使用ab模式加载
        public static bool UseAssetBundle = true;

        /// <summary>
        /// 是否使用引用计数.
        /// 注意：使用引用计数，目前需要使用者手动递减计数，否则会导致资源无法卸载
        /// TODO：实际应该对资源使用弱引用，对资源所属的AB包使用引用计数
        /// </summary>
        public static bool userefrence = false;
        //引用计数未0时是否直接卸载
        public static bool refrenceUnloadforce = false;


        //非ab下调用加载函数
        public static Func<string, Type, UnityEngine.Object> loadDelegate = null;
        //文件类型转文件扩展名
        public static Func<Type, string> type2SuffixDelegate = null;

        //清单资源路径
        public static readonly string CatlogAsset = "Assets/AssetCatlog.asset";

     

        //包内资源路径
        private static string basePath;
        //更新资源路径
        private static string updatePath;
        #endregion

        private void Awake()
        {
            if(instance != null)
            {
                Destroy(this);
                return;
            }
            instance = this;
#if UNITY_EDITOR
            UseAssetBundle = Giant.Resource.AssetBundleManager.UseAssetBundle;
#else
            UseAssetBundle = true;
#endif
        }

        #region va

        //asset路径与assetbundle路径映射 从Manifest中获取
        private static Dictionary<string, string> _assetToBundles = new Dictionary<string, string>();
        //bundle依赖
        private static Dictionary<string, string[]> _bundleToDependencies = new Dictionary<string, string[]>();
        private static Dictionary<string, string> _sceneName2Path = new Dictionary<string, string>();

        //已加载或加载中的asset
        private static Dictionary<string, AssetRequest> _assets = new Dictionary<string, AssetRequest>();

        //已加载或加载中的ab
        private static Dictionary<string, BundleRequest> _bundles = new Dictionary<string, BundleRequest>();

        //加载的场景
        private static List<BundleSceneRequest> _scenes = new List<BundleSceneRequest>();
        //实例化的Gameobject
        private static List<AssetRequest> _InstanceReq = new List<AssetRequest>();


        //加载中的asset
        private static List<AssetRequest> _loadingAssetsRequest = new List<AssetRequest>();
        //加载中的ab
        //private static List<BundleRequest> _loadingBundles = new List<BundleRequest>();


        private static List<AssetRequest> _unusedAssets = new List<AssetRequest>();
        private static List<BundleRequest> _unusedBundles = new List<BundleRequest>();
        #endregion

        #region 初始化 更新 清理


      

        /// <summary>
        /// 加载完Manifest后 调用
        /// </summary>
        /// <param name="manifest"></param>
        internal static void OnLoadAssetCatlog(AssetCatlog manifest)
        {
            var assets = manifest.assets;

            var bundles = manifest.bundles;

            foreach (var item in bundles)
            {
                _bundleToDependencies[item.name] = item.deps;
            }
            foreach (var item in assets)
            {
                var path = string.Format("{0}/{1}", item.dir, item.name);
                _assetToBundles[path] = item.bundle;

                if (item.name.EndsWith(".unity"))
                {
                    _sceneName2Path[item.name] = path;
                }
            }
           
        }

        private static void Clear()
        {
            UnloadUnUsedAssetsAndBundle();
            UpdateAssets();
            UpdateBundles();

            _assetToBundles.Clear();
            _bundleToDependencies.Clear();
        }

        //private static float lastunloadtime = 0;

        private void Update()
        {
            if (Input.GetKeyDown(KeyCode.F))
            {
                foreach (var item in _assets)
                {

                    Debug.Log("asset: " + item.Key + " ref:" + item.Value.refCount);
                }
                foreach (var item in _bundles)
                {

                    Debug.Log("bundle: " + item.Key + " ref:" + item.Value.refCount);
                }
            }

            UpdateLoadingAssets();
            UpdateAssets();
            UpdateBundles();

            //if (autocollect) {
            //     Collect();
            //}

        }

        /// <summary>
        /// 更新加载中的资源
        /// </summary>
        private static void UpdateLoadingAssets()
        {
            for (var i = 0; i < _loadingAssetsRequest.Count; i++)
            {
                var request = _loadingAssetsRequest[i];
                if (request.Update())
                    continue;
                _loadingAssetsRequest.RemoveAt(i);
                --i;
            }
        }

        /// <summary>
        /// 更新bundles
        /// </summary>
        private static void UpdateBundles()
        {
            for (int i = 0; i < _unusedBundles.Count; i++)
            {
                var item = _unusedBundles[i];
                if (item.isDone)
                {
                    //UnloadDependencies(item);
                    item.Unload();
                    _unusedBundles.RemoveAt(i);
                    --i;
                }
            }
        }

        /// <summary>
        /// 更新assets
        /// </summary>
        private static void UpdateAssets()
        {
            for (int i = 0; i < _unusedAssets.Count; i++)
            {
                var request = _unusedAssets[i];
                if (!request.isDone) continue;
                request.Unload();
                _unusedAssets.RemoveAt(i);
                --i;
            }

            for (var i = 0; i < _scenes.Count; ++i)
            {
                var request = _scenes[i];
                if (request.Update() || !request.IsUnused())
                    continue;
                _scenes.RemoveAt(i);
                //Log(string.Format("UnloadScene:{0}", request.path));
                request.Unload();
                //RemoveUnusedAssets();
                --i;
            }
        }
        #endregion

        #region Common Func
        /// <summary>
        /// 通过asset名获取bundle名
        /// </summary>
        /// <param name="path"></param>
        /// <param name="assetBundleName"></param>
        /// <returns></returns>
        internal static bool GetAssetBundleName(string path, out string assetBundleName)
        {
            return _assetToBundles.TryGetValue(path, out assetBundleName);
        }

        /// <summary>
        /// 获得bundle的依赖bundle
        /// </summary>
        /// <param name="assetBundleName"></param>
        /// <returns></returns>
        private static string[] GetAllDependencies(string assetBundleName)
        {
            string[] deps;
            if (_bundleToDependencies.TryGetValue(assetBundleName, out deps))
            {
                return deps;
            }

            return new string[0];
        }


        private static string GetDataPath(string bundleName)
        {
            if (string.IsNullOrEmpty(updatePath))
                return basePath;

            var abPath = Giant.Common.VString.Concat(updatePath, bundleName);
            var ret = File.Exists(abPath.ToString()) ? updatePath : basePath;
            abPath.Dispose();
            return ret;
        }
        #endregion

        private static bool fileExist(string path)
        {
            if (UseAssetBundle)
            {
                return _assetToBundles.ContainsKey(path);
            }
            else
            {
                return File.Exists(path);
            }
        }

        private static string GetPath(string path)
        {
            if (path[0] == '/')
            {
                path = path.Substring(1);
            }
            if (fileExist(path))
                return path;

            string newpath = "Assets/AssetBundles/" + path;

            if (fileExist(newpath))
                return newpath;

            newpath = "Assets/" + path;
            if (fileExist(newpath))
                return newpath;

            //Debug.LogError("file not find " + path);
            LogUtils.LogError("file not find " + path);
            //if (!path.StartsWith("Assets/")) {
            //    path = "Assets/" + path;
            //}

            return path;
        }

        #region Load Asset


        /// <summary>
        /// 加载资源接口
        /// </summary>
        /// <param name="path"></param>
        /// <param name="type"></param>
        /// <param name="async"></param>
        /// <returns></returns>
        private static AssetRequestHandle LoadAsset(string path, Type type, bool async, string suffix = "")
        {
           
            AssetRequestHandle rethandle = default;
            if (string.IsNullOrEmpty(path))
            {
                Log(LogType.logerror, "invalid path");
                return rethandle;
            }

            if (string.IsNullOrEmpty(suffix))
            {
                if (type2SuffixDelegate != null)
                {
                    suffix = type2SuffixDelegate(type);
                    if (!string.IsNullOrEmpty(suffix) && !path.EndsWith(suffix))
                    {
                        path += suffix;
                    }
                }

            }
            else
            {
                if(!path.EndsWith(suffix))
                    path += suffix;
            }


            path = GetPath(path);
           // Debug.Log("LoadAsest:" + path);

            AssetRequest request;
            if (_assets.TryGetValue(path, out request))
            {
                request.Retain();
                _loadingAssetsRequest.Add(request);
                rethandle.request = request;
                return rethandle;
            }

            string assetBundleName;
            if (GetAssetBundleName(path, out assetBundleName))
            {
                request = async ? new BundleAssetAsyncRequest(assetBundleName)
                    : new BundleAssetRequest(assetBundleName);
            }
            else
            {
                request = new AssetRequest();
            }

            request.path = path;
            request.assetType = type;
            AddAssetRequest(request);
            request.Retain();
            rethandle.request = request;
            //Log(LogType.log, string.Format("LoadAsset:{0}", path));
            return rethandle;
        }

        private static void AddAssetRequest(AssetRequest request)
        {
            _assets.Add(request.path, request);
            _loadingAssetsRequest.Add(request);
            request.Load();
        }

        #endregion

        #region Loadab
        /// <summary>
        /// 加载ab 内部调用
        /// </summary>
        /// <param name="assetBundleName"></param>
        /// <returns></returns>
        internal static BundleRequest LoadBundle(string assetBundleName)
        {
            return LoadBundle(assetBundleName, false);
        }

        /// <summary>
        /// 异步加载ab  内存调用
        /// </summary>
        /// <param name="assetBundleName"></param>
        /// <returns></returns>
        internal static BundleRequest LoadBundleAsync(string assetBundleName)
        {
            return LoadBundle(assetBundleName, true);
        }
        private static BundleRequest LoadBundle(string assetBundleName, bool asyncMode)
        {
            if (string.IsNullOrEmpty(assetBundleName))
            {
                Log(LogType.logerror, "assetbundleName is empty");
                return null;
            }
            var directroy = GetDataPath(assetBundleName);
            var vpath = Giant.Common.VString.Concat(directroy, assetBundleName);
            var path = vpath.ToString(); 
            vpath.Dispose(); 

            BundleRequest bundle;
            if (_bundles.TryGetValue(path, out bundle))
            {
                bundle.Retain();
                _loadingAssetsRequest.Add(bundle);
                return bundle;
            }

            bundle = asyncMode ? new BundleAsyncRequest() : new BundleRequest();

            bundle.path = path;
            _bundles.Add(path, bundle);

            bundle.Load();
            _loadingAssetsRequest.Add(bundle);

            LoadDependencies(bundle, assetBundleName, asyncMode);
            bundle.Retain();
            return bundle;
        }

        private static void LoadDependencies(BundleRequest bundle, string assetBundleName, bool asyncMode)
        {
            var dependencies = GetAllDependencies(assetBundleName);
            if (dependencies.Length <= 0)
            {
                return;
            }

            for (int i = 0; i < dependencies.Length; i++)
            {
                var d = dependencies[i];
                bundle.dependencies.Add(LoadBundle(d, asyncMode));
            }
        }

        /*private static void UnloadDependencies(BundleRequest bundle)
        {
            for (int i = 0; i < bundle.dependencies.Count; i++)
            {
                var item = bundle.dependencies[i];
                item.Release();
            }
            bundle.dependencies.Clear();
        }*/
        #endregion
        private static bool CheckUnusedAB()
        {
            foreach (var item in _bundles)
            {
                if (item.Value.IsUnused() && item.Value.isDone)
                {
                    return true;
                }
            }
            return false;
        }


        /// <summary>
        /// 实例化gameobject
        /// </summary>
        /// <param name="path"></param>
        /// <param name="position"></param>
        /// <param name="rotation"></param>
        /// <param name="type"></param>
        /// <param name="async"></param>
        /// <returns></returns>
        private static AssetRequestHandle Instantiate(string path, Vector3 position, Quaternion rotation, Type type, bool async)
        {
            if (string.IsNullOrEmpty(path))
            {
                Log(LogType.logerror, "invalid path");
                return default;
            }

            string suffix = ".prefab";
            if (!path.EndsWith(suffix))
            {
                path += suffix;
            }

            path = GetPath(path);

            var request = async ? new InstanceAsyncRequest(position, rotation)
                : new InstanceRequest(position, rotation);

            request.path = path;
            request.assetType = type;
            _loadingAssetsRequest.Add(request);
            _InstanceReq.Add(request);
            request.Load();
            request.Retain();
            var rethandle = new AssetRequestHandle();
            rethandle.request = request;
            //Log(LogType.log, string.Format("LoadAsset:{0}", path));
            return rethandle;
        }

        #region 外部调用接口 
        /// <summary>
        /// 卸载没有使用的assets和assetbundles
        /// </summary>
        public static void UnloadUnUsedAssetsAndBundle()
        {
            if (!userefrence)
            {//如果不是使用引用计数方式卸载,则清空缓存并调用Resources.UnloadUnusedAssets();
                foreach (var item in _assets)
                {
                    if (item.Value.isDone && item.Value.completed == null)
                    {
                        _unusedAssets.Add(item.Value);
                    }
                }

                foreach (var request in _unusedAssets)
                {
                    _assets.Remove(request.path);
                    request.Unload();
                }
                _unusedAssets.Clear();

                //当_InstanceReq中资源被销毁时清除列表
                foreach (var request in _InstanceReq)
                {
                    if (request.isDone && request.asset == null)
                    {
                        _unusedAssets.Add(request);
                    }
                }
                foreach (var request in _unusedAssets)
                {
                    _InstanceReq.Remove(request);
                    request.Unload();
                }


                Resources.UnloadUnusedAssets();
                return;
            }
            //清理没有用的asset
            foreach (var item in _assets)
            {
                if (item.Value.IsUnused() && item.Value.isDone)
                {
                    _unusedAssets.Add(item.Value);
                }
            }

            foreach (var request in _unusedAssets)
            {
                _assets.Remove(request.path);
                request.Unload();
            }
            _unusedAssets.Clear();
           

            //清理没有用的bundle
            while (CheckUnusedAB())
            {
                foreach (var item in _bundles)
                {
                    if (item.Value.IsUnused() && item.Value.isDone)
                    {
                        _unusedBundles.Add(item.Value);
                    }
                }

                foreach (var request in _unusedBundles)
                {
                    _bundles.Remove(request.path);
                    request.Unload();
                }
                _unusedBundles.Clear();
            }
        }

        public static void UnloadUnusedAssets()
        {
            instance.StartCoroutine(instance.UnloadUnusedAssetsLastFrame());
        }

        IEnumerator UnloadUnusedAssetsLastFrame()
        {
            //经过测试 3帧以后调用可以卸载内存
            Resources.UnloadUnusedAssets();
            yield return null;
            yield return null;
            yield return null;
            Resources.UnloadUnusedAssets();
        }

        private static void CommonInit(Func<Type, string> _type2Suffix)
        {
            var instance = FindObjectOfType<AssetsMgr>();
            if (instance == null)
            {
                instance = new GameObject("[AssetMgr]").AddComponent<AssetsMgr>();
                DontDestroyOnLoad(instance.gameObject);
            }

            Clear();
#if UNITY_EDITOR
            loadDelegate = (path,type) =>
            {
                if(File.Exists(path))
                {
                  
                    if(AssetDatabase.GetAssetPath(AssetDatabase.LoadAssetAtPath(path, type)) != path)
                    {
                        Debug.LogError("文件大小写错误：" + path);
                    }
                   
                }
                
                return AssetDatabase.LoadAssetAtPath(path, type);
            }; 
#endif
            if (_type2Suffix == null)
            {
                type2SuffixDelegate = AssetUtily.DefaultType2SuffixFunc;
            }
            else
            {
                type2SuffixDelegate = _type2Suffix;
            }

          
          
        }

        public static void SetABPath(string _basePath,string _updatePath)
        {
            basePath = _basePath;
            updatePath = _updatePath;
        }

        /// <summary>
        /// 异步初始化
        /// </summary>
        /// <returns></returns>
        public static AssetRequestHandle InitializeAsync(Func<Type, string> _type2Suffix)
        {
            CommonInit(_type2Suffix);

            var request = new ManifestAsyncRequest { path = CatlogAsset };
            AddAssetRequest(request);
            AssetRequestHandle rethandle = new AssetRequestHandle();
            rethandle.request = request;
            return rethandle;
        }

        /// <summary>
        /// 同步调用初始化
        /// </summary>
        public static void Initialize(Func<Type, string> _type2Suffix)
        {
            CommonInit(_type2Suffix);

            var request = new ManifestRequest { path = CatlogAsset };
            AddAssetRequest(request);
            request.Release();
        }

        /// <summary>
        /// 实例化GameObject
        /// </summary>
        /// <param name="path"></param>
        /// <param name="position"></param>
        /// <param name="rotation"></param>
        /// <returns></returns>
        public static AssetRequestHandle Instantiate(string path, Vector3 position, Quaternion rotation)
        {
            return Instantiate(path, position, rotation, typeof(GameObject), false);
        }

        /// <summary>
        /// 异步实例化GameObject
        /// </summary>
        /// <param name="path"></param>
        /// <param name="position"></param>
        /// <param name="rotation"></param>
        /// <returns></returns>
        public static AssetRequestHandle InstantiateAsync(string path, Vector3 position, Quaternion rotation)
        {
            return Instantiate(path, position, rotation, typeof(GameObject), true);
        }

        /// <summary>
        /// 销毁GameObject
        /// </summary>
        /// <param name="request"></param>
        private static void ReleaseInstance(AssetRequest request)
        {
            _InstanceReq.Remove(request);
            request.Release();
        }

        public static void ReleaseInstance(AssetRequestHandle requesthandle)
        {
            if (requesthandle.IsValid())
            {
                ReleaseInstance(requesthandle.request);
            }
        }

        /// <summary>
        /// 销毁Gameobject
        /// </summary>
        /// <param name="go"></param>
        public static void ReleaseInstance(GameObject go)
        {
            for (int i = 0; i < _InstanceReq.Count; i++)
            {
                var request = _InstanceReq[i];
                if (request.asset == go)
                {
                    //_InstanceReq.RemoveAt(i);
                    //request.Release();
                    ReleaseInstance(request);
                    break;
                }
            }
        }

        static string GetScenePathByName(string path)
        {
            if (UseAssetBundle)
            {
                string realpath;
                if (_sceneName2Path.TryGetValue(path, out realpath))
                {
                    path = realpath;
                }
            }
            else
            {
#if UNITY_EDITOR
                path = Path.GetFileNameWithoutExtension(path);
                var guids = AssetDatabase.FindAssets(string.Format("{0} t:Scene", path));
                foreach(var guid in guids)
                {
                    string realPath = AssetDatabase.GUIDToAssetPath(guid);
                    string realName = Path.GetFileNameWithoutExtension(realPath);
                    if(realName == path)
                    {
                        path = realPath;
                        break;
                    }
                }
#endif

            }
            return path;
        }


        /// <summary>
        /// 异步加载场景
        /// </summary>
        /// <param name="path"></param>
        /// <param name="additive"></param>
        /// <returns></returns>
        public static AssetRequestHandle LoadSceneAsync(string path, bool additive)
        {
            if (string.IsNullOrEmpty(path))
            {
                Debug.LogError("invalid path");
                return default;
            }

            if (!path.EndsWith(".unity"))
            {
                path += ".unity";
            }
            path = GetScenePathByName(path);
           

            path = GetPath(path);

            var request = new BundleSceneAsyncRequest(path, additive);
            request.Load();
            request.Retain();
            _scenes.Add(request);
            AssetRequestHandle rethandle = new AssetRequestHandle();
            rethandle.request = request;
            return rethandle;
        }

        /// <summary>
        /// 卸载场景
        /// </summary>
        /// <param name="scene"></param>
        public static void UnloadScene(BundleSceneRequest scene)
        {
            scene.Unload();
        }

        public static void UnloadScene(string sceneName)
        {
            for (int i = 0; i < _scenes.Count; i++)
            {
                if (_scenes[i].sceneName == sceneName)
                {

                    _scenes[i].Unload();
                    _scenes.RemoveAt(i);
                    return;
                }
            }
        }


        /// <summary>
        /// 异步加载asset
        /// </summary>
        /// <param name="path"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        public static AssetRequestHandle LoadAssetAsync(string path, Type type, string suffix = "")
        {
            return LoadAsset(path, type, true, suffix);
        }

        public static AssetRequestHandle LoadAssetAsync<T>(string path, string suffix = "")
        {
            return LoadAsset(path, typeof(T), true, suffix);
        }

        /// <summary>
        /// 同步加载asset
        /// </summary>
        /// <param name="path"></param>
        /// <param name="type"></param>
        /// <returns></returns>
        public static AssetRequestHandle LoadAsset(string path, Type type, string suffix = "")
        {
            return LoadAsset(path, type, false, suffix);
        }
        #endregion

    }
}

