-- This work is licensed under the Creative Commons Attribution-ShareAlike 3.0 Unported License.
-- To view a copy of this license, visit http://creativecommons.org/licenses/by-sa/3.0/
-- or send a letter to Creative Commons, 444 Castro Street, Suite 900, Mountain View, California, 94041, USA.

local MereHealingFrames, privateVars = ...

local BuffManager = MereHealingFrames.BuffManager
local PriorityBuff = MereHealingFrames.BuffManager.PriorityBuff

function PriorityBuff:new(playerBuffs, buffList, debuffList, debuffsOverBuffs, buffCaster, debuffCaster, buffSlots)
    local this = {}
    setmetatable(this, self)
    self.__index = self

    this.playerBuffs = playerBuffs
    this.buffList = buffList or ""
    this.debuffList = debuffList or ""
    if debuffsOverBuffs == nil then
        this.debuffsOverBuffs = true
    else
        this.debuffsOverBuffs = debuffsOverBuffs
    end
    this.buffCaster = buffCaster or "self"
    this.debuffCaster = debuffCaster or "self"
    this.buffSlots = buffSlots

    this.BuffNameToDetails = {}
    this.DeBuffNameToDetails = {}
    this.priorityToDetails = {}
    this.BuffIdToDetails = {}

    this:SetupPriorityLists()
    this:HookPlayerBuffs()

    this.CurrentPriority = nil

    return this
end

function PriorityBuff:SetupPriorityLists()
    self.CurrentPriority = 1
    if self.debuffsOverBuffs then
        self:SetupDebuffPriorityLists()
        self:SetupBuffPriorityLists()
    else
        self:SetupBuffPriorityLists()
        self:SetupDebuffPriorityLists()
    end
    self.CurrentPriority = nil
end

function PriorityBuff:SetupBuffPriorityLists()
    self.BuffNameToDetails = self:SetupPriorityList(self.buffList, true)
end

function PriorityBuff:SetupDebuffPriorityLists()
    self.DeBuffNameToDetails = self:SetupPriorityList(self.debuffList, false)
end

function PriorityBuff:SetupPriorityList(list, isBuff)
    local nameToDetails = {}
    for buffName, _ in string.gsplit(list, ",") do
        local trimmedBuffName = buffName:trim()
        if trimmedBuffName ~= "" then
            local details = {
                priority = self.CurrentPriority,
                buff = isBuff,
                name = trimmedBuffName,
                buffId = nil,
                stacks = 0,
                begin = nil,
                duration = 0,
                expiryTime = nil,
            }
            table.insert(self.priorityToDetails, details)
            nameToDetails[trimmedBuffName] = details

            self.CurrentPriority = self.CurrentPriority + 1
        end
    end
    return nameToDetails
end


function PriorityBuff:HookPlayerBuffs()
    for i, details in ipairs(self.priorityToDetails) do
        local tracking = self.playerBuffs.NameTracking[details.name] or {}
        table.insert(tracking, self)
        self.playerBuffs.NameTracking[details.name] = tracking
    end
end

function PriorityBuff:UnHookPlayerBuffs()
    local tracking = self.playerBuffs.NameTracking[details.name]
    local id = nil
    for i, tracker in ipairs(tracking) do
        if tracker == self then
            id = i
            break
        end
    end
    if id ~= nil then
        table.remove(self.playerBuffs.NameTracking, id)
    end
end

function PriorityBuff:BuffAdd(buffId, buffDetail)
    local details

    local mySpell = buffDetail.caster == MereHealingFrames.PlayerId
    local unitSpell = buffDetail.caster == self.playerBuffs.playerInfo.unitId

    MereHealingFrames.Debug(2, "new buff, mySpell: %s, unitSpell: %s", tostring(mySpell), tostring(unitSpell))
    MereHealingFrames.DebugDump(8, self.DeBuffNameToDetails, self.BuffNameToDetails )
    if buffDetail.debuff then
        if (self.debuffCaster ~= "any") and
                ((self.debuffCaster == "self" and not mySpell) or
                        (self.debuffCaster == "unit" and not unitSpell))
        then
            return
        end
        details = self.DeBuffNameToDetails[buffDetail.name]
    else
        if (self.buffCaster ~= "any") and
                ((self.buffCaster == "self" and not mySpell) or
                        (self.buffCaster == "unit" and not unitSpell))
        then
            return
        end
        details = self.BuffNameToDetails[buffDetail.name]
    end

    -- the buff/debuff has the same name, and so if we're only tracking one part of the buff/debuff
    -- we need to ignore the other part, so the above will come out with a details == nil if we're not
    -- interested in the buff/debuff  I guess we could split the buff/debuff lists
    if details == nil then return end

    details.buffId = buffId
    details.stacks = buffDetail.stack
    details.begin = buffDetail.begin
    details.duration = buffDetail.duration
    if (buffDetail.begin and buffDetail.duration) then
        details.expiryTime = buffDetail.begin + buffDetail.duration
    else
        details.expiryTime = nil
    end

    self.BuffIdToDetails[buffId] = details

    if self.CurrentPriority ~= nil then
        if (self.CurrentPriority > details.priority) then
            self.CurrentPriority = details.priority
            self:UpdateIcons()
        end
    else
        BuffManager:RegisterForTick(self)
        self.CurrentPriority = details.priority
        self:UpdateIcons()
    end

    self.playerBuffs:RegisterBuffId(buffId, self)
    BuffManager:RegisterForTick(self)
end

function PriorityBuff:BuffChange(buffId, buffDetail)
    local details = self.BuffIdToDetails[buffId]

    if not details or details.buffId ~= buffId then
        return
    end

    details.stacks = buffDetail.stack
    details.begin = buffDetail.begin
    details.duration = buffDetail.duration
    if (buffDetail.begin and buffDetail.duration) then
        details.expiryTime = buffDetail.begin + buffDetail.duration
    else
        details.expiryTime = nil
    end

    if self.CurrentPriority == details.priority then
        self:UpdateIcons()
    end
end

function PriorityBuff:BuffRemove(buffId)
    local details = self.BuffIdToDetails[buffId]
    if details and details.buffId == buffId then
        details.buffId = nil
        details.stacks = 0
        details.begin = nil
        details.duration = 0
        details.expiryTime = nil

        if self.CurrentPriority == details.priority then
            self:ScanForNewPriority()
        end
    else
        self:ScanForNewPriority()
    end

    self.BuffIdToDetails[buffId] = nil
end

function PriorityBuff:ScanForNewPriority()
    self.CurrentPriority = nil

    for i, details in ipairs(self.priorityToDetails) do
        if details.buffId ~= nil then
            self.CurrentPriority = details.priority
            break
        end
    end

    if self.CurrentPriority == nil then
        BuffManager:DeregisterForTick(self)
    else
        BuffManager:RegisterForTick(self)
    end

    self:UpdateIcons()
end

function PriorityBuff:UpdateIcons()
    local currentTime = Inspect.Time.Frame()
    if self.CurrentPriority == nil then
        self:UpdateAllIcons(function (buffIcon) buffIcon:Clear() end)
    else
        local buffDetails = self.priorityToDetails[self.CurrentPriority]

        local buffState = "buff"
        if not buffDetails.buff then
            buffState = "debuff"
        end

        local timeLeft = nil
        if buffDetails.duration then
            timeLeft = buffDetails.expiryTime - currentTime
        end

        self:UpdateAllIcons(function (buffIcon)
            buffIcon:UpdateIcon(buffDetails.name, buffDetails.stacks, buffDetails.remaining, buffState)
            buffIcon:UpdateTimer(timeLeft)
        end
        )
    end
end

function PriorityBuff:UpdateStacks(stackSize)
    self:UpdateAllIcons(function (buffIcon) buffIcon:UpdateCounter(stackSize) end)
end

function PriorityBuff:Tick(currentTime)
    local timeLeft
    local buffDetails = self.priorityToDetails[self.CurrentPriority]

    if buffDetails and buffDetails.expiryTime then
        timeLeft = buffDetails.expiryTime - currentTime
    else
        return
    end

    self:UpdateAllIcons(function (buffIcon) buffIcon:UpdateTimer(timeLeft) end)
end

function PriorityBuff:UpdateAllIcons(updateFunc)
    for i, value in pairs(self.buffSlots or {}) do
        if value then
            local Handlers = self.playerBuffs.UIHandlers[i] or {}
            for _, BuffIcon in pairs(Handlers) do
                updateFunc(BuffIcon)
            end
        end
    end
end