ZO_TimerBar = ZO_InitializingObject:Subclass()

TIMER_BAR_COUNTS_UP = 1
TIMER_BAR_COUNTS_DOWN = 2

local TIMER_BAR_DEFAULT_FADE_DURATION = 0.25
local TIMER_BAR_DEFAULT_UPDATE_INTERVAL = 0.25

function ZO_TimerBar:Initialize(control)
    control:SetHidden(true)
    
    self.updateFunction = function(barControl, time)
        self:Update(time)
    end
    self.control = control
    self.status = control:GetNamedChild("Status")
    self.time = control:GetNamedChild("Time")
    self.running = false
    
    --defaults
    self.direction = TIMER_BAR_COUNTS_UP
    self.fades = false  
    
    self.timeFormatStyle = TIME_FORMAT_STYLE_COLONS
    self.timePrecision = TIME_FORMAT_PRECISION_SECONDS  
end

function ZO_TimerBar:SetLabel(text)
    self.control:GetNamedChild("Label"):SetText(text)
end

function ZO_TimerBar:SetDirection(direction)
    if self.direction ~= direction then
        self.direction = direction
        if self.running then
            self:Update(GetFrameTimeSeconds())
        end
    end
end

function ZO_TimerBar:SetTimeFormatParameters(timeFormatStyle, timePrecision)
    self.timeFormatStyle = timeFormatStyle
    self.timePrecision = timePrecision
end

function ZO_TimerBar:SetFades(fades, duration)
    if self.fades ~= fades then
        self.fades = fades
        if fades then
            if not self.animation then
                self.animation = ZO_AlphaAnimation:New(self.control)
                self.fadeDuration = duration or TIMER_BAR_DEFAULT_FADE_DURATION
            end
        else
            if self.animation then
                self.animation:Stop()
            end
        end
    end
end

function ZO_TimerBar:IsStarted()
    return self.running
end

function ZO_TimerBar:IsPaused()
    return self.paused
end

function ZO_TimerBar:GetTimeLeft()
    if self:IsStarted() then
        return zo_max(0, self.ends - GetFrameTimeSeconds())
    end
    return 0
end

function ZO_TimerBar:Start(starts, ends)
    if self.fades then
        self.animation:Stop()
    end

    local control = self.control
    control:SetHandler("OnUpdate", self.updateFunction)
    control:SetHidden(false)

    local status = self.status
    status:SetMinMax(0, ends - starts)
    self.starts = starts
    self.ends = ends
    self.running = true
    self.paused = false
    self.nextBarUpdate = 0
    self.nextLabelUpdate = 0
    self.pauseElapsed = 0

    --Find out how much time it takes for the bar to move one UI unit. This isn't anything super precise, it just gives a servicable estimate of how often we should update
    local width, _ = self.control:GetDimensions()
    if width > 0 then
        self.barUpdateInterval = (ends - starts) / width
    else
        self.barUpdateInterval = TIMER_BAR_DEFAULT_UPDATE_INTERVAL
    end
    self:Update(GetFrameTimeSeconds())
end

function ZO_TimerBar:SetPaused(paused)
    if paused ~= self.paused then
        self.paused = paused
        if paused then
            self.control:SetHandler("OnUpdate", nil)
            self.pauseTime = GetFrameTimeSeconds()
        else
            local frameTime = GetFrameTimeSeconds()
            local timeSpentInPause = frameTime - self.pauseTime
            self.pauseElapsed = self.pauseElapsed + timeSpentInPause
            self.ends = self.ends + timeSpentInPause
            self.pauseTime = nil
            self.nextBarUpdate = 0
            self.nextLabelUpdate = 0
            self.control:SetHandler("OnUpdate", self.updateFunction)
            self:Update(frameTime)
        end
    end
end

do
    local function FadeOutDone(control)
        control:SetAlpha(1)
        control:SetHidden(true)
    end

    function ZO_TimerBar:Stop()
        if not self:IsStarted() then
            return
        end

        local control = self.control
        control:SetHandler("OnUpdate", nil)

        if self.fades then
            self.animation:FadeOut(0, self.fadeDuration, ZO_ALPHA_ANIMATION_OPTION_USE_CURRENT_ALPHA, FadeOutDone)
        else
            control:SetHidden(true)
        end

        self.running = false
        self.starts = nil
        self.ends = nil
        self.paused = nil
        self.pauseElapsed = nil
        self.pauseTime = nil
    end
end

function ZO_TimerBar:Update(time)
    if time > self.ends then
        self:Stop()
        return
    end

    local barReady = time > self.nextBarUpdate
    local labelReady = self.time and time > self.nextLabelUpdate

    if not (barReady or labelReady) then
        return
    end

    local timeString = ""
    local remainingUntilUpdate
    if self.direction == TIMER_BAR_COUNTS_UP then
        local totalElapsed = time - self.starts - self.pauseElapsed
        if barReady then
            self.status:SetValue(totalElapsed)
        end

        if labelReady then
            timeString, remainingUntilUpdate = ZO_FormatTime(totalElapsed, self.timeFormatStyle, self.timePrecision, TIME_FORMAT_DIRECTION_ASCENDING)
            if self.timeStringDecoratorFunction then
                timeString = self.timeStringDecoratorFunction(timeString)
            end
            self.time:SetText(timeString)
        end
    else
        local totalRemaining = self.ends - time

        if barReady then
            self.status:SetValue(totalRemaining)
        end

        if labelReady then
            timeString, remainingUntilUpdate = ZO_FormatTime(totalRemaining, self.timeFormatStyle, self.timePrecision, TIME_FORMAT_DIRECTION_DESCENDING)
            if self.timeStringDecoratorFunction then
                timeString = self.timeStringDecoratorFunction(timeString)
            end
            self.time:SetText(timeString)
        end
    end

    self.timeString = timeString

    if barReady then
        self.nextBarUpdate = time + self.barUpdateInterval
    end

    if labelReady then
        self.nextLabelUpdate = time + remainingUntilUpdate
    end
end

-- This function should accept a time string as its argument and return a decorated time string, for timer bars where we want to
-- add some special accoutrements to the time display on the timer bar.
function ZO_TimerBar:SetTimeStringDecoratorFunction(timeStringDecoratorFunction)
    self.timeStringDecoratorFunction = timeStringDecoratorFunction
end

function ZO_TimerBar:GetNarrationText()
    if self.direction == TIMER_BAR_COUNTS_DOWN then
        return SCREEN_NARRATION_MANAGER:CreateNarratableObject(zo_strformat(SI_SCREEN_NARRATION_TIMER_BAR_DESCENDING_FORMATTER, self.timeString))
    else
        --TODO XAR: Do we want to use a special formatter for when the timer is counting up as well?
        return SCREEN_NARRATION_MANAGER:CreateNarratableObject(self.timeString)
    end
end

--[[ Timer Bar Segment ]]--
ZO_TimerBarSegment = ZO_InitializingObject:Subclass()

-- Color is expected to be a ZO_ColorDef
function ZO_TimerBarSegment:Initialize(duration, startColor, endColor)
    self.duration = duration
    self.startColor = startColor
    self.endColor = endColor
end

function ZO_TimerBarSegment:GetDuration()
    return self.duration
end

function ZO_TimerBarSegment:GetStartColor()
    return self.startColor
end

function ZO_TimerBarSegment:GetEndColor()
    return self.endColor
end

--[[ Multi Segment Timer Bar ]]--

ZO_MultiSegmentTimerBar = ZO_TimerBar:Subclass()

-- Currently, this only supports bar templates with children but no grandchildren;
-- Ensure that any template used only has one layer of inheritance.
function ZO_MultiSegmentTimerBar:Initialize(control, barTemplate)
    control:SetHidden(true)

    self.updateFunction = function(barControl, time)
        self:Update(time)
    end
    self.control = control

    barTemplate = barTemplate or "TimerBarStatus"
    self.barPool = ZO_ControlPool:New(barTemplate, control)
    self.segments = { }
    self.barData = { }
    self.time = control:GetNamedChild("Time")
    self.running = false

    --defaults
    self.direction = TIMER_BAR_COUNTS_UP
    self.fades = false

    self.timeFormatStyle = TIME_FORMAT_STYLE_COLONS
    self.timePrecision = TIME_FORMAT_PRECISION_SECONDS
end

function ZO_MultiSegmentTimerBar:Start(startTimeS)
    if self.fades then
        self.animation:Stop()
    end

    local control = self.control
    control:SetHandler("OnUpdate", self.updateFunction)
    control:SetHidden(false)

    self.barPool:ReleaseAllObjects()
    ZO_ClearNumericallyIndexedTable(self.barData)

    local totalDurationS = self:GetTotalDuration()
    for index, segment in ipairs(self.segments) do
        local bar = self.barPool:AcquireObject()
        bar:SetParent(control)
        bar:SetMinMax(0, totalDurationS)
        local startR, startG, startB, startA = segment:GetStartColor():UnpackRGBA()
        local endR, endG, endB, endA = segment:GetEndColor():UnpackRGBA()
        bar:SetGradientColors(startR, startG, startB, startA, endR, endG, endB, endA)
        bar:SetAnchorFill()
        bar:SetDrawLevel(index)
        for childIndex = 1, bar:GetNumChildren() do
            bar:GetChild(childIndex):SetDrawLevel(index)
        end

        local previousSumDuration = index == 1 and 0 or self.barData[index - 1].sumDuration
        self.barData[index] =
        {
            bar = bar,
            sumDuration = segment:GetDuration() + previousSumDuration,
        }
    end

    self.starts = startTimeS
    self.ends = startTimeS + totalDurationS
    self.running = true
    self.paused = false
    self.nextBarUpdate = 0
    self.nextLabelUpdate = 0
    self.pauseElapsed = 0

    --Find out how much time it takes for the bar to move one UI unit. This isn't anything super precise, it just gives a servicable estimate of how often we should update
    local width = self.control:GetWidth()
    if width > 0 then
        self.barUpdateInterval = (totalDurationS) / width
    else
        self.barUpdateInterval = TIMER_BAR_DEFAULT_UPDATE_INTERVAL
    end

    if self.customOnStartBehavior and type(customOnStartBehavior) == "function" then
        self.customOnStartBehavior(self.barData)
    end

    self:Update(GetFrameTimeSeconds())
end

function ZO_MultiSegmentTimerBar:Update(timeS)
    if timeS > self.ends then
        self:Stop()
        return
    end

    local barReady = timeS > self.nextBarUpdate
    local labelReady = self.time and timeS > self.nextLabelUpdate

    if not (barReady or labelReady) then
        return
    end

    local timeString = ""
    local remainingUntilUpdate
    if self.direction == TIMER_BAR_COUNTS_UP then
        local totalElapsed = timeS - self.starts - self.pauseElapsed
        if barReady then
            for i, barData in ipairs(self.barData) do
                local currentValue = zo_min(totalElapsed, self:GetSumDurationForSegment(i))
                self.barData[i].bar:SetValue(currentValue)
            end
        end

        if labelReady then
            timeString, remainingUntilUpdate = ZO_FormatTime(totalElapsed, self.timeFormatStyle, self.timePrecision, TIME_FORMAT_DIRECTION_ASCENDING)
            if self.timeStringDecoratorFunction then
                timeString = self.timeStringDecoratorFunction(timeString)
            end
            self.time:SetText(timeString)
        end
    else
        local totalRemaining = self.ends - timeS

        if barReady then
            for i, barData in ipairs(self.barData) do
                local currentValue = zo_min(totalRemaining, self:GetSumDurationForSegment(i))
                self.barData[i].bar:SetValue(currentValue)
            end
        end

        if labelReady then
            timeString, remainingUntilUpdate = ZO_FormatTime(totalRemaining, self.timeFormatStyle, self.timePrecision, TIME_FORMAT_DIRECTION_DESCENDING)
            if self.timeStringDecoratorFunction then
                timeString = self.timeStringDecoratorFunction(timeString)
            end
            self.time:SetText(timeString)
        end
    end

    self.timeString = timeString

    if barReady then
        self.nextBarUpdate = timeS + self.barUpdateInterval
    end

    if labelReady then
        self.nextLabelUpdate = timeS + remainingUntilUpdate
    end
end

function ZO_MultiSegmentTimerBar:AddSegment(duration, startColor, endColor)
    internalassert(not self.running, "TimerBars do not support adding segments while the timer is running")

    local segmentData = ZO_TimerBarSegment:New(duration, startColor, endColor)
    table.insert(self.segments, segmentData)
end

function ZO_MultiSegmentTimerBar:ClearSegments()
    ZO_ClearNumericallyIndexedTable(self.segments)
end

function ZO_MultiSegmentTimerBar:GetSumDurationForSegment(segmentIndex)
    local sumDuration = 0
    local numSegments = #self.segments
    for index = segmentIndex, numSegments do
        sumDuration = sumDuration + self.segments[index]:GetDuration()
    end

    return sumDuration
end

function ZO_MultiSegmentTimerBar:GetTotalDuration()
    return self:GetSumDurationForSegment(1)
end

-- If a specific MultiSegmentTimerBar implementation has anything that requires special handling,
-- that should be accounted for here.
function ZO_MultiSegmentTimerBar:SetCustomOnStartBehavior(customOnStartBehavior)
    self.customOnStartBehavior = customOnStartBehavior
end