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

namespace SFramework
{
    enum Update_State {
        usInvaild,

        usStart,
        usShowUI,
        usHotfixLua,
        usGetResVersion,
        usCalcUpdateResInfo,
        usDownloadResource,
        usFinish,
    }
    public class SUpdateManager : MonoBehaviour
    {
        private Update_State _currentState;
        private GameObject _logicInstance;
        private SUIPanelUpdate _uiPanelUpdate;
        private SUIPanelTips _uiPanelTips;
        private SVersionInfo _localResVersion, _remoteResVersion;
        private int _willDownloadBytes;
        private List<SFileInfo> _willDownloadFileInfoList = new List<SFileInfo>();

        void Awake()
        {
            _currentState = Update_State.usStart;

            Debug.Log("Application.dataPath            -> " + Application.dataPath);
            Debug.Log("Application.streamingAssetsPath -> " + Application.streamingAssetsPath);
            Debug.Log("Application.persistentDataPath  -> " + Application.persistentDataPath);
            _logicInstance = GameObject.Find("LogicInstance");
            Debug.Assert(_logicInstance != null);
        }

        void Start()
        {
            _currentState = Update_State.usShowUI;
            ShowUpdateUI();
            AddUIController();
#pragma warning disable 0162
            _currentState = Update_State.usHotfixLua;
            if (!SFrameworkDef.isEnableHotfix) _currentState = Update_State.usFinish;
#pragma warning restore
            TryUpdate();
        }

        private void ShowUpdateUI()
        {
            // Add CanvasUI
            GameObject canvasUpdate = SResourceManager.LoadResource("prefabs/canvas_update.prefab") as GameObject;
            GameObject.Instantiate(canvasUpdate);
        }

        private void AddUIController()
        {
            // Add UIController
            _uiPanelUpdate = _logicInstance.AddComponent<SUIPanelUpdate>();
            _uiPanelTips = _logicInstance.AddComponent<SUIPanelTips>();
        }

        public void TryUpdate()
        {
            StartCoroutine(UpdateLogic());
        }

        public void UpdateStateInfo(string stateInfo)
        {
            Debug.Log("SUpdateManager -> " + stateInfo);

            _uiPanelUpdate.SetUpdateInfo(stateInfo);
        }

        IEnumerator UpdateLogic()
        {
            yield return null;

            switch (_currentState)
            {
                case Update_State.usHotfixLua:
                    StartCoroutine(TryHotfixLuaLogic());
                    break;
                case Update_State.usGetResVersion:
                    StartCoroutine(TryGetResVersionLogic());
                    break;
                case Update_State.usCalcUpdateResInfo:
                    StartCoroutine(TryCalcUpdateResInfoLogic());
                    break;
                case Update_State.usDownloadResource:
                    StartCoroutine(TryDownloadResource());
                    break;
                case Update_State.usFinish:
                    StartCoroutine(TryFinishLogic());
                    break;
                default:
                    Debug.Assert(false, "Update_State Error -> " + _currentState.ToString());
                    break;
            }

            yield return new WaitForSeconds(1.0f);
            // string resourceVersionURL = SFrameworkDef.resourceVersionUrl;
            // Step 3 : Switch To Start Scene.
        }

        IEnumerator TryHotfixLuaLogic()
        {
            // Step 1 : Try Hotfix.
            yield return null;

            UpdateStateInfo("Communication With Server ...");
            yield return new WaitForSeconds(1.0f);

            string hotfixLuaUrl = SFrameworkDef.hotfixLuaUrl;
            UnityEngine.Networking.UnityWebRequest luaRequest = 
                UnityEngine.Networking.UnityWebRequest.Get(hotfixLuaUrl);
            luaRequest.timeout = SFrameworkDef.httpRequestTimeoutTime;
            yield return luaRequest.SendWebRequest();
            //if (luaRequest.isNetworkError || luaRequest.isHttpError)
            if (luaRequest.result == UnityEngine.Networking.UnityWebRequest.Result.ConnectionError || luaRequest.result == UnityEngine.Networking.UnityWebRequest.Result.ProtocolError)
            {
                Debug.LogError("SUpdateManager -> UpdateLogic luaRequest Error -> " + luaRequest.error);
                UpdateStateInfo("Communication With Server ... Error: " + luaRequest.error);

                // show tips and notice try again.
                _uiPanelTips.ShowTips(
                    "Connect Failed", 
                    "Connection To Server Failed!\nPlease Check Your Network State.\nClick Yes Try Again. Click No Exit Game.", 
                    delegate(){
                        Debug.Log("SUpdateManager -> Click Tips Yes -> TryUpdate Again!");
                        TryUpdate();
                    }, 
                    delegate(){
                        Debug.Log("SUpdateManager -> Click Tips No  -> Exit Game!");
                        Application.Quit();
                    }
                );
                yield break;
            }
            string luaCode = luaRequest.downloadHandler.text;
            SLuaEnv.Instance.DoString(luaCode);
            
            UpdateStateInfo("Communication With Server ... Success");
            _currentState = Update_State.usGetResVersion;
            TryUpdate();
        }

        IEnumerator TryGetResVersionLogic()
        {
            // Step 2 : Try Get Res Version Info And Check IsNeed Update Resource.
            yield return null;

            UpdateStateInfo("Commuication With Resource Server ...");
            yield return new WaitForSeconds(1.0f);

            string platformName = SPlatformInfo.GetBuildPlatformName();
            string resVersionUrl = SFrameworkDef.resourceVersionUrlBase + platformName + "/" + SFrameworkDef.resourceVersionName;
            UnityEngine.Networking.UnityWebRequest resVersionRequest = 
                UnityEngine.Networking.UnityWebRequest.Get(resVersionUrl);
            resVersionRequest.timeout = SFrameworkDef.httpRequestTimeoutTime;
            yield return resVersionRequest.SendWebRequest();
            if (resVersionRequest.result == UnityEngine.Networking.UnityWebRequest.Result.ConnectionError || resVersionRequest.result == UnityEngine.Networking.UnityWebRequest.Result.ProtocolError)
            {
                Debug.LogError("SUpdateManager -> UpdateLogic resVersionRequest Error -> " + resVersionRequest.error);
                UpdateStateInfo("Communication With Resource Server ... Error: " + resVersionRequest.error);
                
                // show tips and notice try again.
                _uiPanelTips.ShowTips(
                    "Connect Failed",
                    "Connection To Resrouce Server Failed!\nPlease Check Your Network State.\nClick Yes Try Again. Click No Exit Game.",
                    delegate() {
                        Debug.Log("SUpdateManager -> Click Tips Yes -> Try Again!");
                        TryUpdate();
                    },
                    delegate() {
                        Debug.Log("SUpdateManager -> Click Tips No  -> Exit Game!");
                        Application.Quit();
                    }
                );
                yield break;
            }
            // get version.json success start to calc download files info.
            string versionJson = resVersionRequest.downloadHandler.text;
            Debug.Log("remoteVersionJson -> " + versionJson);

            yield return new WaitForSeconds(1.0f);
            SVersionInfo remoteVersion = LitJson.JsonMapper.ToObject<SVersionInfo>(versionJson);
            // Debug.Log("Remote Version -> " + remoteVersionInfo.version);
            SVersionInfo localVersion = SResourceManager.GetLocalVersionInfo();
            // Debug.Log("Local  Version -> " + localVersionInfo.version);
            UpdateStateInfo(string.Format("Remote Version: {0} Local Version: {1}", remoteVersion.version, localVersion.version));
            yield return new WaitForSeconds(1.0f);

            _localResVersion = localVersion;
            _remoteResVersion = remoteVersion;
            yield return new WaitForSeconds(1.0f);
            UpdateStateInfo("Commuication With Resource Server ... Success");

            _currentState = Update_State.usCalcUpdateResInfo;
            if (localVersion.version == remoteVersion.version)
            {
                // No need to update resource.
                _currentState = Update_State.usFinish;
            }
            // Debug.Log("currentState -> " + currentState.ToString());
            TryUpdate();
        }

        IEnumerator TryCalcUpdateResInfoLogic()
        {
            // Step 3: Try Update Resource By LocalVersionInfo And RemoteVersionInfo.
            yield return null;

            UpdateStateInfo("Prepare Calc Update Resource ...");
            // Get Update File Count And File Total Size And Update FileInfoList.
            Debug.Assert(_localResVersion != null, "TryUpdateResourceLogic -> localResVersion = null");
            Debug.Assert(_remoteResVersion != null, "TryUpdateResourceLogic -> remoteResVersion = null");
            Debug.Assert(_localResVersion.version != _remoteResVersion.version, "TryUpdateResourceLogic -> LVersion == RVersion");

            foreach (SFileInfo oneFileInfo in _remoteResVersion.fileInfo)
            {
                SFileInfo localFileInfo = _localResVersion.fileInfo.Find(target => 
                    {
                        if (target.fileName == oneFileInfo.fileName)
                            return true;
                        else
                            return false;
                    }
                );

                // File Existed And Don't Need Update.
                if (localFileInfo != null && localFileInfo.hashInfo == oneFileInfo.hashInfo)
                    continue;

                // File Is Not Existed Or Need Update.
                _willDownloadBytes += oneFileInfo.fileSize;
                _willDownloadFileInfoList.Add(oneFileInfo);
            }

            UpdateStateInfo("Prepare Calc Update Resource ... Success");
            // Show Tips For Player.
            // If Choose No Exit Game.
            // If Choose Yes Start Update Resource.
            _uiPanelTips.ShowTips(
                "UpdateInfo",
                string.Format(
                    "Found new version:{0} ...\nFile Count:{1} FileSize:{2}", 
                    _remoteResVersion.version, _willDownloadFileInfoList.Count, 
                    System.Math.Round(_willDownloadBytes / 1024f / 1024f, 2) + "MB"
                ),
                delegate () {
                    Debug.Log("SUpdateManager -> Click Tips Yes -> Start Download Logic");
                    _currentState = Update_State.usDownloadResource;
                    TryUpdate();
                },
                delegate () {
                    Debug.Log("SUpdateManager -> Click Tips No  -> Exit Game");
                    Application.Quit();
                }
            );
        }

        IEnumerator TryDownloadResource()
        {
            // UnityWebRequest.GetAssetBundle(string url, int version, uint crc)
            // If crc is not zero, engine will compare crc.
            // UnityEngine.Networking.UnityWebRequest.GetAssetBundle("", 0, 0);

            yield return null;

            UpdateStateInfo("Prepare To Clean Local Resources ...");
            yield return new WaitForSeconds(1.0f);

            // Download Resource Logic Start:
            // Step 1: Try To Delete All Local File(PersistentPath) That Need Update. 
            int cleanedCount = 1;
            int fileTotalCount = _willDownloadFileInfoList.Count;
            foreach (SFileInfo oneFileInfo in _willDownloadFileInfoList)
            {
                SResourceManager.TryDeleteAssetBundle(oneFileInfo.fileName);
                UpdateStateInfo(string.Format(
                    "Clean Local Files({0}/{1}) -> {2}", cleanedCount, fileTotalCount, oneFileInfo.fileName
                ));
                _uiPanelUpdate.SetProgressPercent(cleanedCount * 1.0f / fileTotalCount);
                cleanedCount++;
                yield return new WaitForSeconds(1.0f);
            }
            UpdateStateInfo("Clean All Local Resources ... Success");
            yield return new WaitForSeconds(1.0f);

            // Step 2: Download Each File One By One And Write It On Right Path.
            UpdateStateInfo("Prepare To Download Remote Resources ...");
            float downloadStartTime = UnityEngine.Time.realtimeSinceStartup;
            float downloadedBytes   = 0.0f;
            string downloadSpeed    = "";
            foreach (SFileInfo oneFileInfo in _willDownloadFileInfoList)
            {
                float baseDownloadedBytes = downloadedBytes;
                string platformName = SPlatformInfo.GetBuildPlatformName();
                string downloadLink = SFrameworkDef.resourceVersionUrlBase + platformName + "/" + oneFileInfo.fileName;
                UnityEngine.Networking.UnityWebRequest resDownloadRequest = 
                    // UnityEngine.Networking.UnityWebRequest.GetAssetBundle(downloadLink, (uint)remoteResVersion.version, 0);
                    UnityEngine.Networking.UnityWebRequest.Get(downloadLink);

                float downloadPercent = 0.0f;
                _uiPanelUpdate.SetProgressPercent(downloadPercent);
                resDownloadRequest.SendWebRequest();
                while (!resDownloadRequest.isDone)
                {
                    downloadPercent = resDownloadRequest.downloadProgress * 100.0f;
                    if (downloadPercent < 0) downloadPercent = 0;
                    System.Math.Round(downloadPercent, 2);
                    float fileDownloadBytes = baseDownloadedBytes + resDownloadRequest.downloadedBytes;
                    downloadSpeed = SToolFunction.GetDownloadSpeed(fileDownloadBytes, UnityEngine.Time.realtimeSinceStartup - downloadStartTime);

                    UpdateStateInfo(string.Format("Downloading: {0} From {1} \nPercent: {2} Speed:{3}", 
                        oneFileInfo.fileName, downloadLink, downloadPercent, downloadSpeed
                    ));
                    _uiPanelUpdate.SetProgressPercent(downloadPercent);
                    yield return null;
                }
                _uiPanelUpdate.SetProgressPercent(1.0f);

                downloadedBytes += resDownloadRequest.downloadedBytes;
                downloadSpeed = SToolFunction.GetDownloadSpeed(downloadedBytes, UnityEngine.Time.realtimeSinceStartup - downloadStartTime);
                UpdateStateInfo(string.Format("Downloading: {0} \nPercent: {1} Speed:{2} Success", oneFileInfo.fileName, downloadPercent, downloadSpeed));
                yield return new WaitForSeconds(1.0f);

                SResourceManager.SaveAssetBundle(oneFileInfo.fileName, resDownloadRequest.downloadHandler.data);
                UpdateStateInfo(string.Format("Download And Save {0} Success", oneFileInfo.fileName));
                yield return new WaitForSeconds(1.0f);
            }
            downloadSpeed = SToolFunction.GetDownloadSpeed(downloadedBytes, UnityEngine.Time.realtimeSinceStartup - downloadStartTime);
            UpdateStateInfo("All Remote Resource Download Compelete ... Speed:" + downloadSpeed);

            // Step 3: Try Write version.json(Remote Version) To PersistentPath.
            SResourceManager.SaveRemoteVersion(_remoteResVersion);

            // Step 4: Change currentState And Call TryUpdate().
            UpdateStateInfo("All Files Download Finish ...");
            yield return new WaitForSeconds(1.0f);
            _currentState = Update_State.usFinish;
            TryUpdate();
        }

        IEnumerator TryFinishLogic()
        {
            // Step 4: Try To Switch Scene To Start.unity.
            yield return null;

            UpdateStateInfo("All Data Updated ... Success");
            yield return new WaitForSeconds(1.0f);

            yield return new WaitForSeconds(1.0f);

            SResourceManager.LoadResource("scenes/start.unity");
            UnityEngine.SceneManagement.SceneManager.LoadScene("Start");
        }

    }
}
