--[[----------------------------------------------------------
    Srendarr - Aura & Buff Tracker
    ----------------------------------------------------------
    *
    *
    * Version 1.60
    * Kith, Garkin, silentgecko
    *
    *
]]--
local Srendarr = _G['Srendarr'] -- grab addon table from global
local L

Srendarr.name               = 'Srendarr'
Srendarr.slash              = '/srendarr'
Srendarr.version            = '1.60'
Srendarr.versionDB          = 2

-- CONSTANTS
Srendarr.ICON_SIZE          = 40
Srendarr.BAR_HEIGHT         = 16
Srendarr.SPAM_THRESHOLD     = 0.25  -- button mash limiter
Srendarr.UPDATE_THRESHOLD   = 0.5   -- don't update aura display if start|finish values change by less than this
Srendarr.UPDATE_RATE        = 0.05  -- (ms) update rate
Srendarr.AURA_FADE_TIME     = 1     -- (ms) time to fade expired (timed) auras

Srendarr.auraAnchors        = {}    -- display anchors for all 3 frames
Srendarr.auraFrames         = {}
Srendarr.auraSceneFragments = {}
Srendarr.ui_Locked          = true

local defaults = {
    -- general
    combineBuff             = true,
    shortBuffThreshold      = 90,       -- buffs shorter then this value will be displayed on short buff frame
    onlyInCombat            = false,
    showTargetAuras         = false,
    showDebuff              = true,
    hideMinorBuffs          = false,
    hideMajorBuffs          = false,
    -- filters
    showSoulSummons         = true,
    showSoulSummonsTarget   = true,
    showToggle              = true,
    showToggleTarget        = true,
    showPassive             = true,     -- master control for passives
    showPassiveTarget       = true,     -- master control for target passives
    showCyrodiil            = true,
    showCyrodiilTarget      = true,
    showDisguise            = true,
    showDisguiseTarget      = true,
    showAllEffects          = false,
    showAllEffectsTarget    = false,
    showMundus              = true,
    showMundusTarget        = true,
    showVampLycan           = true,
    showVampLycanTarget     = true,
    -- windows
    frames = {
        [1] = { -- short (and combined) buffs
            anchor              = {point = BOTTOMRIGHT, x = 0, y = 0},
            alpha               = 1.0,
            scale               = 1.0,
            tooltips            = false,
            auraGrowth          = 'UP',     -- UP|DOWN|LEFT|LEFTCENTER|RIGHT|RIGHTCENTER
            auraPadding         = 4,
            auraSort            = 'TIME',   -- TIME|NAME
            auraIconOnRight     = true,     -- only for vertical growth
            showNameBar         = true,
            -- name
            nameFont            = 'Univers 67',
            nameColour          = {r = 0.9, g = 0.9, b = 0.9},
            nameOutline         = 'soft-shadow-thick',
            nameSize            = 16,
            -- timer
            timerShowTP         = false,    -- show timer for (T)gogles or (P)assives
            timerShowHorz       = 'BELOW',  -- BELOW|ABOVE|OVER|HIDE
            timerShowVert       = 'OVER',   -- OVER|HIDE
            timerFont           = 'Univers 67',
            timerColour         = {r = 0.9, g = 0.9, b = 0.9},
            timerOutline        = 'soft-shadow-thick',
            timerSize           = 14,
            barGloss            = true,
            barWidth            = 160,
            barColours = {
                timed           = {r1 = 0, g1 = 0.1843137294054, b1 = 0.50980395078659, r2 = 0.32156863808632, g2 = 0.8431373285635, b2 = 1},
                toggle          = {r1 = 0.77647066116333, g1 = 0.60000002384186, b1 = 0.11372549831867, r2 = 0.97254908084869, g2 = 0.87450987100601, b2 = 0.29411765933037},
                passive         = {r1 = 0.41960787773132, g1 = 0.38039219379425, b1 = 0.23137256503105, r2 = 0.41960787773132, g2 = 0.38039219379425, b2 = 0.23137256503105},
            },
        },
        [2] = { -- long buffs
            anchor              = {point = BOTTOMRIGHT, x = -210, y = 0},
            alpha               = 1.0,
            scale               = 1.0,
            tooltips            = false,
            auraGrowth          = 'UP',     -- UP|DOWN|LEFT|LEFTCENTER|RIGHT|RIGHTCENTER
            auraPadding         = 4,
            auraSort            = 'TIME',   -- TIME|NAME
            auraIconOnRight     = true,     -- only for vertical growth
            showNameBar         = true,
            -- name
            nameFont            = 'Univers 67',
            nameColour          = {r = 0.9, g = 0.9, b = 0.9},
            nameOutline         = 'soft-shadow-thick',
            nameSize            = 16,
            -- timer
            timerShowTP         = false,    -- show timer for (T)gogles or (P)assives
            timerShowHorz       = 'BELOW',  -- BELOW|ABOVE|OVER|HIDE
            timerShowVert       = 'OVER',   -- OVER|HIDE
            timerFont           = 'Univers 67',
            timerColour         = {r = 0.9, g = 0.9, b = 0.9},
            timerOutline        = 'soft-shadow-thick',
            timerSize           = 14,
            barGloss            = true,
            barWidth            = 160,
            barColours = {
                timed           = {r1 = 0, g1 = 0.1843137294054, b1 = 0.50980395078659, r2 = 0.32156863808632, g2 = 0.8431373285635, b2 = 1},
                toggle          = {r1 = 0.77647066116333, g1 = 0.60000002384186, b1 = 0.11372549831867, r2 = 0.97254908084869, g2 = 0.87450987100601, b2 = 0.29411765933037},
                passive         = {r1 = 0.41960787773132, g1 = 0.38039219379425, b1 = 0.23137256503105, r2 = 0.41960787773132, g2 = 0.38039219379425, b2 = 0.23137256503105},
            },
        },
        [3] = { -- debuffs
            anchor              = {point = TOPRIGHT, x = 0, y = 0},
            alpha               = 1.0,
            scale               = 1.0,
            tooltips            = false,
            auraGrowth          = 'DOWN',   -- UP|DOWN|LEFT|LEFTCENTER|RIGHT|RIGHTCENTER
            auraPadding         = 4,
            auraSort            = 'TIME',   -- TIME|NAME
            auraIconOnRight     = true,     -- only for vertical growth
            showNameBar         = true,
            -- name
            nameFont            = 'Univers 67',
            nameColour          = {r = 0.9, g = 0.9, b = 0.9},
            nameOutline         = 'soft-shadow-thick',
            nameSize            = 16,
            -- timer
            timerShowTP         = false,    -- show timer for (T)gogles or (P)assives
            timerShowHorz       = 'BELOW',  -- BELOW|ABOVE|OVER|HIDE
            timerShowVert       = 'OVER',   -- OVER|HIDE
            timerFont           = 'Univers 67',
            timerColour         = {r = 0.9, g = 0.9, b = 0.9},
            timerOutline        = 'soft-shadow-thick',
            timerSize           = 14,
            barGloss            = true,
            barWidth            = 160,
            barColours = {
                timed           = {r1 = 0, g1 = 0.1843137294054, b1 = 0.50980395078659, r2 = 0.32156863808632, g2 = 0.8431373285635, b2 = 1},
                toggle          = {r1 = 0.77647066116333, g1 = 0.60000002384186, b1 = 0.11372549831867, r2 = 0.97254908084869, g2 = 0.87450987100601, b2 = 0.29411765933037},
                passive         = {r1 = 0.41960787773132, g1 = 0.38039219379425, b1 = 0.23137256503105, r2 = 0.41960787773132, g2 = 0.38039219379425, b2 = 0.23137256503105},
            },
        },
        [4] = { -- target long buffs
            anchor              = {point = TOP, x = 161, y = 88},
            alpha               = 1.0,
            scale               = 0.8,
            tooltips            = false,
            auraGrowth          = 'RIGHT',  -- UP|DOWN|LEFT|LEFTCENTER|RIGHT|RIGHTCENTER
            auraPadding         = 2,
            auraSort            = 'NAME',   -- TIME|NAME
            auraIconOnRight     = true,     -- only for vertical growth
            showNameBar         = true,
            -- name
            nameFont            = 'Univers 67',
            nameColour          = {r = 0.9, g = 0.9, b = 0.9},
            nameOutline         = 'soft-shadow-thick',
            nameSize            = 16,
            -- timer
            timerShowTP         = false,    -- show timer for (T)gogles or (P)assives
            timerShowHorz       = 'BELOW',  -- BELOW|ABOVE|OVER|HIDE
            timerShowVert       = 'OVER',   -- OVER|HIDE
            timerFont           = 'Univers 67',
            timerColour         = {r = 0.9, g = 0.9, b = 0.9},
            timerOutline        = 'soft-shadow-thick',
            timerSize           = 14,
            barGloss            = true,
            barWidth            = 160,
            barColours = {
                timed           = {r1 = 0, g1 = 0.1843137294054, b1 = 0.50980395078659, r2 = 0.32156863808632, g2 = 0.8431373285635, b2 = 1},
                toggle          = {r1 = 0.77647066116333, g1 = 0.60000002384186, b1 = 0.11372549831867, r2 = 0.97254908084869, g2 = 0.87450987100601, b2 = 0.29411765933037},
                passive         = {r1 = 0.41960787773132, g1 = 0.38039219379425, b1 = 0.23137256503105, r2 = 0.41960787773132, g2 = 0.38039219379425, b2 = 0.23137256503105},
            },
        },
        [5] = { -- debuffs
            anchor              = {point = TOPRIGHT, x = 0, y = 0},
            alpha               = 1.0,
            scale               = 1.0,
            tooltips            = false,
            auraGrowth          = 'DOWN',   -- UP|DOWN|LEFT|LEFTCENTER|RIGHT|RIGHTCENTER
            auraPadding         = 4,
            auraSort            = 'TIME',   -- TIME|NAME
            auraIconOnRight     = true,     -- only for vertical growth
            showNameBar         = true,
            -- name
            nameFont            = 'Univers 67',
            nameColour          = {r = 0.9, g = 0.9, b = 0.9},
            nameOutline         = 'soft-shadow-thick',
            nameSize            = 16,
            -- timer
            timerShowTP         = false,    -- show timer for (T)gogles or (P)assives
            timerShowHorz       = 'BELOW',  -- BELOW|ABOVE|OVER|HIDE
            timerShowVert       = 'OVER',   -- OVER|HIDE
            timerFont           = 'Univers 67',
            timerColour         = {r = 0.9, g = 0.9, b = 0.9},
            timerOutline        = 'soft-shadow-thick',
            timerSize           = 14,
            barGloss            = true,
            barWidth            = 160,
            barColours = {
                timed           = {r1 = 0, g1 = 0.1843137294054, b1 = 0.50980395078659, r2 = 0.32156863808632, g2 = 0.8431373285635, b2 = 1},
                toggle          = {r1 = 0.77647066116333, g1 = 0.60000002384186, b1 = 0.11372549831867, r2 = 0.97254908084869, g2 = 0.87450987100601, b2 = 0.29411765933037},
                passive         = {r1 = 0.41960787773132, g1 = 0.38039219379425, b1 = 0.23137256503105, r2 = 0.41960787773132, g2 = 0.38039219379425, b2 = 0.23137256503105},
            },
        },
    },
}

function Srendarr.OnInitialize(code, addon)
    if (addon ~= Srendarr.name) then return end

    local self = Srendarr

    EVENT_MANAGER:UnregisterForEvent(self.name, EVENT_ADD_ON_LOADED)
    SLASH_COMMANDS[self.slash] = self.SlashCommand

    self.db = ZO_SavedVars:New('SrendarrDB', self.versionDB, nil, defaults)

    L = self:GetLocale()

    for x = 1, 5 do
        local db = self.db.frames[x]
        self.auraAnchors[x] = self.AuraAnchor:New(x, db.anchor.point, db.anchor.x, db.anchor.y, db.scale)
        self.auraFrames[x] = self.AuraFrame:New(x, db.alpha)
        self.auraSceneFragments[x] = ZO_HUDFadeSceneFragment:New(self.auraAnchors[x])

        HUD_SCENE:AddFragment(self.auraSceneFragments[x])
        HUD_UI_SCENE:AddFragment(self.auraSceneFragments[x])
        SIEGE_BAR_SCENE:AddFragment(self.auraSceneFragments[x])
    end

    self:InitializeEvents()
    self:InitializeOnUpdate()
    self:InitializeAbilityCheck()
    self:InitializeSettings()
end

function Srendarr.SlashCommand(text)
    if (text == 'lock') then
        for _, frame in pairs(Srendarr.auraAnchors) do
            frame:DisableDrag()
        end

        Srendarr.ui_Locked = true
    elseif (text == 'unlock') then
        for _, frame in pairs(Srendarr.auraAnchors) do
            frame:EnableDrag()
        end

        Srendarr.ui_Locked = false
    else
        CHAT_SYSTEM:AddMessage(Srendarr:GetLocale().Usage)
    end
end

-- Remove all auras (called on player death and settings changes)
function Srendarr:StripAuras()
    self:ClearTimers() -- end all update timers

    for _, frame in pairs(self.auraFrames) do
        frame:RemoveAllAuras()
    end
end

-- Check and add auras that are 'known' to the player, called on player alive, settings changes and 'proper' zone changes
function Srendarr:UpdateAuras()
    self:StripAuras()

    local numAuras = GetNumBuffs('player')
    local auraFrameShort  = self.auraFrames[1]
    local auraFrameDebuffs  = self.auraFrames[3]
    local auraFrameLong = self.db.combineBuff and self.auraFrames[1] or self.auraFrames[2]
    local updateShort = false
    local updateLong = false

    if (self.db.showTargetAurasDebuff) then
        local numAurasTarget = GetNumBuffs('reticleover')

        if (numAurasTarget > 0) then
            for x = 1, numAurasTarget do
                local name, start, finish, buff, stack, icon, _, effectType, abilityType, _, abilityId = GetUnitBuffInfo('reticleover', x)
                local isShield = abilityType == ABILITY_TYPE_DAMAGESHIELD

                -- Skip Minor Buffs
                if (Srendarr.db.hideMinorBuffs and Srendarr.MinorBuffs[abilityId] ~= nil) then
                    return
                end

                -- Skip Major Buffs
                if (Srendarr.db.hideMajorBuffs and Srendarr.MajorBuffs[abilityId] ~= nil) then
                    return
                end

                if (finish > start) then
                    if (Srendarr:IsWatchedTimedTarget(name)) then
                        auraFrameShort:AddAura(1, 2, name, icon, start, finish, isShield, abilityType, buff, abilityId, effectType)
                    end
                end
            end

            auraFrameShort:UpdateDisplay()
        end
    end

    if (numAuras > 0) then -- existing 'known' auras, re-add and UpdateDisplay
        for x = 1, numAuras do
            local name, start, finish, buff, stack, icon, _, effectType, abilityType, statusEffectType, abilityId = GetUnitBuffInfo('player', x)

            local isShield = abilityType == ABILITY_TYPE_DAMAGESHIELD

            -- Skip Minor Buffs
            if (Srendarr.db.hideMinorBuffs and Srendarr.MinorBuffs[abilityId] ~= nil) then
                return
            end

            -- Skip Major Buffs
            if (Srendarr.db.hideMajorBuffs and Srendarr.MajorBuffs[abilityId] ~= nil) then
                return
            end

            if (finish > start) then -- timed ability
                if(effectType == BUFF_EFFECT_TYPE_DEBUFF) then

                    d('abilityId', abilityId)
                    d('abilityName', name)
                    d('buff', buff)
                    d('effectType', effectType)
                    d('statufEffectType', statusEffectType)
                    if (Srendarr:IsWatchedTimed(name)) then
                        auraFrameDebuffs:AddAura(1, 1, name, icon, start, finish, isShield, abilityType, buff, abilityId, effectType)
                    end
                else
                    if (finish - start < self.db.shortBuffThreshold) then
                        if (Srendarr:IsWatchedTimed(name)) then
                            auraFrameShort:AddAura(1, 1, name, icon, start, finish, isShield, abilityType, buff, abilityId, effectType)
                            updateShort = true
                        end
                    else
                        if (self:IsWatchedTimed(name)) then
                            auraFrameLong:AddAura(2, 1, name, icon, start, finish, isShield, abilityType, buff, abilityId, effectType)
                            updateLong = true
                        end
                    end
                end
            elseif (self:IsToggled(name)) then -- toggled ability
                if (self.db.showToggle) then -- showing toggled
                    auraFrameLong:AddAura(3, 1, name, icon, 1, 1, isShield, abilityType, buff, abilityId, effectType)
                    updateLong = true
                end
            else -- passive (assumption: passives have start = finish)
                if (abilityType == ABILITY_TYPE_REGISTERTRIGGER) then
                    auraFrameShort:AddAura(4, 1, name, icon, 1, 1, isShield, abilityType, buff, abilityId, effectType)
                    updateShort = true
                elseif (self:IsWatchedPassive(name, abilityType)) then -- validate against user settings
                    auraFrameLong:AddAura(4, 1, name, icon, 1, 1, isShield, abilityType, buff, abilityId, effectType)
                    updateLong = true
                end
            end
            if (abilityType == ABILITY_TYPE_REGISTERTRIGGER) then
                local slotNum = self:GetTriggeredSlotNum(name)
                if slotNum then
                    local actionButton = ZO_ActionBar_GetButton(slotNum)
                    self:PlayProcAnimations(actionButton)
                end
            end
            if (name == L.Passive_MedicinalUse) then
                self:SetMedicinalUseCoefficient(abilityId)
            end
        end
    end

    if (updateShort) then
        auraFrameShort:UpdateDisplay()
    end
    if (updateLong) then
        auraFrameLong:UpdateDisplay()
    end
end

do -- TIMER AND STATUSBAR UPDATE HANDLER
    local RATE          = Srendarr.UPDATE_RATE
    local FADE_TIME     = Srendarr.AURA_FADE_TIME * -1
    local CAST_TIME     = 1 -- fake 'max cast time' to make a facsimile casting bar when needed

    local strformat     = string.format
    local math_floor    = math.floor
    local math_max      = math.max

    local seconds       = ''
    local minutes       = ''
    local hours         = ''
    local cast          = ''
    local auraTimers = {
        [1]             = {},   -- buffs
        [2]             = {},   -- debuffs
        [3]             = {},   -- target buffs
        [4]             = {},   -- target debuffs
    }
    local nextUpdate    = 0
    local percent, remaining

    local function FormatTime(remaining)
        if (remaining < 60) then -- seconds
            return strformat(seconds, remaining)
        elseif (remaining < 3600) then -- minutes
            return strformat(minutes, math_floor(remaining / 60))
        else -- hours
            return strformat(hours, math_floor(remaining / 3600))
        end
    end

    local function OnUpdate(self, updateTime)
        if (updateTime >= nextUpdate) then
            for _, timers in pairs(auraTimers) do
                for _, aura in pairs(timers) do
                    remaining = aura.k_finish - updateTime

                    if (remaining < FADE_TIME) then -- expired and finished fading, kill
                        aura.k_frame:RemoveAura(aura.k_name)
                    elseif (remaining <= 0) then -- expired but hasn't faded yet
                        if (not aura.k_isGhost) then
                            aura:Ghost()
                        end

                        aura:SetAlpha(math_max(0, 1 - (remaining / FADE_TIME)))
                    elseif (aura.k_start > updateTime) then -- must have a cast time
                        aura.bar:SetValue(math_max(0, (updateTime - aura.k_start) + CAST_TIME))
                        aura.timer:SetText(cast)
                    else -- normal timer
                        aura.bar:SetValue(1 - ((updateTime - aura.k_start) / (aura.k_finish - aura.k_start)))
                        aura.timer:SetText(FormatTime(remaining))
                    end
                end
            end

            nextUpdate = updateTime + RATE
        end
    end

    function Srendarr:AddTimer(bdType, name, aura)
        if (not auraTimers[bdType][name]) then
            auraTimers[bdType][name] = aura
        end
    end

    function Srendarr:RemoveTimer(bdType, name)
        if (auraTimers[bdType][name]) then
            auraTimers[bdType][name] = nil
        end
    end

    function Srendarr:ClearTimers()
        for _, timers in pairs(auraTimers) do
            for name in pairs(timers) do
                timers[name] = nil
            end
        end
    end

    function Srendarr:ClearTimerType(bdType)
        for name in pairs(auraTimers[bdType]) do
            auraTimers[bdType][name] = nil
        end
    end

    function Srendarr:InitializeOnUpdate()
        local L = Srendarr:GetLocale()

        seconds = L.Time_Seconds
        minutes = L.Time_Minutes
        hours   = L.Time_Hours
        cast    = L.Time_Cast

        Srendarr:SetHandler('OnUpdate', OnUpdate)
    end
end

EVENT_MANAGER:RegisterForEvent(Srendarr.name, EVENT_ADD_ON_LOADED, Srendarr.OnInitialize)