

local Details = _G.Details
local DF = _G.DetailsFramework
local openRaidLib = LibStub:GetLibrary("LibOpenRaid-1.0", true)

--namespace
Details.CooldownTracking = {}

--return if the cooldown tracker is enabled
function Details.CooldownTracking.IsEnabled()
    return Details.ocd_tracker.enabled
end

--enable the cooldown tracker
function Details.CooldownTracking.EnableTracker()
    Details.ocd_tracker.enabled = true

    --register callbacks with the openRaidLib
    openRaidLib.RegisterCallback(Details.CooldownTracking, "CooldownListUpdate", "OnReceiveUnitFullCooldownList")
    openRaidLib.RegisterCallback(Details.CooldownTracking, "CooldownUpdate", "OnReceiveSingleCooldownUpdate")
    openRaidLib.RegisterCallback(Details.CooldownTracking, "CooldownListWipe", "OnCooldownListWipe")
    openRaidLib.RegisterCallback(Details.CooldownTracking, "CooldownAdded", "OnCooldownAdded")
    openRaidLib.RegisterCallback(Details.CooldownTracking, "CooldownRemoved", "OnCooldownRemoved")

    Details.CooldownTracking.RefreshCooldownFrames()
end

--disable the cooldown tracker
function Details.CooldownTracking.DisableTracker()
    Details.ocd_tracker.enabled = false

    --hide the panel
    if (DetailsOnlineCDTrackerScreenPanel) then
        DetailsOnlineCDTrackerScreenPanel:Hide()
    end

    --unregister callbacks
    openRaidLib.UnregisterCallback(Details.CooldownTracking, "CooldownListUpdate", "OnReceiveUnitFullCooldownList")
    openRaidLib.UnregisterCallback(Details.CooldownTracking, "CooldownUpdate", "OnReceiveSingleCooldownUpdate")
    openRaidLib.UnregisterCallback(Details.CooldownTracking, "CooldownListWipe", "OnCooldownListWipe")
end


--> Library Open Raid Callbacks
    --callback on the event 'CooldownListUpdate', this is triggered when a player in the group sent the list of cooldowns
    --@unitId: which unit got updated
    --@unitCooldows: a table with [spellId] = cooldownInfo
    --@allUnitsCooldowns: a table containing all units [unitName] = {[spellId] = cooldownInfo}
    function Details.CooldownTracking.OnReceiveUnitFullCooldownList(unitId, unitCooldows, allUnitsCooldowns)
        --print("|cFFFFFF00received full cooldown list|r from:", unitId)
        Details.CooldownTracking.RefreshCooldownFrames()
    end

    --callback on the event 'CooldownUpdate', this is triggered when a player uses a cooldown or a cooldown got updated (time left reduced, etc)
    --@unitId: which unit got updated
    --@spellId: which cooldown spell got updated
    --@cooldownInfo: cooldown information table to be passed with other functions
    --@unitCooldows: a table with [spellId] = cooldownInfo
    --@allUnitsCooldowns: a table containing all units [unitName] = {[spellId] = cooldownInfo}
    function Details.CooldownTracking.OnReceiveSingleCooldownUpdate(unitId, spellId, cooldownInfo, unitCooldows, allUnitsCooldowns)
        local screenPanel = DetailsOnlineCDTrackerScreenPanel
        local gotUpdated = false
        local unitName = GetUnitName(unitId, true)

        if (screenPanel) then
            local cooldownFrame = screenPanel.playerCache[unitName] and screenPanel.playerCache[unitName][spellId]
            if (cooldownFrame) then
                --get the cooldown time from the lib, it return data ready to use on statusbar
                local isReady, normalizedPercent, timeLeft, charges, minValue, maxValue, currentValue = openRaidLib.GetCooldownStatusFromCooldownInfo(cooldownInfo)
                if (not isReady) then
                    cooldownFrame:SetTimer(currentValue, minValue, maxValue)
                else
                    cooldownFrame:SetTimer()
                end
                gotUpdated = true
            end
        end

        if (not gotUpdated) then
            Details.CooldownTracking.RefreshCooldownFrames()
        end
    end

    --when the list of cooldowns got wiped, usually happens when the player left a group
    --@allUnitsCooldowns: a table containing all units [unitName] = {[spellId] = cooldownInfo}
    function Details.CooldownTracking.OnCooldownListWipe(allUnitsCooldowns)
        Details.CooldownTracking.RefreshCooldownFrames()
    end

    --when a cooldown has been added to an unit
    function Details.CooldownTracking.OnCooldownAdded(unitId, spellId, cooldownInfo, unitCooldows, allUnitsCooldowns)
        --here could update the cooldown of the unit, but I'm too lazy so it update all units
        Details.CooldownTracking.RefreshCooldownFrames()
    end

    --when a cooldown has been removed from an unit
    function Details.CooldownTracking.OnCooldownRemoved(unitId, spellId, unitCooldows, allUnitsCooldowns)
        Details.CooldownTracking.RefreshCooldownFrames()
    end


--> Frames
    --hide all bars created
    function Details.CooldownTracking.HideAllBars()
        for _, bar in ipairs (DetailsOnlineCDTrackerScreenPanel.bars) do
            bar:ClearAllPoints()
            bar:Hide()

            bar.cooldownInfo = nil
            bar.spellId = nil
            bar.class = nil
            bar.unitName = nil
        end
    end

    --get a cooldown frame
    function Details.CooldownTracking.GetOrCreateNewCooldownFrame(screenPanel, frameId)
        local cooldownFrame = screenPanel.bars[frameId]
        if (cooldownFrame) then
            return cooldownFrame
        end
        local cooldownFrame = DF:CreateTimeBar(screenPanel, [[Interface\AddOns\Details\images\bar_serenity]], Details.ocd_tracker.width-2, Details.ocd_tracker.height-2, 100, nil, screenPanel:GetName() .. "CDFrame" .. frameId)
        tinsert(screenPanel.bars, cooldownFrame)
        cooldownFrame:EnableMouse(false)
        return cooldownFrame
    end

    --create the screen panel, goes into the UIParent and show cooldowns
    function Details.CooldownTracking.CreateScreenFrame()
        DetailsOnlineCDTrackerScreenPanel = CreateFrame("frame", "DetailsOnlineCDTrackerScreenPanel", UIParent, "BackdropTemplate")
        local screenPanel = DetailsOnlineCDTrackerScreenPanel
        screenPanel:Hide()
        screenPanel:SetSize(Details.ocd_tracker.width, Details.ocd_tracker.height)
        screenPanel:SetPoint("center", 0, 0)
        screenPanel:SetBackdrop({edgeFile = [[Interface\Buttons\WHITE8X8]], edgeSize = 1, bgFile = [[Interface\Tooltips\UI-Tooltip-Background]], tileSize = 64, tile = true})
        screenPanel:SetBackdropColor(0, 0, 0, .55)
        screenPanel:SetBackdropBorderColor(0, 0, 0, .3)
        screenPanel:EnableMouse(true)

        --register on libwindow
        local libWindow = LibStub("LibWindow-1.1")
        libWindow.RegisterConfig(screenPanel, _detalhes.ocd_tracker.pos)
        libWindow.MakeDraggable(screenPanel)
        libWindow.RestorePosition(screenPanel)

        screenPanel:RegisterEvent("GROUP_ROSTER_UPDATE")
        screenPanel:SetScript("OnShow", function()
            screenPanel:RegisterEvent("GROUP_ROSTER_UPDATE")
        end)
        screenPanel:SetScript("OnHide", function()
            screenPanel:UnregisterEvent("GROUP_ROSTER_UPDATE")
        end)

        screenPanel:SetScript("OnEvent", function(self, event)
            if (event == "GROUP_ROSTER_UPDATE") then
                if (screenPanel.scheduleRosterUpdate) then
                    return
                end
                screenPanel.scheduleRosterUpdate = C_Timer.NewTimer(1, Details.CooldownTracking.RefreshCooldownFrames)
            end
        end)

        screenPanel.bars = {}
        screenPanel.cooldownCache = Details.ocd_tracker.current_cooldowns
        screenPanel.playerCache = {}
        screenPanel.statusBarFrameIndex = 1

        return screenPanel
    end

    function Details.CooldownTracking.SetupCooldownFrame(cooldownFrame)
        local spellIcon = GetSpellTexture(cooldownFrame.spellId)
        if (spellIcon) then
            cooldownFrame:SetIcon(spellIcon, .1, .9, .1, .9)
            local classColor = C_ClassColor.GetClassColor(cooldownFrame.class)
            cooldownFrame:SetStatusBarColor(classColor.r, classColor.g, classColor.b)
            cooldownFrame:SetLeftText(DF:RemoveRealmName(cooldownFrame.unitName))
            cooldownFrame:SetSize(Details.ocd_tracker.width, Details.ocd_tracker.height)
        end
    end

    function Details.CooldownTracking.ProcessUnitCooldowns(unitId, unitCooldowns, cooldownsOrganized)
        if (unitCooldowns) then
            local unitInfo = openRaidLib.GetUnitInfo(unitId)
            if (unitInfo) then
                local screenPanel = DetailsOnlineCDTrackerScreenPanel
                for spellId, cooldownInfo in pairs(unitCooldowns) do
                    --get a bar
                    local cooldownFrame = Details.CooldownTracking.GetOrCreateNewCooldownFrame(screenPanel, screenPanel.statusBarFrameIndex)
                    cooldownFrame.cooldownInfo = cooldownInfo
                    local isReady, normalizedPercent, timeLeft, charges, minValue, maxValue, currentValue = openRaidLib.GetCooldownStatusFromCooldownInfo(cooldownInfo)

                    cooldownFrame.spellId = spellId
                    cooldownFrame.class = unitInfo.class
                    cooldownFrame.unitName = unitInfo.nameFull

                    --setup the cooldown in the bar
                    Details.CooldownTracking.SetupCooldownFrame(cooldownFrame)
                    --add the cooldown into the organized by class table
                    tinsert(cooldownsOrganized[unitInfo.classId], cooldownFrame)
                    --iterate to the next cooldown frame
                    screenPanel.statusBarFrameIndex = screenPanel.statusBarFrameIndex + 1

                    --store the cooldown frame into a cache to get the cooldown frame quicker when a cooldown receives updates
                    screenPanel.playerCache[unitInfo.nameFull] = screenPanel.playerCache[unitInfo.nameFull] or {}
                    screenPanel.playerCache[unitInfo.nameFull][spellId] = cooldownFrame
                end
            end
        end
    end

--> update cooldown frames based on the amount of players in the group or raid
    function Details.CooldownTracking.RefreshCooldownFrames()
        local screenPanel = DetailsOnlineCDTrackerScreenPanel

        if (not screenPanel) then
            screenPanel = Details.CooldownTracking.CreateScreenFrame()
        end

        if (Details.ocd_tracker.framme_locked) then
            screenPanel:EnableMouse(false)
        else
            screenPanel:EnableMouse(true)
        end

        Details.CooldownTracking.HideAllBars()
        screenPanel.scheduleRosterUpdate = nil
        wipe(screenPanel.playerCache)
        screenPanel.statusBarFrameIndex = 1

        if (Details.ocd_tracker.show_conditions.only_in_group) then
            if (not IsInGroup()) then
                screenPanel:Hide()
                return
            end
        end

        if (Details.ocd_tracker.show_conditions.only_inside_instance) then
            local isInInstanceType = select(2, GetInstanceInfo())
            if (isInInstanceType ~= "party" and isInInstanceType ~= "raid" and isInInstanceType ~= "scenario" and isInInstanceType ~= "arena") then
                screenPanel:Hide()
                return
            end
        end

        local cooldownsOrganized = {}
        for classId = 1, 12 do --12 classes
            cooldownsOrganized[classId] = {}
        end

        local numGroupMembers = GetNumGroupMembers()

        local filter = ""
        for filterName, isEnabled in pairs(Details.ocd_tracker.filters) do
            if (isEnabled) then
                filter = filter .. filterName ..  ","
            end
        end

        if (IsInRaid()) then
            for i = 1, numGroupMembers do
                local unitId = "raid"..i
                local unitCooldowns = openRaidLib.GetUnitCooldowns(unitId, filter)
                Details.CooldownTracking.ProcessUnitCooldowns(unitId, unitCooldowns, cooldownsOrganized)
            end

        elseif (IsInGroup()) then
            for i = 1, numGroupMembers - 1 do
                local unitId = "party"..i
                local unitCooldowns = openRaidLib.GetUnitCooldowns(unitId, filter)
                Details.CooldownTracking.ProcessUnitCooldowns(unitId, unitCooldowns, cooldownsOrganized)
            end

            --player
            local unitCooldowns = openRaidLib.GetUnitCooldowns("player", filter)
            Details.CooldownTracking.ProcessUnitCooldowns("player", unitCooldowns, cooldownsOrganized)

        else
            --player
            local unitCooldowns = openRaidLib.GetUnitCooldowns("player", filter)
            Details.CooldownTracking.ProcessUnitCooldowns("player", unitCooldowns, cooldownsOrganized)
        end

        for classId = 1, 12 do --12 classes
            table.sort(cooldownsOrganized[classId], function(t1, t2) return t1.spellId < t2.spellId end)
        end

        local xPos = 1
        local yPos = 0
        local maxHeight = 0
        local maxWidth = Details.ocd_tracker.width + 2
        local cooldownFrameIndex = 1

        for classId = 1, 12 do
            local cooldownFrameList = cooldownsOrganized[classId]
            for index, cooldownFrame in ipairs(cooldownFrameList) do
                local cooldownInfo = cooldownFrame.cooldownInfo
                local isReady, normalizedPercent, timeLeft, charges, minValue, maxValue, currentValue = openRaidLib.GetCooldownStatusFromCooldownInfo(cooldownInfo)
                --local isReady, normalizedPercent, timeLeft, charges, minValue, maxValue, currentValue = openRaidLib.GetCooldownStatusFromUnitSpellID(cooldownFrame.unitName, cooldownFrame.spellId)

                if (not isReady) then
                    cooldownFrame:SetTimer(currentValue, minValue, maxValue)
                else
                    cooldownFrame:SetTimer()
                end

                --positioning
                if (cooldownFrameIndex % Details.ocd_tracker.lines_per_column == 0) then
                    xPos = xPos + Details.ocd_tracker.width + 2
                    maxWidth = xPos + Details.ocd_tracker.width + 2
                    yPos = 0
                end
                cooldownFrame:SetPoint("topleft", screenPanel, "topleft", xPos, yPos)
                yPos = yPos - Details.ocd_tracker.height - 1
                if (yPos < maxHeight)  then
                    maxHeight = yPos
                end

                cooldownFrameIndex = cooldownFrameIndex + 1
            end
        end

        maxHeight = abs(maxHeight)

        if (maxHeight == 0) then
            screenPanel:Hide()
            return
        end

        screenPanel:SetSize(maxWidth, maxHeight)
        screenPanel:Show()
    end


--> Options panel

    --initialize the cooldown options window and embed it to Details! options panel
    function Details:InitializeCDTrackerWindow()
        local DetailsCDTrackerWindow = CreateFrame("frame", "DetailsCDTrackerWindow", UIParent, "BackdropTemplate")
        DetailsCDTrackerWindow:SetSize(700, 480)
        DetailsCDTrackerWindow.Frame = DetailsCDTrackerWindow
        DetailsCDTrackerWindow.__name = "Cooldown Tracker"
        DetailsCDTrackerWindow.real_name = "DETAILS_CDTRACKERWINDOW"
        DetailsCDTrackerWindow.__icon = [[Interface\TUTORIALFRAME\UI-TUTORIALFRAME-SPIRITREZ]]
        DetailsCDTrackerWindow.__iconcoords = {130/512, 256/512, 0, 1}
        DetailsCDTrackerWindow.__iconcolor = "white"
        _G.DetailsPluginContainerWindow.EmbedPlugin(DetailsCDTrackerWindow, DetailsCDTrackerWindow, true)

        function DetailsCDTrackerWindow.RefreshWindow()
            Details.OpenCDTrackerWindow()
        end

        --check if is enabled at startup
        if (Details.CooldownTracking.IsEnabled()) then
            Details.CooldownTracking.EnableTracker()
        end

        DetailsCDTrackerWindow:Hide()
    end

    function Details.OpenCDTrackerWindow()

        --check if the window exists, if not create it
        if (not _G.DetailsCDTrackerWindow or not _G.DetailsCDTrackerWindow.Initialized) then
            _G.DetailsCDTrackerWindow.Initialized = true
            local f = _G.DetailsCDTrackerWindow or DF:CreateSimplePanel(UIParent, 700, 480, "Details! Online CD Tracker", "DetailsCDTrackerWindow")
            DF:ApplyStandardBackdrop(f)
            --enabled with a toggle button
            --execute to reset position
            --misc configs
            local options_text_template = DF:GetTemplate ("font", "OPTIONS_FONT_TEMPLATE")
            local options_dropdown_template = DF:GetTemplate ("dropdown", "OPTIONS_DROPDOWN_TEMPLATE")
            local options_switch_template = DF:GetTemplate ("switch", "OPTIONS_CHECKBOX_TEMPLATE")
            local options_slider_template = DF:GetTemplate ("slider", "OPTIONS_SLIDER_TEMPLATE")
            local options_button_template = DF:GetTemplate ("button", "OPTIONS_BUTTON_TEMPLATE")

            local generalOptions = {
                {--enable ocd
                    type = "toggle",
                    get = function() return Details.ocd_tracker.enabled end,
                    set = function (self, fixedparam, value)
                        if (value) then
                            Details.CooldownTracking.EnableTracker()
                        else
                            Details.CooldownTracking.DisableTracker()
                        end
                    end,
                    name = "Enable Experimental Cooldown Tracker",
                    desc = "Enable Experimental Cooldown Tracker",
                },

                {--show only in group
                    type = "toggle",
                    get = function() return Details.ocd_tracker.show_conditions.only_in_group end,
                    set = function (self, fixedparam, value)
                        Details.ocd_tracker.show_conditions.only_in_group = value
                        Details.CooldownTracking.RefreshCooldownFrames()
                    end,
                    name = "Only in Group",
                    desc = "Only in Group",
                },

                {--show only inside instances
                    type = "toggle",
                    get = function() return Details.ocd_tracker.show_conditions.only_inside_instance end,
                    set = function (self, fixedparam, value)
                        Details.ocd_tracker.show_conditions.only_inside_instance = value
                        Details.CooldownTracking.RefreshCooldownFrames()
                    end,
                    name = "Only Inside Instances",
                    desc = "Only Inside Instances",
                },
                {--lock frame
                    type = "toggle",
                    get = function() return Details.ocd_tracker.framme_locked end,
                    set = function (self, fixedparam, value)
                        Details.ocd_tracker.framme_locked = value
                        Details.CooldownTracking.RefreshCooldownFrames()
                    end,
                    name = "Lock Frame",
                    desc = "Lock Frame",
                },

                {type = "breakline"},

                {--filter: show raid wide defensive cooldowns
                    type = "toggle",
                    get = function() return Details.ocd_tracker.filters["defensive-raid"] end,
                    set = function (self, fixedparam, value)
                        Details.ocd_tracker.filters["defensive-raid"] = value
                        Details.CooldownTracking.RefreshCooldownFrames()
                    end,
                    name = "Defensive: Raid",
                    desc = "Exanple: druid tranquility.",
                },

                {--filter: show target defensive cooldowns
                    type = "toggle",
                    get = function() return Details.ocd_tracker.filters["defensive-target"] end,
                    set = function (self, fixedparam, value)
                        Details.ocd_tracker.filters["defensive-target"] = value
                        Details.CooldownTracking.RefreshCooldownFrames()
                    end,
                    name = "Defensive: Target",
                    desc = "Exanple: priest pain suppression.",
                },

                {--filter: show personal defensive cooldowns
                    type = "toggle",
                    get = function() return Details.ocd_tracker.filters["defensive-personal"] end,
                    set = function (self, fixedparam, value)
                        Details.ocd_tracker.filters["defensive-personal"] = value
                        Details.CooldownTracking.RefreshCooldownFrames()
                    end,
                    name = "Defensive: Personal",
                    desc = "Exanple: mage ice block.",
                },

                {--filter: show ofensive cooldowns
                    type = "toggle",
                    get = function() return Details.ocd_tracker.filters["ofensive"] end,
                    set = function (self, fixedparam, value)
                        Details.ocd_tracker.filters["ofensive"] = value
                        Details.CooldownTracking.RefreshCooldownFrames()
                    end,
                    name = "Offensive Cooldowns",
                    desc = "Exanple: priest power infusion.",
                },

                {--filter: show utility cooldowns
                    type = "toggle",
                    get = function() return Details.ocd_tracker.filters["utility"] end,
                    set = function (self, fixedparam, value)
                        Details.ocd_tracker.filters["utility"] = value
                        Details.CooldownTracking.RefreshCooldownFrames()
                    end,
                    name = "Utility Cooldowns",
                    desc = "Exanple: druid roar.",
                },

                {--filter: show interrupt cooldowns
                    type = "toggle",
                    get = function() return Details.ocd_tracker.filters["interrupt"] end,
                    set = function (self, fixedparam, value)
                        Details.ocd_tracker.filters["interrupt"] = value
                        Details.CooldownTracking.RefreshCooldownFrames()
                    end,
                    name = "Interrupt Cooldowns",
                    desc = "Exanple: rogue kick.",
                },                

                {type = "breakline"},

                {--bar width
                    type = "range",
                    get = function() return Details.ocd_tracker.width end,
                    set = function (self, fixedparam, value)
                        Details.ocd_tracker.width = value
                        Details.CooldownTracking.RefreshCooldownFrames()
                    end,
                    min = 10,
                    max = 200,
                    step = 1,
                    name = "Width",
                    desc = "Width",
                },

                {--bar height
                    type = "range",
                    get = function() return Details.ocd_tracker.height end,
                    set = function (self, fixedparam, value)
                        Details.ocd_tracker.height = value
                        Details.CooldownTracking.RefreshCooldownFrames()
                    end,
                    min = 10,
                    max = 200,
                    step = 1,
                    name = "Height",
                    desc = "Height",
                },
                
                {--bar height
                    type = "range",
                    get = function() return Details.ocd_tracker.lines_per_column end,
                    set = function (self, fixedparam, value)
                        Details.ocd_tracker.lines_per_column = floor(value)
                        Details.CooldownTracking.RefreshCooldownFrames()
                    end,
                    min = 1,
                    max = 30,
                    step = 1,
                    name = "Lines Per Column",
                    desc = "Lines Per Column",
                },

            }

            DF:BuildMenu(f, generalOptions, 5, -30, 150, true, options_text_template, options_dropdown_template, options_switch_template, true, options_slider_template, options_button_template)

            --cooldown selection
            local cooldownProfile = Details.ocd_tracker.cooldowns

            local cooldownSelectionFrame = CreateFrame("frame", "$parentCooldownSelectionFrame", f, "BackdropTemplate")
            cooldownSelectionFrame:SetPoint("topleft", f, "topleft", 0, -150)
            cooldownSelectionFrame:SetPoint("bottomright", f, "bottomright", 0, 10)
            DF:ApplyStandardBackdrop(cooldownSelectionFrame)

            --lib test test warning texts
            local warning1 = cooldownSelectionFrame:CreateFontString(nil, "overlay", "GameFontNormal", 5)
            warning1:SetPoint("center", f, "center", 0, 0)
            warning1:SetText("A cooldown tracker on Details!?\nWhat's next, a Caw counter for Elwynn Forest?")
            DF:SetFontColor(warning1, "silver")
            DF:SetFontSize(warning1, 14)
            local animationHub = DF:CreateAnimationHub(warning1)
            local anim1 = DF:CreateAnimation(animationHub, "rotation", 1, 0, 35)
            anim1:SetEndDelay(math.huge)
            anim1:SetSmoothProgress(1)
            animationHub:Play()
            animationHub:Pause()

            local warning2 = cooldownSelectionFrame:CreateFontString(nil, "overlay", "GameFontNormal", 5)
            warning2:SetJustifyH("left")
            warning2:SetPoint("topleft", f, "topleft", 5, -160)
            DF:SetFontColor(warning2, "lime")
            warning2:SetText("This is a concept of a cooldown tracker using the new library 'Open Raid' which uses comms to update cooldown timers.\nThe code to implement is so small that can fit inside a weakaura\nIf you're a coder, the implementation is on Details/frames/window_cdtracker.lua")
        end

        _G.DetailsPluginContainerWindow.OpenPlugin(_G.DetailsCDTrackerWindow)
        _G.DetailsCDTrackerWindow:Show()
    end