--========================================================--
--             Scorpio Cooldown Template                  --
--                                                        --
-- Author      :  kurapica125@outlook.com                 --
-- Create Date :  2020/12/07                              --
--========================================================--

--========================================================--
Scorpio           "Scorpio.UI.CooldownTemplate"      "1.0.0"
--========================================================--

----------------------------------------------
-- Data Type
----------------------------------------------
__Sealed__() struct "CooldownStatus" {
    { name = "start",   type = Number },
    { name = "duration",type = Number },
}

----------------------------------------------
-- Template
----------------------------------------------
__Sealed__() __ChildProperty__(Frame, "CooldownLabel")
class "CooldownLabel" (function(_ENV)
    inherit "FontString"

    event "OnCooldownFinished"

    --- Whether use auto color on the label based on the duration
    property "AutoColor"        { type = Boolean,   default = true }

    --- The alert second that used to notify the user
    property "AlertSecond"      { type = Number,    default = 10 }

    --- The color for duration over 1 day
    property "DayColor"         { type = ColorType, default = Color(0.4, 0.4, 0.4) }

    --- The color for duration over 1 hour
    property "HourColor"        { type = ColorType, default = Color(0.6, 0.4, 0) }

    --- The color for duration over 1 min
    property "MinuteColor"      { type = ColorType, default = Color(0.8, 0.6, 0) }

    --- The color for duration over the alert second
    property "SecondColor"      { type = ColorType, default = Color(1, 0.82, 0) }

    --- The shine color for duration less than the alert second
    property "AlertInColor"     { type = ColorType, default = Color(1, 0.82, 0.12) }

    --- The shine color for duration less than the alert second
    property "AlertOutColor"    { type = ColorType, default = Color(1, 0.12, 0.12) }

    --- The day suffix text
    property "DaySuffix"        { type = String,    default = "d" }

    --- The hour suffix text
    property "HourSuffix"       { type = String,    default = "h" }

    --- The miniute suffix text
    property "MinuteSuffix"     { type = String,    default = "m" }

    --- Set the cooldown status
    __Async__()
    function SetCooldown(self, start, duration)
        self.TaskID             = (self.TaskID or 0) + 1

        local total             = start and duration and (start + duration) or 0
        local now               = GetTime()
        local task              = self.TaskID
        local processed         = false

        while total > now do
            processed           = true
            local remain        = total - now

            if remain < self.AlertSecond then
                if self.AutoColor then
                    self:SetText((remain - floor(remain) > 0.5 and self.AlertInColor or self.AlertOutColor) .. strformat("%.1f", remain))
                else
                    self:SetText(strformat("%.1f", remain))
                end
                Delay(0.1)
            elseif remain <= 60 then
                if self.AutoColor then
                    self:SetText(self.SecondColor .. ceil(remain))
                else
                    self:SetText(ceil(remain))
                end
                Delay(1)
            elseif remain < 3600 then
                if self.AutoColor then
                    self:SetText(self.MinuteColor .. ceil(remain / 60) .. self.MinuteSuffix)
                else
                    self:SetText(ceil(remain / 60) .. self.MinuteSuffix)
                end
                remain          = remain % 60
                Delay(remain > 0 and remain or 60)
            elseif (remain < 86400) then
                if self.AutoColor then
                    self:SetText(self.HourColor .. ceil(remain / 3600) .. self.HourSuffix)
                else
                    self:SetText(ceil(remain / 3600) .. self.HourSuffix)
                end
                remain          = remain % 3600
                Delay(remain > 0 and remain or 3600)
            else
                if self.AutoColor then
                    self:SetText(self.HourColor .. ceil(remain / 86400) .. self.DaySuffix)
                else
                    self:SetText(ceil(remain / 86400) .. self.DaySuffix)
                end
                remain          = remain % 86400
                Delay(remain > 0 and remain or 86400)
            end

            if task ~= self.TaskID then return end

            now                 = GetTime()
        end

        self:SetText("")

        if processed then
            OnCooldownFinished(self)
        end
    end
end)

__Sealed__() __ChildProperty__(Frame, "CooldownStatusBar")
class "CooldownStatusBar" (function(_ENV)
    inherit "StatusBar"

    event "OnCooldownFinished"

    local function processCooldown(self, start, duration, task)
        if task ~= self.TaskID then return end

        local now               = GetTime()
        local fin               = start and duration and (start + duration) or 0
        local reversed          = self.Reverse
        local processed         = false

        if duration > 0 and self.ShowSafeZone then
            local safeZone      = self:GetChild("SafeZone")
            local _,_,_,latency = GetNetStats()
            local pct           = (latency or 0) / 1000 / duration
            if pct > 1 then pct = 1 end

            safeZone:ClearAllPoints()
            safeZone:SetPoint("BOTTOM")
            safeZone:SetPoint(reversed and "TOPLEFT" or "TOPRIGHT")
            safeZone:SetWidth(pct * self:GetWidth())
        end

        if fin > now then
            processed           = true

            self:SetMinMaxValues(0, duration)
            self:Show()

            while fin > now do
                local remain    = now - start
                self:SetValue(reversed and (fin - now) or remain)

                if remain > 10 then
                    Delay(max(0.1, remain / 100))
                else
                    Next()
                end

                if task ~= self.TaskID then return end

                now             = GetTime()
            end
        end

        Next()
        if task ~= self.TaskID then return end

        -- Reset
        self:SetValue(self.AutoFullValue and select(2, self:GetMinMaxValues()) or 0)
        if self.AutoHide then Next(self.Hide, self) end

        if processed then
            OnCooldownFinished(self)
        end
    end

    --- Whether the status bar is reversed
    property "Reverse"          { type = Boolean }

    --- Whether show the safe zone
    property "ShowSafeZone"     { type = Boolean, handler = function(self, val) self:GetChild("SafeZone"):SetShown(val or false) end }

    --- Whether auto hide the status bar when cooldown finished
    property "AutoHide"         { type = Boolean, default = true }

    --- Whether set to full value if not in cooldown
    property "AutoFullValue"    { type = Boolean, default = false }

    function SetCooldown(self, start, duration)
        self.TaskID             = (self.TaskID or 0) + 1

        self:SetValue(self.AutoFullValue and select(2, self:GetMinMaxValues()) or 0)
        if duration <= 0 then return self.AutoHide and Next(self.Hide, self) end

        -- waiting the reverse settings to be applied at the same time
        Next(processCooldown, self, start, duration, self.TaskID)
    end

    __Template__{
        SafeZone                = Texture,
    }
    function __ctor(self)
        self:GetChild("SafeZone"):SetShown(false)
    end
end)

----------------------------------------------
-- Property
----------------------------------------------
--- The start and duration settings
UI.Property                     {
    name                        = "Cooldown",
    type                        = CooldownStatus,
    require                     = { Cooldown, CooldownLabel, CooldownStatusBar },
    set                         = function(self, val) self:SetCooldown(val.start or GetTime(), val.duration or 0) end,
}

----------------------------------------------
-- Default Style
----------------------------------------------
Style.UpdateSkin("Default",     {
    [Cooldown]                  = {
        hideCountdownNumbers    = true,
    },
    [CooldownLabel]             = {
        drawLayer               = "ARTWORK",
        fontObject              = CombatTextFont,
        textColor               = Color.WHITE,
        location                = { Anchor("CENTER") },
    },
    [CooldownStatusBar]         = {
        SafeZone                = {
            drawLayer           = "ARTWORK",
            color               = Color.WHITE,
        }
    },
})