
require("Base/GlobalFunctions.lua")

local tUpdateConfig = {
    -- ignoreUpdate = true,
    ignoreUpdate = false,
    hotfixLuaUrl = "http://192.168.30.87:80/files/sframework_update/hotfix.lua",
    httpRequestTimeoutTime = 3,
    resourceVersionUrlBase = "http://192.168.30.87:80/files/sframework_update/Bundles/";
    resourceVersionName = "version.json",
}

local SUpdateManager = SUpdateManager or {}

local tUpdateState = {
    usInvaild   = "usInvaild",

    usStart             = "usStart",
    usShowUI            = "usShowUI",
    usHotfixLua         = "usHotfixLua",
    usGetResVersion     = "usGetResVersion",
    usCalcUpdateResInfo = "usCalcUpdateResInfo",
    usDownloadResource  = "usDownloadResource",

    usFinish            = "usFinish"
}
SUpdateManager._updateState = tUpdateState.usInvaild

SUpdateManager._rVersionInfo = nil
SUpdateManager._lVersionInfo = nil
SUpdateManager._rVersionInfoJson = nil
SUpdateManager._willDownloadBytes = 0
SUpdateManager._willDownloadFileList = nil

local function stepState(newState) 
    local oldState = SUpdateManager._updateState
    print("----> SUpdateManager StepState " ..  oldState .. " -> " .. newState)
    SUpdateManager._updateState = newState
end

local function retryUpdate() 
    print("----------> retryUpdate")
    require("Base/CoroutineHelper.lua"):Start(SUpdateManager.ProcessUpdate)
end

function SUpdateManager.Awake(luaRoot) 
    print("SUpdateManager.Awake ~")

    local dataPath              = CS.UnityEngine.Application.dataPath
    local streamingAssetsPath   = CS.UnityEngine.Application.streamingAssetsPath
    local persistentDataPath    = CS.UnityEngine.Application.persistentDataPath

    print("Application.dataPath             -> " .. dataPath)
    print("Application.streamingAssetsPath  -> " .. streamingAssetsPath)
    print("Application.persistentDataPath   -> " .. persistentDataPath)
end

function SUpdateManager.Start(luaRoot) 
    print("SUpdateManager.Start ~")
    stepState(tUpdateState.usStart)

    -- Register Event
    local eventDispatchCenter = require("Base/SEventDispatchCenter.lua")
    eventDispatchCenter:RegisterEvent(eventDispatchCenter.EventType.UPDATE_RETRY, retryUpdate)

    -- Process Show Update UI.
    require("Base/UIHelper.lua"):OpenUI("Update/SUIPanelUpdate.lua")
    stepState(tUpdateState.usShowUI)

    -- Check Ignore Update.
    if tUpdateConfig.ignoreUpdate == true then
        stepState(tUpdateState.usFinish)
        print("SUpdateManager.ignoreUpdate == true, Jump Update!")
        require("Base/CoroutineHelper.lua"):Start(SUpdateManager.ProcessUpdate)
        return
    end

    -- Process Update.
    stepState(tUpdateState.usHotfixLua)
    require("Base/CoroutineHelper.lua"):Start(SUpdateManager.ProcessUpdate)
end

function SUpdateManager.OnDestroy() 
    print("SUpdateManager.OnDestroy ~")
    local eventDispatchCenter = require("Base/SEventDispatchCenter.lua")
    eventDispatchCenter:UnregisterEvent(eventDispatchCenter.EventType.UPDATE_RETRY, retryUpdate)
end

function SUpdateManager.tryHotfixLuaLogic() 
    print("----> Process usHotfixLua Start  ...")
    require("Base/CoroutineHelper.lua"):WaitForEndOfFrame()

    local eventDispatchCenter = require("Base/SEventDispatchCenter.lua")
    eventDispatchCenter:DispatchEvent(eventDispatchCenter.EventType.UPDATE_STATE_INFO, "Communication With Server ...")
    require("Base/CoroutineHelper.lua"):Wait(1)

    local hotfixLuaUrl = tUpdateConfig.hotfixLuaUrl
    --print("-------------> hotfixLuaUrl: " .. hotfixLuaUrl)
    local luaRequest = CS.UnityEngine.Networking.UnityWebRequest.Get(hotfixLuaUrl)
    luaRequest.timeout = tUpdateConfig.httpRequestTimeoutTime
    require("Base/CoroutineHelper.lua"):WaitSendWebRequest(luaRequest)
    if luaRequest.result == CS.UnityEngine.Networking.UnityWebRequest.Result.ConnectionError or luaRequest.result == CS.UnityEngine.Networking.UnityWebRequest.Result.ProtocolError then
        Warn("SUpdateManager -> UpdateLogic luaRequest Error -> " .. tostring(luaRequest.error))
        eventDispatchCenter:DispatchEvent(eventDispatchCenter.EventType.UPDATE_STATE_INFO, "Communication With Server ... Error: " .. tostring(luaRequest.error))
        eventDispatchCenter:DispatchEvent(eventDispatchCenter.EventType.UPDATE_SHOW_TIPS, "Connect Failed", "Connection To Resrouce Server Failed!\nPlease Check Your Network State.\nClick Yes Try Again. Click No Exit Game.")
        --print("-------> Hotfix Step Failed")
        return
    end

    local luaCode = luaRequest.downloadHandler.text
    CS.SFramework.SLuaEnv.Instance:DoString(luaCode)

    print("----> Process usHotfixLua Finish ...")
    eventDispatchCenter:DispatchEvent(eventDispatchCenter.EventType.UPDATE_STATE_INFO, "Communication With Server ... Success")
    require("Base/CoroutineHelper.lua"):Wait(1)
    stepState(tUpdateState.usGetResVersion)
    require("Base/CoroutineHelper.lua"):Start(SUpdateManager.ProcessUpdate)
end

function SUpdateManager.tryGetResVersionLogic()
    print("----> Process usGetResVersion Start  ...")
    require("Base/CoroutineHelper.lua"):WaitForEndOfFrame()

    local eventDispatchCenter = require("Base/SEventDispatchCenter.lua")
    eventDispatchCenter:DispatchEvent(eventDispatchCenter.EventType.UPDATE_STATE_INFO, "Communication With Resource Server ...")
    require("Base/CoroutineHelper.lua"):Wait(1)

    local platformName = CS.SFramework.SPlatformInfoR.GetBuildPlatformName()
    local resVersionUrl = tUpdateConfig.resourceVersionUrlBase .. platformName .. "/" .. tUpdateConfig.resourceVersionName
    --print("------------> resVersionUrl: " .. tostring(resVersionUrl))

    local luaRequest = CS.UnityEngine.Networking.UnityWebRequest.Get(resVersionUrl)
    luaRequest.timeout = tUpdateConfig.httpRequestTimeoutTime
    require("Base/CoroutineHelper.lua"):WaitSendWebRequest(luaRequest)
    if luaRequest.result == CS.UnityEngine.Networking.UnityWebRequest.Result.ConnectionError or luaRequest.result == CS.UnityEngine.Networking.UnityWebRequest.Result.ProtocolError then
        -- Notice User, Choose;
        Warn("SUpdateManager -> UpdateLogic resVersionRequest Error -> " .. tostring(luaRequest.error))
        eventDispatchCenter:DispatchEvent(eventDispatchCenter.EventType.UPDATE_STATE_INFO, "Communication With Resource Server ... Error: " .. tostring(luaRequest.error))
        --print("-------> GetResVersion Step Failed")
        return
    end

    local jsonConverter = require("Base/json.lua")
    local remoteVersionJson = luaRequest.downloadHandler.text
    print("remoteVersionJson -> " .. remoteVersionJson)
    local localVersionJson = CS.SFramework.SResourceManagerR.GetLocalVersionJson()
    print("localVersionJson  -> " .. localVersionJson)
    --require("Base/CoroutineHelper.lua"):Wait(1)

    local remoteVersionInfo = jsonConverter:decode(remoteVersionJson)
    local localVersionInfo  = jsonConverter:decode(localVersionJson)

    local rversion = remoteVersionInfo.version
    local lversion = localVersionInfo.version
    local stateMessage = string.format("Remote Version: %d Local Version: %d", rversion, lversion)
    print(stateMessage)
    eventDispatchCenter:DispatchEvent(eventDispatchCenter.EventType.UPDATE_STATE_INFO, stateMessage)
    SUpdateManager._lVersionInfo = localVersionInfo 
    SUpdateManager._rVersionInfo = remoteVersionInfo
    SUpdateManager._rVersionInfoJson = remoteVersionJson
    require("Base/CoroutineHelper.lua"):Wait(1)

    eventDispatchCenter:DispatchEvent(eventDispatchCenter.EventType.UPDATE_STATE_INFO, "Commuication With ResourceServer ... Success")
    require("Base/CoroutineHelper.lua"):Wait(1)

    if rversion == lversion then
        print("lversion == rversion no need update.")
        stepState(tUpdateState.usFinish)
        require("Base/CoroutineHelper.lua"):Start(SUpdateManager.ProcessUpdate)
        return
    end

    print("----> Process usGetResVersion Finish ...")
    --require("Base/CoroutineHelper.lua"):Wait(1)
    stepState(tUpdateState.usCalcUpdateResInfo)
    require("Base/CoroutineHelper.lua"):Start(SUpdateManager.ProcessUpdate)
end

function SUpdateManager.tryCalcUpdateResInfoLogic()
    print("----> Process usCalcUpdateResInfo Start  ...")
    require("Base/CoroutineHelper.lua"):WaitForEndOfFrame()

    local eventDispatchCenter = require("Base/SEventDispatchCenter.lua")
    eventDispatchCenter:DispatchEvent(eventDispatchCenter.EventType.UPDATE_STATE_INFO, "Prepare Calc Update Resource ...")
    require("Base/CoroutineHelper.lua"):Wait(1)

    local lVersionInfo = SUpdateManager._lVersionInfo
    local rVersionInfo = SUpdateManager._rVersionInfo

    local localResourceCache = {}
    local localResourceCount = 0
    for _, oneFileInfo in pairs(lVersionInfo.fileInfo) do
        localResourceCache[oneFileInfo.fileName] = oneFileInfo
        localResourceCount = localResourceCount + 1
    end
    print("----> Cache Local Resource Count: " .. tostring(localResourceCount))

    local willDownloadFileCount = 0
    local willDownloadBytes = 0
    local willDownloadFileList = {}
    for _, oneFileInfo in pairs(rVersionInfo.fileInfo) do
        local lFile = localResourceCache[oneFileInfo.fileName]
        -- lFile == nil => add file     edit file
        if lFile == nil or (lFile ~= nil and lFile.hashInfo ~= oneFileInfo.hashInfo) then
            willDownloadFileCount = willDownloadFileCount + 1
            willDownloadBytes = willDownloadBytes + oneFileInfo.fileSize
            willDownloadFileList[oneFileInfo.fileName] = oneFileInfo
        end
        -- remove file
        localResourceCache[oneFileInfo.fileName] = nil
    end

    for _, removeFileInfo in pairs(localResourceCache) do
        willDownloadFileCount = willDownloadFileCount + 1
        willDownloadBytes = willDownloadBytes + removeFileInfo.fileSize
        willDownloadFileList[removeFileInfo.fileName] = removeFileInfo
    end

    SUpdateManager._willDownloadBytes = willDownloadBytes
    SUpdateManager._willDownloadFileList = willDownloadFileList
    SUpdateManager._willDownloadFileCount = willDownloadFileCount
    print("----> will download file count: " .. tostring(willDownloadFileCount))
    eventDispatchCenter:DispatchEvent(eventDispatchCenter.EventType.UPDATE_STATE_INFO, "Prepare Calc Update Resource ... Success")

    local totalSize = CS.System.Math.Round(willDownloadBytes / 1024 / 1024, 2)
    local tipsInfo = string.format(
        "Found new version:%s ...\nFile Count:%d FileSize:%s MB", 
        rVersionInfo.version, willDownloadFileCount, tostring(totalSize)
    )
    eventDispatchCenter:DispatchEvent(eventDispatchCenter.EventType.UPDATE_SHOW_TIPS, "Download Tips", tipsInfo)
    print("----> Process usCalcUpdateResInfo Finish ...")

    stepState(tUpdateState.usDownloadResource)
    --require("Base/CoroutineHelper.lua"):Start(SUpdateManager.ProcessUpdate)
end

function SUpdateManager.tryDownloadResourceLogic()
    print("----> Process usDownloadResource Start  ...")
    require("Base/CoroutineHelper.lua"):WaitForEndOfFrame()

    local eventDispatchCenter = require("Base/SEventDispatchCenter.lua")
    eventDispatchCenter:DispatchEvent(eventDispatchCenter.EventType.UPDATE_STATE_INFO, "Prepare To Clean Local Resources ...")
    require("Base/CoroutineHelper.lua"):Wait(1)

    local willDownloadByte = SUpdateManager._willDownloadBytes
    local willDownloadFileList = SUpdateManager._willDownloadFileList
    local willDownloadFileCount = SUpdateManager._willDownloadFileCount

    -- Step 1: Try To Delete All Local File(PersistentPath) That Need Update.
    local cleanedCount = 1
    for fileName, fileInfo in pairs(willDownloadFileList) do
        CS.SFramework.SResourceManagerR.TryDeleteAssetBundle(fileInfo.fileName)
        local stateMessage = string.format("Clean Local Files(%d/%d) -> %s", cleanedCount, willDownloadFileCount, fileName)
        eventDispatchCenter:DispatchEvent(eventDispatchCenter.EventType.UPDATE_STATE_INFO, stateMessage)
        cleanedCount = cleanedCount + 1
        eventDispatchCenter:DispatchEvent(eventDispatchCenter.EventType.UPDATE_PROGRESS_PERCENT, cleanedCount / willDownloadFileCount)
        require("Base/CoroutineHelper.lua"):Wait(1)
    end
    print("----> Success Clean Local File Count: " .. tostring(cleanedCount))
    eventDispatchCenter:DispatchEvent(eventDispatchCenter.EventType.UPDATE_STATE_INFO, "Clean All Local Resources ... Success")
    require("Base/CoroutineHelper.lua"):Wait(1)

    -- Step 2: Download Each File One By One And Write It On Right Path.
    eventDispatchCenter:DispatchEvent(eventDispatchCenter.EventType.UPDATE_STATE_INFO, "Prepare To Download Remote Resources ...")
    local downloadStartTime = CS.UnityEngine.Time.realtimeSinceStartup
    local downloadedBytes = 0
    local downloadSpeed = ""
    for fileName, fileInfo in pairs(willDownloadFileList) do
        local baseDownloadedBytes = downloadedBytes
        local platformName = CS.SFramework.SPlatformInfoR.GetBuildPlatformName()
        local donwloadLink = tUpdateConfig.resourceVersionUrlBase .. platformName .. "/" .. fileInfo.fileName
        local luaRequest = CS.UnityEngine.Networking.UnityWebRequest.Get(donwloadLink)
        luaRequest.timeout = tUpdateConfig.httpRequestTimeoutTime

        local downloadPercent = 0
        eventDispatchCenter:DispatchEvent(eventDispatchCenter.EventType.UPDATE_PROGRESS_PERCENT, downloadPercent)
        luaRequest:SendWebRequest()
        repeat
            downloadPercent = luaRequest.downloadProgress
            if downloadPercent < 0 then downloadPercent = 0 end
            local fileDownloadBytes = baseDownloadedBytes + luaRequest.downloadedBytes
            downloadSpeed = CS.SFramework.SToolFunctionR.GetDownloadSpeed(fileDownloadBytes, CS.UnityEngine.Time.realtimeSinceStartup - downloadStartTime)
            -- update download info to show information.
            local stateMessage = string.format(
                "Downloading: %s From %s \nPercent: %.2f Speed:%s",
                fileName, donwloadLink, downloadPercent * 100, downloadSpeed
            )
            eventDispatchCenter:DispatchEvent(eventDispatchCenter.EventType.UPDATE_STATE_INFO, stateMessage)
            eventDispatchCenter:DispatchEvent(eventDispatchCenter.EventType.UPDATE_PROGRESS_PERCENT, downloadPercent)
            require("Base/CoroutineHelper.lua"):WaitForEndOfFrame()
        until luaRequest.isDone
        eventDispatchCenter:DispatchEvent(eventDispatchCenter.EventType.UPDATE_PROGRESS_PERCENT, 1)

        downloadedBytes = downloadedBytes + luaRequest.downloadedBytes
        downloadSpeed = CS.SFramework.SToolFunctionR.GetDownloadSpeed(downloadedBytes, CS.UnityEngine.Time.realtimeSinceStartup - downloadStartTime)
        -- update download speed
        local stateMessage = string.format("Downloading: %s \nPercent: %.2f Speed:%s Success", fileName, downloadPercent * 100, downloadSpeed)
        eventDispatchCenter:DispatchEvent(eventDispatchCenter.EventType.UPDATE_STATE_INFO, stateMessage)
        require("Base/CoroutineHelper.lua"):Wait(1)

        CS.SFramework.SResourceManagerR.SaveAssetBundle(fileInfo.fileName, luaRequest.downloadHandler.data)
        stateMessage = string.format("Download And Save %s Success", fileName)
        eventDispatchCenter:DispatchEvent(eventDispatchCenter.EventType.UPDATE_STATE_INFO, stateMessage)
        require("Base/CoroutineHelper.lua"):Wait(1)
    end

    downloadSpeed = CS.SFramework.SToolFunctionR.GetDownloadSpeed(downloadedBytes, CS.UnityEngine.Time.realtimeSinceStartup - downloadStartTime)
    -- update download speed
    eventDispatchCenter:DispatchEvent(eventDispatchCenter.EventType.UPDATE_STATE_INFO, "All Remote Resource Download Compelete ... Speed: " .. tostring(downloadSpeed))
    require("Base/CoroutineHelper.lua"):Wait(1)
    -- Step 3: Try Write version.json(Remote Version) To PersistentPath.
    CS.SFramework.SResourceManagerR.SaveRemoteVersionJson(SUpdateManager._rVersionInfoJson)
    eventDispatchCenter:DispatchEvent(eventDispatchCenter.EventType.UPDATE_STATE_INFO, "All Files Download Finish ...")

    print("----> Process usDownloadResource Finish ...")
    require("Base/CoroutineHelper.lua"):Wait(1)

    -- Step 4: Change currentState And retry
    stepState(tUpdateState.usFinish)
    require("Base/CoroutineHelper.lua"):Start(SUpdateManager.ProcessUpdate)
end

function SUpdateManager.tryFinishLogic()
    print("----> Process usFinish Start  ...")
    require("Base/CoroutineHelper.lua"):WaitForEndOfFrame()

    local eventDispatchCenter = require("Base/SEventDispatchCenter.lua")
    eventDispatchCenter:DispatchEvent(eventDispatchCenter.EventType.UPDATE_STATE_INFO, "All Data Updated ... Success")
    require("Base/CoroutineHelper.lua"):Wait(1)
    print("----> Process usFinish Finish ...")
    require("Base/CoroutineHelper.lua"):Wait(1)

    -- jump scene
    LoadResource("scenes/start.unity")
    --LoadResource("scenes/gameb.unity")
    --CS.UnityEngine.SceneManagement.SceneManager.LoadScene("GameA")
    CS.UnityEngine.SceneManagement.SceneManager.LoadScene("Start")
end

function SUpdateManager.ProcessUpdate()
    local currentState = SUpdateManager._updateState
    print("SUpdateManager.ProcessUpdate Start ... ... State: " .. tostring(currentState))

    if currentState == tUpdateState.usHotfixLua then 
        require("Base/CoroutineHelper.lua"):Start(SUpdateManager.tryHotfixLuaLogic)
        return
    end
    if currentState == tUpdateState.usGetResVersion then
        require("Base/CoroutineHelper.lua"):Start(SUpdateManager.tryGetResVersionLogic)
        return
    end
    if currentState == tUpdateState.usCalcUpdateResInfo then
        require("Base/CoroutineHelper.lua"):Start(SUpdateManager.tryCalcUpdateResInfoLogic)
        return
    end
    if currentState == tUpdateState.usDownloadResource then
        require("Base/CoroutineHelper.lua"):Start(SUpdateManager.tryDownloadResourceLogic)
        return
    end
    if currentState == tUpdateState.usFinish then
        require("Base/CoroutineHelper.lua"):Start(SUpdateManager.tryFinishLogic)
        return
    end

    print("SUpdateManager.ProcessUpdate All Step Finish But Run ... ... Error State: " .. tostring(currentState))
end

return SUpdateManager