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

// 已加载的assetBundle引用计数
public class LoadedAssetBundle
{
    public AssetBundle m_AssetBundle;
    public int m_ReferencedCount;

    public LoadedAssetBundle(AssetBundle assetBundle)
    {
        m_AssetBundle = assetBundle;
        m_ReferencedCount = 1;
    }
}

// AB管理类
public class AssetBundleManager : Singleton<AssetBundleManager>
{
    static string m_BaseDownloadingURL = "";
    static string[] m_Variants = { };
    static AssetBundleManifest m_AssetBundleManifest = null;
#if UNITY_EDITOR
    static int m_SimulateAssetBundleInEditor = -1;
    const string kSimulateAssetBundles = "SimulateAssetBundles";
#endif

    static Dictionary<string, LoadedAssetBundle> m_LoadedAssetBundles = new Dictionary<string, LoadedAssetBundle>();
    static Dictionary<string, WWW> m_DownloadingWWWs = new Dictionary<string, WWW>();
    static Dictionary<string, string> m_DownloadingErrors = new Dictionary<string, string>();
    static List<AssetBundleLoadOperation> m_InProgressOperations = new List<AssetBundleLoadOperation>();
    static Dictionary<string, string[]> m_Dependencies = new Dictionary<string, string[]>();

    // 下载路径
    public static string BaseDownloadingURL
    {
        get { return m_BaseDownloadingURL; }
        set { m_BaseDownloadingURL = value; }
    }

    // 
    public static string[] Variants
    {
        get { return m_Variants; }
        set { m_Variants = value; }
    }

    // AssetBundleManifest
    public static AssetBundleManifest AssetBundleManifestObject
    {
        set { m_AssetBundleManifest = value; }
    }
    const string AssetBundlesPath = "/AssetBundles/";


    //初始化
    protected IEnumerator Initialize()
    {
#if UNITY_EDITOR
        //Debug.Log("当前模式:" + (SimulateAssetBundleInEditor ? "模拟模式" : "正常模式"));
#endif

        string platformFolderForAssetBundles =
#if UNITY_EDITOR
            GetPlatformFolderForAssetBundles(EditorUserBuildSettings.activeBuildTarget);
#else
			GetPlatformFolderForAssetBundles(Application.platform);
#endif

        //下载路径
        string relativePath = GetRelativePath();
        BaseDownloadingURL = relativePath + AssetBundlesPath + platformFolderForAssetBundles + "/";
        var request = Initialize(platformFolderForAssetBundles);
        if (request != null)
        {
            yield return StartCoroutine(request);
        }
        Initializing = false;
    }
    // 真实路径
    public string GetRelativePath()
    {
        if (Application.isEditor)
            //return "file://" + System.Environment.CurrentDirectory.Replace("\\", "/");
            return "file://" + Application.streamingAssetsPath;
        //else if (Application.isWebPlayer)
        //    return Path.GetDirectoryName(Application.absoluteURL).Replace("\\", "/") + "/StreamingAssets";
        else if (Application.isMobilePlatform || Application.isConsolePlatform)
            return Application.streamingAssetsPath;
        else
            return "file://" + Application.streamingAssetsPath;
    }

#if UNITY_EDITOR
    public static string GetPlatformFolderForAssetBundles(BuildTarget target)
    {
        switch (target)
        {
            case BuildTarget.Android:
                return "Android";
            case BuildTarget.iOS:
                return "iOS";
            case BuildTarget.WebGL:
                return "WebGL";
            case BuildTarget.StandaloneWindows:
            case BuildTarget.StandaloneWindows64:
                return "Windows";
            case BuildTarget.StandaloneOSXIntel:
            case BuildTarget.StandaloneOSXIntel64:
            case BuildTarget.StandaloneOSX:
                return "OSX";
            default:
                return null;
        }
    }
#endif

    static string GetPlatformFolderForAssetBundles(RuntimePlatform platform)
    {
        switch (platform)
        {
            case RuntimePlatform.Android:
                return "Android";
            case RuntimePlatform.IPhonePlayer:
                return "iOS";
            case RuntimePlatform.WebGLPlayer:
                return "WebGL";
            case RuntimePlatform.WindowsPlayer:
                return "Windows";
            case RuntimePlatform.OSXPlayer:
                return "OSX";
            default:
                return null;
        }
    }
    //IEnumerator Start()
    //{
    //    yield return StartCoroutine(Initialize());
    //}
    public bool Initializing = false;
    public IEnumerator Load<T>(string assetBundleName, string assetName, Action<T> callback)
        where T : UnityEngine.Object
    {
        while (Initializing)
        {
            yield return new WaitForEndOfFrame();
        }
        if (m_AssetBundleManifest == null)
        {
            //Debug.Log("未初始化!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!");
            Initializing = true;
            yield return StartCoroutine(Initialize());
        }
        //Debug.Log(string.Format("在第{0}帧开始加载{1}", Time.frameCount, assetName));

        // 从AB中加载资源
        AssetBundleLoadAssetOperation request = LoadAssetAsync(assetBundleName, assetName, typeof(T));
        if (request == null)
            yield break;
        yield return StartCoroutine(request);

        // 获取资源T
        T asset = request.GetAsset<T>();
        //Debug.Log(string.Format("加载{0}在第{1}帧时{2}", assetName, Time.frameCount, (asset == null ? "失败" : "成功")));
        if (asset != null)
        {
            callback(asset);
            //添加到资源缓存池
            ResourceManager.AddAsset(asset.name, asset);
            UnloadAssetBundle(assetBundleName);
        }
    }
    //protected IEnumerator LoadLevel(string assetBundleName, string levelName, bool isAdditive)
    //{
    //    Debug.Log("Start to load scene " + levelName + " at frame " + Time.frameCount);

    //    // Load level from assetBundle.
    //    AssetBundleLoadOperation request = AssetBundleManager.LoadLevelAsync(assetBundleName, levelName, isAdditive);
    //    if (request == null)
    //        yield break;
    //    yield return StartCoroutine(request);

    //    // This log will only be output when loading level additively.
    //    Debug.Log("Finish loading scene " + levelName + " at frame " + Time.frameCount);
    //}
#if UNITY_EDITOR
    // 是否要在编辑器中模拟assetbundle,而不实际构建它们
    public static bool SimulateAssetBundleInEditor
    {
        get
        {
            if (m_SimulateAssetBundleInEditor == -1)
                m_SimulateAssetBundleInEditor = EditorPrefs.GetBool(kSimulateAssetBundles, true) ? 1 : 0;

            return m_SimulateAssetBundleInEditor != 0;
        }
        set
        {
            int newValue = value ? 1 : 0;
            if (newValue != m_SimulateAssetBundleInEditor)
            {
                m_SimulateAssetBundleInEditor = newValue;
                EditorPrefs.SetBool(kSimulateAssetBundles, value);
            }
        }
    }
#endif

    // 获取已加载的AssetBundle,只在成功下载所有依赖项时返回vaild对象。
    static public LoadedAssetBundle GetLoadedAssetBundle(string assetBundleName, out string error)
    {
        if (m_DownloadingErrors.TryGetValue(assetBundleName, out error))
            return null;

        LoadedAssetBundle bundle = null;
        m_LoadedAssetBundles.TryGetValue(assetBundleName, out bundle);
        if (bundle == null)
            return null;

        // No dependencies are recorded, only the bundle itself is required.
        string[] dependencies = null;
        if (!m_Dependencies.TryGetValue(assetBundleName, out dependencies))
            return bundle;

        // Make sure all dependencies are loaded
        foreach (var dependency in dependencies)
        {
            if (m_DownloadingErrors.TryGetValue(assetBundleName, out error))
                return bundle;

            // Wait all the dependent assetBundles being loaded.
            LoadedAssetBundle dependentBundle;
            m_LoadedAssetBundles.TryGetValue(dependency, out dependentBundle);
            if (dependentBundle == null)
                return null;
        }

        return bundle;
    }

    // Load AssetBundleManifest.
    static public AssetBundleLoadManifestOperation Initialize(string manifestAssetBundleName)
    {
#if UNITY_EDITOR
        // If we're in Editor simulation mode, we don't need the manifest assetBundle.
        if (SimulateAssetBundleInEditor)
            return null;
#endif

        LoadAssetBundle(manifestAssetBundleName, true);
        var operation = new AssetBundleLoadManifestOperation(manifestAssetBundleName, "AssetBundleManifest", typeof(AssetBundleManifest));
        m_InProgressOperations.Add(operation);
        return operation;
    }

    // Load AssetBundle and its dependencies.
    static protected void LoadAssetBundle(string assetBundleName, bool isLoadingAssetBundleManifest = false)
    {
#if UNITY_EDITOR
        // If we're in Editor simulation mode, we don't have to really load the assetBundle and its dependencies.
        if (SimulateAssetBundleInEditor)
            return;
#endif

        if (!isLoadingAssetBundleManifest)
            assetBundleName = RemapVariantName(assetBundleName);

        // Check if the assetBundle has already been processed.
        bool isAlreadyProcessed = LoadAssetBundleInternal(assetBundleName, isLoadingAssetBundleManifest);

        // Load dependencies.
        if (!isAlreadyProcessed && !isLoadingAssetBundleManifest)
            LoadDependencies(assetBundleName);
    }

    // Remaps the asset bundle name to the best fitting asset bundle variant.
    static protected string RemapVariantName(string assetBundleName)
    {
        string[] bundlesWithVariant = m_AssetBundleManifest.GetAllAssetBundlesWithVariant();

        // If the asset bundle doesn't have variant, simply return.
        if (System.Array.IndexOf(bundlesWithVariant, assetBundleName) < 0)
            return assetBundleName;

        string[] split = assetBundleName.Split('.');

        int bestFit = int.MaxValue;
        int bestFitIndex = -1;
        // Loop all the assetBundles with variant to find the best fit variant assetBundle.
        for (int i = 0; i < bundlesWithVariant.Length; i++)
        {
            string[] curSplit = bundlesWithVariant[i].Split('.');
            if (curSplit[0] != split[0])
                continue;

            int found = System.Array.IndexOf(m_Variants, curSplit[1]);
            if (found != -1 && found < bestFit)
            {
                bestFit = found;
                bestFitIndex = i;
            }
        }

        if (bestFitIndex != -1)
            return bundlesWithVariant[bestFitIndex];
        else
            return assetBundleName;
    }

    // Where we actuall call WWW to download the assetBundle.
    static protected bool LoadAssetBundleInternal(string assetBundleName, bool isLoadingAssetBundleManifest)
    {
        // Already loaded.
        LoadedAssetBundle bundle = null;
        m_LoadedAssetBundles.TryGetValue(assetBundleName, out bundle);
        if (bundle != null)
        {
            bundle.m_ReferencedCount++;
            return true;
        }

        // @TODO: Do we need to consider the referenced count of WWWs?
        // In the demo, we never have duplicate WWWs as we wait LoadAssetAsync()/LoadLevelAsync() to be finished before calling another LoadAssetAsync()/LoadLevelAsync().
        // But in the real case, users can call LoadAssetAsync()/LoadLevelAsync() several times then wait them to be finished which might have duplicate WWWs.
        if (m_DownloadingWWWs.ContainsKey(assetBundleName))
            return true;

        WWW download = null;
        string url = m_BaseDownloadingURL + assetBundleName;

        // For manifest assetbundle, always download it as we don't have hash for it.
        if (isLoadingAssetBundleManifest)
            download = new WWW(url);
        else
            download = WWW.LoadFromCacheOrDownload(url, m_AssetBundleManifest.GetAssetBundleHash(assetBundleName), 0);

        m_DownloadingWWWs.Add(assetBundleName, download);

        return false;
    }

    // Where we get all the dependencies and load them all.
    static protected void LoadDependencies(string assetBundleName)
    {
        if (m_AssetBundleManifest == null)
        {
            Debug.LogError("Please initialize AssetBundleManifest by calling AssetBundleManager.Initialize()");
            return;
        }

        // Get dependecies from the AssetBundleManifest object..
        string[] dependencies = m_AssetBundleManifest.GetAllDependencies(assetBundleName);
        if (dependencies.Length == 0)
            return;

        for (int i = 0; i < dependencies.Length; i++)
            dependencies[i] = RemapVariantName(dependencies[i]);

        // Record and load all dependencies.
        m_Dependencies.Add(assetBundleName, dependencies);
        for (int i = 0; i < dependencies.Length; i++)
            LoadAssetBundleInternal(dependencies[i], false);
    }

    // Unload assetbundle and its dependencies.
    static public void UnloadAssetBundle(string assetBundleName)
    {
#if UNITY_EDITOR
        // If we're in Editor simulation mode, we don't have to load the manifest assetBundle.
        if (SimulateAssetBundleInEditor)
            return;
#endif

        //Debug.Log(m_LoadedAssetBundles.Count + " assetbundle(s) in memory before unloading " + assetBundleName);

        UnloadAssetBundleInternal(assetBundleName);
        UnloadDependencies(assetBundleName);

        //Debug.Log(m_LoadedAssetBundles.Count + " assetbundle(s) in memory after unloading " + assetBundleName);
    }

    static protected void UnloadDependencies(string assetBundleName)
    {
        string[] dependencies = null;
        if (!m_Dependencies.TryGetValue(assetBundleName, out dependencies))
            return;

        // Loop dependencies.
        foreach (var dependency in dependencies)
        {
            UnloadAssetBundleInternal(dependency);
        }

        m_Dependencies.Remove(assetBundleName);
    }

    static protected void UnloadAssetBundleInternal(string assetBundleName)
    {
        string error;
        LoadedAssetBundle bundle = GetLoadedAssetBundle(assetBundleName, out error);
        if (bundle == null)
            return;

        if (--bundle.m_ReferencedCount == 0)
        {
            bundle.m_AssetBundle.Unload(false);
            m_LoadedAssetBundles.Remove(assetBundleName);
            //Debug.Log("AssetBundle " + assetBundleName + " has been unloaded successfully");
        }
    }

    void Update()
    {
        // Collect all the finished WWWs.
        var keysToRemove = new List<string>();
        foreach (var keyValue in m_DownloadingWWWs)
        {
            WWW download = keyValue.Value;

            // If downloading fails.
            if (download.error != null)
            {
                m_DownloadingErrors.Add(keyValue.Key, download.error);
                keysToRemove.Add(keyValue.Key);
                continue;
            }

            // If downloading succeeds.
            if (download.isDone)
            {
                //Debug.Log("Downloading " + keyValue.Key + " is done at frame " + Time.frameCount);
                m_LoadedAssetBundles.Add(keyValue.Key, new LoadedAssetBundle(download.assetBundle));
                keysToRemove.Add(keyValue.Key);
            }
        }

        // Remove the finished WWWs.
        foreach (var key in keysToRemove)
        {
            WWW download = m_DownloadingWWWs[key];
            m_DownloadingWWWs.Remove(key);
            download.Dispose();
        }

        // Update all in progress operations
        for (int i = 0; i < m_InProgressOperations.Count;)
        {
            if (!m_InProgressOperations[i].Update())
            {
                m_InProgressOperations.RemoveAt(i);
            }
            else
                i++;
        }
    }

    // Load asset from the given assetBundle.
    static public AssetBundleLoadAssetOperation LoadAssetAsync(string assetBundleName, string assetName, System.Type type)
    {
        AssetBundleLoadAssetOperation operation = null;
#if UNITY_EDITOR
        if (SimulateAssetBundleInEditor)
        {
            string[] assetPaths = AssetDatabase.GetAssetPathsFromAssetBundleAndAssetName(assetBundleName, assetName);
            if (assetPaths.Length == 0)
            {
                Debug.LogError("There is no asset with name \"" + assetName + "\" in " + assetBundleName);
                return null;
            }

            // 根据类型从AssetDatabase中读取资源
            UnityEngine.Object target = AssetDatabase.LoadAssetAtPath(assetPaths[0], type);
            // 加载
            operation = new AssetBundleLoadAssetOperationSimulation(target);
        }
        else
#endif
        {
            LoadAssetBundle(assetBundleName);
            operation = new AssetBundleLoadAssetOperationFull(assetBundleName, assetName, type);

            m_InProgressOperations.Add(operation);
        }

        return operation;
    }

    //    // Load level from the given assetBundle.
    //    static public AssetBundleLoadOperation LoadLevelAsync(string assetBundleName, string levelName, bool isAdditive)
    //    {
    //        AssetBundleLoadOperation operation = null;
    //#if UNITY_EDITOR
    //        if (SimulateAssetBundleInEditor)
    //        {
    //            string[] levelPaths = AssetDatabase.GetAssetPathsFromAssetBundleAndAssetName(assetBundleName, levelName);
    //            if (levelPaths.Length == 0)
    //            {
    //                ///@TODO: The error needs to differentiate that an asset bundle name doesn't exist
    //                //        from that there right scene does not exist in the asset bundle...

    //                Debug.LogError("There is no scene with name \"" + levelName + "\" in " + assetBundleName);
    //                return null;
    //            }

    //            if (isAdditive)
    //                EditorApplication.LoadLevelAdditiveInPlayMode(levelPaths[0]);
    //            else
    //                EditorApplication.LoadLevelInPlayMode(levelPaths[0]);

    //            operation = new AssetBundleLoadLevelSimulationOperation();
    //        }
    //        else
    //#endif
    //        {
    //            LoadAssetBundle(assetBundleName);
    //            operation = new AssetBundleLoadLevelOperation(assetBundleName, levelName, isAdditive);

    //            m_InProgressOperations.Add(operation);
    //        }

    //        return operation;
    //    }
}