using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;

using Cysharp.Threading.Tasks;

using UnityEngine;
using UnityEngine.Events;
using UnityEngine.Networking;
namespace XenseAR
{
    public class AssetBundleLoader
    {

        MonoBehaviour masterMonoBehaviour;
        string bundleURL = "";
        private string bundleName = "";
        UnityWebRequest www;

        public AssetBundleLoader(MonoBehaviour monoBehaviour, string bundleUrl, string bundleName)
        {
            masterMonoBehaviour = monoBehaviour;
            this.bundleURL = bundleUrl;
            this.bundleName = bundleName;
        }

        public AssetBundleLoader(MonoBehaviour monoBehaviour)
        {
            masterMonoBehaviour = monoBehaviour;
        }

        public async UniTask<bool> LoadAsync(Action<GameObject> instantiateObject = null, bool instantiate = true, UnityAction callback = null, CancellationToken token = default)
        {
            try
            {
                return await DownloadAndCacheAsync(instantiateObject, instantiate, callback, token);
            }
            catch (Exception e)
            {
                Debug.Log("Exception " + e);
                return false;
            }
        }

        async UniTask<bool> DownloadAndCacheAsync(Action<GameObject> instantiateObject = null, bool instantiate = true,
          UnityAction callback = null, CancellationToken token = default)
        {
#if UNITY_EDITOR_WIN
            string bundleURL_post = bundleURL + Defines.standaloneWindows64Postfix;
#elif UNITY_EDITOR_OSX
            string bundleURL_post = bundleURL + Defines.iosPostfix;
#else
#if UNITY_ANDROID
            string bundleURL_post = bundleURL + Defines.androidPostfix;
#endif
#if UNITY_IOS
            string bundleURL_post = bundleURL + Defines.iosPostfix;
#endif
#if UNITY_WEBGL
            string bundleURL_post = bundleURL + Defines.webglPostfix;
#endif
#if UNITY_WSA
            string bundleURL_post = bundleURL + Defines.wsaPlayerPostfix;
#endif
#if UNITY_STANDALONE_WIN
            string bundleURL_post = bundleURL + Defines.standaloneWindows64Postfix;
#endif
#if UNITY_STANDALONE_OSX
            string bundleURL_post = bundleURL + Defines.standaloneOSXPostfix;
#endif
#endif
            string bundlePath = bundleURL_post;
#if !UNITY_WEBGL
            bool withManifest = true;

            await UniTask.WaitUntil(() => Caching.ready == true);

            // Clear cache for previous versions of the asset bundle
            if (!string.IsNullOrEmpty(bundleName))
                Caching.ClearOtherCachedVersions(bundleName, Hash128.Parse("0"));

            www = UnityWebRequest.Get(bundleURL_post + ".manifest?r=" + (UnityEngine.Random.value * 9999999));

            // Notice: Starting from 3.1.1, some postfix were change, cause request to fail
            // Add a fallback request to check the original url availability
            try
            {
                await www.SendWebRequest();
            }
            catch
            {
                // Incase URL not lead to anywhere, C# throw a protocal error, catch it here since we already expect it, but do nothing
            }
            if (www.result != UnityWebRequest.Result.Success)
            {
                // Use URL fallback, if it fail, return, else use the fallback url
                bundleURL_post = bundleURL;
                bundlePath = bundleURL_post;
                www = UnityWebRequest.Get(bundleURL_post + ".manifest?r=" + (UnityEngine.Random.value * 9999999));

                await www.SendWebRequest();

                if (www.result != UnityWebRequest.Result.Success)
                {
                    Debug.LogError($"Asset Bundle Request fail with result:{www.result}. Error: {www.error}");
                    www.Dispose();
                    www = null;
                    return false;
                }
            }

            Hash128 hashString = default(Hash128);

            if (www.downloadHandler.text.Contains("ManifestFileVersion"))
            {
                var hashes = www.downloadHandler.text
                            // separate the text into lines, in an list/array of strings
                            .Split(Environment.NewLine.ToCharArray(), StringSplitOptions.RemoveEmptyEntries)
                            // select only the lines that starts with 'lookFor', ignoring white spaces
                            .Where(x => x.Trim().StartsWith("Hash:"))
                            // convert result to array
                            .ToArray();
                string myParsedHash = hashes
                    // on the manifest file, the hash we need is always the first (AssetFileHash), so here we just take the first one found.
                    .FirstOrDefault()
                    // ignore whitespaces
                    .Trim();
                myParsedHash = string.IsNullOrEmpty(myParsedHash) ? "" :
                    // get the right part of the string after what 'lookfor'. +1 because the substring index starts at zero
                    myParsedHash.Substring("Hash:".Length + 1);

                hashString = Hash128.Parse(myParsedHash.Trim());

                if (hashString.isValid)
                {
                    if (Caching.IsVersionCached(bundleURL_post, hashString))
                    {
                        Debug.Log("Bundle with this hash is already cached!");
                    }
                    else
                    {
                        Debug.Log("No cached version found for this hash..");
                    }
                }
                else
                {
                    Debug.LogError("Invalid hash: " + hashString);
                    www.Dispose();
                    www = null;
                    return false;
                }
            }
            else
            {
                withManifest = false;
            }
            if (withManifest)
            {
                bundlePath = bundleURL_post + "?r=" + (UnityEngine.Random.value * 9999999);
                www = UnityWebRequestAssetBundle.GetAssetBundle(bundlePath, hashString, 0);
            }
            else
            {
                www = UnityWebRequestAssetBundle.GetAssetBundle(bundleURL_post);
            }
#else
            www = UnityWebRequestAssetBundle.GetAssetBundle(bundleURL_post);
#endif

#if UNITY_EDITOR
            www.timeout = 0;
#endif
            await www.SendWebRequest().WithCancellation(token);
            if (www.result != UnityWebRequest.Result.Success)
            {
                //Result of DataProcessingError will not result in www.error, but still fail to use
                Debug.LogError($"Asset Bundle Request fail with result:{www.result}. Error: {www.error}");
                www.Dispose();
                www = null;
                return false;
            }
            //AssetBundle myLoadedAssetBundle = AssetBundle.GetAllLoadedAssetBundles().FirstOrDefault(bundle => bundle.name == bundlePath);
            IEnumerable<AssetBundle> myLoadedAssetBundle = AssetBundle.GetAllLoadedAssetBundles();
            while (myLoadedAssetBundle.Where(bundle => bundle.name == bundlePath).Count() > 0)
            {
                await UniTask.Yield();
            }
            AssetBundle bundle = DownloadHandlerAssetBundle.GetContent(www);
            if (bundle == null)
            {
                Debug.LogError($"Fail to get content with bundle: {www.url}");
                www.Dispose();
                www = null;
                return false;
            }
            GameObject bundlePrefab = null;
            if (instantiate)
            {
                if (bundleName == "")
                {
                    bundleName = bundle.GetAllAssetNames()[0];
                }
                AssetBundleRequest request = bundle.LoadAssetAsync<GameObject>(bundleName);
                await request.ToUniTask();
                bundlePrefab = request.asset as GameObject;

                if (bundlePrefab != null)
                {
                    var go = GameObject.Instantiate(bundlePrefab, masterMonoBehaviour.transform);
                    ReApplyShaders.Start(go);
                    if (instantiateObject != null) instantiateObject.Invoke(go);
                }
            }

            www.Dispose();
            www = null;

            await Resources.UnloadUnusedAssets();
            await bundle.UnloadAsync(false);
            bundle = null;
            callback?.Invoke();
            return true;
        }

        public float getDownloadProgress()
        {
            if (www != null) {
                return www.downloadProgress;
            }
            return 0;
        }
    }
}
