local _, GW = ...
local RegisterMovableFrame = GW.RegisterMovableFrame
local GetSetting = GW.GetSetting
local Wait = GW.Wait
local Self_Hide = GW.Self_Hide
local IsFrameModified = GW.IsFrameModified
local CountTable = GW.CountTable
local AddUpdateCB = GW.AddUpdateCB

local MAIN_MENU_BAR_BUTTON_SIZE = 48
local MAIN_MENU_BAR_BUTTON_MARGIN = 5
local duringVehicle = false

local GW_BLIZZARD_HIDE_FRAMES = {
    MainMenuBar,
    MainMenuBarArtFrameBackground,
    MainMenuBarOverlayFrame,
    MainMenuBarTexture0,
    MainMenuBarTexture1,
    MainMenuBarTexture2,
    MainMenuBarTexture3,
    MainMenuBarArtFrame.LeftEndCap,
    MainMenuBarArtFrame.RightEndCap,
    MainMenuBarArtFrame.PageNumber,
    ReputationWatchBar,
    HonorWatchBar,
    ArtifactWatchBar,
    MainMenuExpBar,
    ActionBarUpButton,
    ActionBarDownButton,
    MainMenuBarPageNumber,
    MainMenuMaxLevelBar0,
    MainMenuMaxLevelBar1,
    MainMenuMaxLevelBar2,
    MainMenuMaxLevelBar3,
    VerticalMultiBarsContainer
}

local GW_BLIZZARD_FORCE_HIDE = {
    ReputationWatchBar,
    HonorWatchBar,
    MainMenuExpBar,
    ArtifactWatchBar,
    KeyRingButton,
    MainMenuBarTexture,
    MainMenuMaxLevelBar,
    MainMenuXPBarTexture,
    ReputationWatchBarTexture,
    ReputationXPBarTexture,
    MainMenuBarPageNumber,
    SlidingActionBarTexture0,
    SlidingActionBarTexture1,
    StanceBarLeft,
    StanceBarMiddle,
    StanceBarRight,
    PossessBackground1,
    PossessBackground2
}

-- forward function defs
local actionBarEquipUpdate
local actionBar_OnUpdate

local function hideBlizzardsActionbars()
    for k, v in pairs(GW_BLIZZARD_HIDE_FRAMES) do
        if v and v.Hide ~= nil then
            v:Hide()
            if v.UnregisterAllEvents ~= nil then
                v:UnregisterAllEvents()
            end
        end
    end
    for k, object in pairs(GW_BLIZZARD_FORCE_HIDE) do
        if object:IsObjectType("Frame") then
            object:UnregisterAllEvents()
            object:SetScript("OnEnter", nil)
            object:SetScript("OnLeave", nil)
        end

        if object:IsObjectType("Button") then
            object:SetScript("OnClick", nil)
        end
        hooksecurefunc(object, "Show", Self_Hide)

        object:Hide()
    end

    MainMenuBar:EnableMouse(false)
end
GW.AddForProfiling("Actionbars2", "hideBlizzardsActionbars", hideBlizzardsActionbars)

-- other things can register callbacks for when actionbar visibility/fade changes
local callback = {}

local function AddActionBarCallback(m)
    local k = CountTable(callback) + 1
    callback[k] = m
end
GW.AddActionBarCallback = AddActionBarCallback

local function stateChanged()
    for k, v in pairs(callback) do
        v()
    end
end
GW.AddForProfiling("Actionbars2", "stateChanged", stateChanged)

hooksecurefunc("ValidateActionBarTransition", stateChanged)

-- fader logic
local fadeTime = 0.1

local function fadeIn_OnFinished(self)
    local bar = self:GetParent()
    for i = 1, 12 do
        bar.gw_Buttons[i].cooldown:SetDrawBling(true)
    end
    if bar.gw_StateTrigger then
        stateChanged()
    end
    bar:SetAlpha(1.0)
end
GW.AddForProfiling("Actionbars2", "fadeIn_OnFinished", fadeIn_OnFinished)

local function actionBarFrameShow(f, instant)
    f.fadeOut:Stop()
    f.fadeIn:Stop()

    f.gw_FadeShowing = true
    if f.gw_StateTrigger then
        stateChanged()
    end

    if instant then
        fadeIn_OnFinished(f.fadeIn)
    else
        f.fadeIn:Play()
    end
end
GW.AddForProfiling("Actionbars2", "actionBarFrameShow", actionBarFrameShow)

local function fadeOut_OnFinished(self)
    local bar = self:GetParent()
    if bar.gw_StateTrigger then
        stateChanged()
    end
    bar:SetAlpha(0.0)
end
GW.AddForProfiling("Actionbars2", "fadeOut_OnFinished", fadeOut_OnFinished)

local function actionBarFrameHide(f, instant)
    f.fadeOut:Stop()
    f.fadeIn:Stop()

    f.gw_FadeShowing = false
    for i = 1, 12 do
        f.gw_Buttons[i].cooldown:SetDrawBling(false)
    end

    if instant then
        fadeOut_OnFinished(f.fadeOut)
    else
        f.fadeOut:Play()
    end
end
GW.AddForProfiling("Actionbars2", "actionBarFrameHide", actionBarFrameHide)

local function fadeCheck(self, forceCombat)
    local inVehicle = false
    if OverrideActionBar:IsShown() or C_PetBattles.IsInBattle() then
        inVehicle = true
    end

    local testFlyout
    if SpellFlyout and SpellFlyout:IsShown() and SpellFlyout:GetParent() then
        testFlyout = SpellFlyout:GetParent():GetParent()
    end

    local inCombat = UnitAffectingCombat("player")
    if forceCombat then
        inCombat = true
    end

    local mouseOut = self.gw_FadeBottomActionbar
    local inLockdown = InCombatLockdown()

    for i = 1, 4 do
        local f = self["gw_Bar" .. i]
        if f then
            if f.gw_NeedsToShow ~= nil and not inLockdown then
                -- this should only be set on initial load and after a bar setting change
                if f.gw_NeedsToShow then
                    f:Show()
                    actionBarFrameShow(f, true)
                else
                    f:Hide()
                    actionBarFrameHide(f, true)
                end
                f.gw_NeedsToShow = nil
            else
                local inFocus
                if mouseOut then
                    if f:IsMouseOver(100, -100, -100, 100) then
                        inFocus = true
                    else
                        inFocus = false
                    end
                else
                    inFocus = true
                end
                local isFlyout = false
                if testFlyout and testFlyout == f then
                    isFlyout = true
                end
                local curAlpha = f:GetAlpha()
                local busy = (f.fadeIn:IsPlaying() or f.fadeOut:IsPlaying())

                if f:IsShown() and inVehicle and duringVehicle == false and not inCombat then
                    f:Hide()
                    duringVehicle = true
                    return
                elseif not f:IsShown() and not inVehicle and duringVehicle and not inCombat then
                    f:Show()
                    duringVehicle = false
                    return
                end

                if f:IsShown() and not inVehicle and (inFocus or inCombat or isFlyout) then
                    -- should be showing
                    if not busy and curAlpha < 1.0 then
                        actionBarFrameShow(f)
                    end
                else
                    -- should not be showing
                    if not busy and curAlpha > 0.0 then
                        actionBarFrameHide(f, not f:IsShown())
                    end
                end
            end
        end
    end

    if self.gw_NeedsLayout and not inLockdown then
        local xOff
        local btn_padding = self.gw_Width
        if self.gw_Bar2:IsShown() then
            xOff = (804 - btn_padding) / 2
        else
            xOff = -(btn_padding - 550) / 2
        end
        self:ClearAllPoints()
        self:SetPoint("TOP", UIParent, "BOTTOM", xOff, 80)
        self.gw_NeedsLayout = nil
    end
end
GW.AddForProfiling("Actionbars2", "fadeCheck", fadeCheck)

local function createFaderAnim(self, state)
    self.fadeOut = self:CreateAnimationGroup("fadeOut")
    self.fadeIn = self:CreateAnimationGroup("fadeIn")
    self.fadeOut:SetLooping("NONE")
    self.fadeIn:SetLooping("NONE")
    self.fadeOut:SetScript("OnFinished", fadeOut_OnFinished)
    self.fadeIn:SetScript("OnFinished", fadeIn_OnFinished)

    local fadeOut = self.fadeOut:CreateAnimation("Alpha")
    local fadeIn = self.fadeIn:CreateAnimation("Alpha")
    fadeOut:SetFromAlpha(1.0)
    fadeOut:SetToAlpha(0.0)
    fadeOut:SetDuration(fadeTime)
    fadeIn:SetFromAlpha(0.0)
    fadeIn:SetToAlpha(1.0)
    fadeIn:SetDuration(fadeTime)

    if state then
        self.gw_StateTrigger = true
    end
    local bar = self:GetParent()
    bar.elapsedTimer = -1
    bar.fadeTimer = -1
end

local MAIN_HUD_FRAMES = {
    "GwHudArtFrame",
    "GwMultiBarBottomRight",
    "GwMultiBarBottomLeft",
    "GwMultiBarRight",
    "GwMultiBarLeft",
    "GwPlayerPowerBar",
    "GwPlayerAuraFrame",
    "GwPlayerClassPower",
    "GwPlayerHealthGlobe",
    "GwPlayerPetFrame"
}
local function toggleMainHud(b, inCombat)
    for i, name in ipairs(MAIN_HUD_FRAMES) do
        local f = _G[name]
        if f then
            if b then
                if f.gw_FadeShowing ~= nil then
                    local bar = f:GetParent()
                    bar.elapsedTimer = -1
                    bar.fadeTimer = -1
                else
                    if inCombat == nil then
                        f:SetAlpha(1)
                    elseif not inCombat then
                        f:Show()
                    end
                    if name == "GwPlayerClassPower" then
                        if not f.visible then 
                            f:Hide()
                        end
                    end
                    -- f:SetAlpha(1)
                end
            else
                if f.gw_FadeShowing ~= nil then
                    local bar = f:GetParent()
                    bar.elapsedTimer = -1
                    bar.fadeTimer = -1
                else
                    if name == "GwPlayerClassPower" then
                        f.visible = f:IsShown()
                    end
                    if inCombat == nil then
                        f:SetAlpha(0)
                    elseif not inCombat then
                        f:Hide()
                    end
                   -- f:SetAlpha(0)
                end
            end
        end
    end
end

local function UpdateHudScale(scale)
    local hudScale = GetSetting("HUD_SCALE")
    MainMenuBarArtFrame:SetScale(hudScale)
    for i, name in ipairs(MAIN_HUD_FRAMES) do
        local f = _G[name]
        local fm = _G[name .. "MoveAble"]
        if f then
            f:SetScale(hudScale)
        end
        if fm then
            fm:SetScale(hudScale)
        end
    end
end
GW.UpdateHudScale = UpdateHudScale

local function updateHotkey(self, actionButtonType)
    local hotkey = self.HotKey
    local text = hotkey:GetText()

    if text == nil then
        return
    end

    text = string.gsub(text, "(s%-)", "S")
    text = string.gsub(text, "(a%-)", "A")
    text = string.gsub(text, "(c%-)", "C")
    text = string.gsub(text, "(Mouse Button )", "M")
    text = string.gsub(text, "(Middle Mouse)", "M3")
    text = string.gsub(text, "(Num Pad )", "N")
    text = string.gsub(text, "(Page Up)", "PU")
    text = string.gsub(text, "(Page Down)", "PD")
    text = string.gsub(text, "(Spacebar)", "SpB")
    text = string.gsub(text, "(Insert)", "Ins")
    text = string.gsub(text, "(Home)", "Hm")
    text = string.gsub(text, "(Delete)", "Del")
    text = string.gsub(text, "(Left Arrow)", "LT")
    text = string.gsub(text, "(Right Arrow)", "RT")
    text = string.gsub(text, "(Up Arrow)", "UP")
    text = string.gsub(text, "(Down Arrow)", "DN")

    if hotkey:GetText() == RANGE_INDICATOR then
        hotkey:SetText("")
    else
        if GetSetting("BUTTON_ASSIGNMENTS") then
            hotkey:SetText(text)
        else
            hotkey:SetText("")
        end
    end
end
GW.AddForProfiling("Actionbars2", "updateHotkey", updateHotkey)

local function setMicroButtons()
    MicroButtonPortrait:Hide()
    GuildMicroButtonTabard:Hide()
    MainMenuBarPerformanceBar:Hide()
    TalentMicroButtonAlert:Hide()
    TalentMicroButtonAlert:SetScript("OnShow", Self_Hide)
    TalentMicroButtonAlert:SetScript("OnHide", nil)
    TalentMicroButtonAlert:SetScript("OnEnter", nil)
    TalentMicroButtonAlert:SetScript("OnLeave", nil)

    for i = 1, #MICRO_BUTTONS do
        if _G[MICRO_BUTTONS[i]] then
            _G[MICRO_BUTTONS[i]]:SetScript("OnShow", Self_Hide)
            _G[MICRO_BUTTONS[i]]:SetScript("OnHide", nil)
            _G[MICRO_BUTTONS[i]]:SetScript("OnEnter", nil)
            _G[MICRO_BUTTONS[i]]:SetScript("OnLeave", nil)
            _G[MICRO_BUTTONS[i]]:SetScript("OnEvent", nil)
            _G[MICRO_BUTTONS[i]]:SetScript("OnUpdate", nil)
            _G[MICRO_BUTTONS[i]]:UnregisterAllEvents()
            _G[MICRO_BUTTONS[i]]:Hide()
        end
    end
end
GW.AddForProfiling("Actionbars2", "setMicroButtons", setMicroButtons)

local function hideBackdrop(self)
    self.gwBackdrop:Hide()
end
GW.AddForProfiling("Actionbars2", "hideBackdrop", hideBackdrop)

local function showBackdrop(self)
    self.gwBackdrop:Show()
end
GW.AddForProfiling("Actionbars2", "showBackdrop", showBackdrop)

local function setActionButtonStyle(buttonName, noBackDrop, hideUnused)
    local btn = _G[buttonName]

    if btn.icon ~= nil then
        btn.icon:SetTexCoord(0.1, 0.9, 0.1, 0.9)
    end
    if btn.HotKey ~= nil then
        btn.HotKey:ClearAllPoints()
        btn.HotKey:SetPoint("CENTER", btn, "BOTTOM", 0, 0)
        btn.HotKey:SetJustifyH("CENTER")
    end
    if btn.Count ~= nil then
        btn.Count:ClearAllPoints()
        btn.Count:SetPoint("TOPRIGHT", btn, "TOPRIGHT", -3, -3)
        btn.Count:SetJustifyH("RIGHT")
        btn.Count:SetFont(UNIT_NAME_FONT, 14, "OUTLINED")
        btn.Count:SetTextColor(1, 1, 0.6)
    end

    if btn.Border ~= nil then
        btn.Border:SetSize(btn:GetWidth(), btn:GetWidth())
        btn.Border:SetBlendMode("BLEND")
        btn.Border:SetTexture("Interface\\AddOns\\GW2_UI\\textures\\bag\\bagitemborder")
    end
    if btn.NormalTexture ~= nil then
        btn:SetNormalTexture("Interface\\AddOns\\GW2_UI\\textures\\bag\\bagnormal")
    end

    if _G[buttonName .. "FloatingBG"] ~= nil then
        _G[buttonName .. "FloatingBG"]:SetTexture(nil)
    end
    if _G[buttonName .. "NormalTexture2"] ~= nil then
        _G[buttonName .. "NormalTexture2"]:SetTexture(nil)
        _G[buttonName .. "NormalTexture2"]:Hide()
    end
    if btn.AutoCastable ~= nil then
        btn.AutoCastable:SetSize(btn:GetWidth(), btn:GetWidth())
    end

    btn:SetPushedTexture("Interface\\AddOns\\GW2_UI\\textures\\actionbutton-pressed")
    btn:SetHighlightTexture("Interface\\AddOns\\GW2_UI\\textures\\UI-Quickslot-Depress")
    btn:SetCheckedTexture("Interface\\AddOns\\GW2_UI\\textures\\UI-Quickslot-Depress")
    btn.Name:SetAlpha(0) --Hide Marco Name on Actionbutton

    if noBackDrop == nil or noBackDrop == false then
        local backDrop = CreateFrame("Frame", nil, btn, "GwActionButtonBackdropTmpl")
        local backDropSize = 1
        if btn:GetWidth() > 40 then
            backDropSize = 2
        end

        backDrop:SetPoint("TOPLEFT", btn, "TOPLEFT", -backDropSize, backDropSize)
        backDrop:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", backDropSize, -backDropSize)

        btn.gwBackdrop = backDrop
    end

    if hideUnused == true then
        btn.gwBackdrop:Hide()
        btn:HookScript("OnHide", hideBackdrop)
        btn:HookScript("OnShow", showBackdrop)
    end
end
GW.AddForProfiling("Actionbars2", "setActionButtonStyle", setActionButtonStyle)

local function main_OnEvent(self, event, ...)
    local unit = ...
    local inCombat = UnitAffectingCombat("player")
    if event == "PET_BATTLE_OPENING_START" then
        toggleMainHud(false, inCombat)
    elseif event == "PET_BATTLE_CLOSE" then
        toggleMainHud(true, inCombat)
    elseif event == "PLAYER_EQUIPMENT_CHANGED" then
        actionBarEquipUpdate()
    elseif unit == "player" and (event == "UNIT_ENTERED_VEHICLE" or event == "UNIT_EXITED_VEHICLE") then
        if event == "UNIT_ENTERED_VEHICLE" and OverrideActionBar:IsShown() then
            toggleMainHud(false, nil)
        else
            toggleMainHud(true, nil)
        end
    elseif event == "PLAYER_REGEN_DISABLED" or event == "PLAYER_REGEN_ENABLED" then
        local forceCombat = (event == "PLAYER_REGEN_DISABLED")
        fadeCheck(self, forceCombat)
    end
end
GW.AddForProfiling("Actionbars2", "main_OnEvent", main_OnEvent)

local function updateMainBar(toggle)
    local fmActionbar = MainMenuBarArtFrame

    local used_height = MAIN_MENU_BAR_BUTTON_SIZE
    local btn_padding = MAIN_MENU_BAR_BUTTON_MARGIN

    fmActionbar.gw_Buttons = {}
    fmActionbar.gw_Backdrops = {}
    fmActionbar.rangeTimer = -1
    fmActionbar.fadeTimer = -1
    fmActionbar.elapsedTimer = -1

    for i = 1, 12 do
        local btn = _G["ActionButton" .. i]
        fmActionbar.gw_Buttons[i] = btn

        if btn ~= nil then
            -- create a backdrop not attached to button because default actionbar backdrop logic is wonky
            local backDrop = CreateFrame("Frame", nil, fmActionbar, "GwActionButtonBackdropTmpl")
            local backDropSize = 1
            if btn:GetWidth() > 40 then
                backDropSize = 2
            end
            backDrop:SetPoint("TOPLEFT", btn, "TOPLEFT", -backDropSize, backDropSize)
            backDrop:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", backDropSize, -backDropSize)
            fmActionbar.gw_Backdrops[i] = backDrop

            btn:SetScript("OnUpdate", nil) -- disable the default button update handler

            local hotkey = _G["ActionButton" .. i .. "HotKey"]
            btn_padding = btn_padding + MAIN_MENU_BAR_BUTTON_SIZE + MAIN_MENU_BAR_BUTTON_MARGIN
            btn:SetSize(MAIN_MENU_BAR_BUTTON_SIZE, MAIN_MENU_BAR_BUTTON_SIZE)

            setActionButtonStyle("ActionButton" .. i, true)
            updateHotkey(btn)

            hotkey:SetPoint("BOTTOMLEFT", btn, "BOTTOMLEFT", 0, 0)
            hotkey:SetPoint("BOTTOMRIGHT", btn, "BOTTOMRIGHT", 0, 0)
            hotkey:SetFont(DAMAGE_TEXT_FONT, 14, "OUTLINED")
            hotkey:SetTextColor(1, 1, 1)

            if IsEquippedAction(btn.action) then
                local borname = "ActionButton" .. i .. "Border"
                if _G[borname] then
                    _G[borname]:SetVertexColor(0, 1.0, 0, 1)
                end
            end

            local rangeIndicator =
                CreateFrame("FRAME", "GwActionRangeIndicator" .. i, hotkey:GetParent(), "GwActionRangeIndicatorTmpl")
            rangeIndicator:SetFrameStrata("BACKGROUND", 1)
            rangeIndicator:SetPoint("TOPLEFT", btn, "BOTTOMLEFT", -1, -2)
            rangeIndicator:SetPoint("TOPRIGHT", btn, "BOTTOMRIGHT", 1, -2)
            _G["GwActionRangeIndicator" .. i .. "Texture"]:SetVertexColor(147 / 255, 19 / 255, 2 / 255)
            rangeIndicator:Hide()

            btn["gw_RangeIndicator"] = rangeIndicator
            btn["gw_HotKey"] = hotkey

            if GetSetting("BUTTON_ASSIGNMENTS") then
                local hkBg =
                    CreateFrame(
                    "Frame",
                    "GwHotKeyBackDropActionButton" .. i,
                    hotkey:GetParent(),
                    "GwActionHotkeyBackdropTmpl"
                )

                hkBg:SetPoint("CENTER", hotkey, "CENTER", 0, 0)
                _G["GwHotKeyBackDropActionButton" .. i .. "Texture"]:SetParent(hotkey:GetParent())
            end

            btn:ClearAllPoints()
            btn:SetPoint(
                "LEFT",
                fmActionbar,
                "LEFT",
                btn_padding - MAIN_MENU_BAR_BUTTON_MARGIN - MAIN_MENU_BAR_BUTTON_SIZE,
                0
            )

            if i == 6 then
                btn_padding = btn_padding + 108
            end
        end
    end

    AddUpdateCB(actionBar_OnUpdate, fmActionbar)
    fmActionbar:SetSize(btn_padding, used_height)
    fmActionbar.gw_Width = btn_padding
    fmActionbar.gw_FadeBottomActionbar = GetSetting("FADE_BOTTOM_ACTIONBAR")
    fmActionbar:RegisterEvent("PET_BATTLE_CLOSE")
    fmActionbar:RegisterEvent("PET_BATTLE_OPENING_START")
    fmActionbar:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
    fmActionbar:RegisterEvent("UNIT_ENTERED_VEHICLE")
    fmActionbar:RegisterEvent("UNIT_EXITED_VEHICLE")
    fmActionbar:RegisterEvent("PLAYER_REGEN_DISABLED")
    fmActionbar:RegisterEvent("PLAYER_REGEN_ENABLED")
    fmActionbar:SetScript("OnEvent", main_OnEvent)
    hooksecurefunc(
        "MultiActionBar_Update",
        function()
            fmActionbar.gw_NeedsLayout = true
        end
    )

    -- disable default main action bar behaviors
    MainMenuBar:UnregisterAllEvents()
    MainMenuBar:SetScript("OnUpdate", nil)
    MainMenuBar:EnableMouse(false)

    return fmActionbar
end
GW.AddForProfiling("Actionbars2", "updateMainBar", updateMainBar)

local function trackBarChanges()
    local fmActionbar = MainMenuBarArtFrame
    if not fmActionbar then
        return
    end

    local show1, show2, show3, show4
    -- need explicit bool's because we test for nil as a separate case
    if SHOW_MULTI_ACTIONBAR_1 then
        show1 = true
    else
        show1 = false
    end
    if SHOW_MULTI_ACTIONBAR_2 then
        show2 = true
    else
        show2 = false
    end
    if SHOW_MULTI_ACTIONBAR_3 then
        show3 = true
    else
        show3 = false
    end
    if SHOW_MULTI_ACTIONBAR_3 and SHOW_MULTI_ACTIONBAR_4 then
        show4 = true
    else
        show4 = false
    end

    fmActionbar.gw_Bar1.gw_NeedsToShow = show1
    fmActionbar.gw_Bar2.gw_NeedsToShow = show2
    fmActionbar.gw_Bar3.gw_NeedsToShow = show3
    fmActionbar.gw_Bar4.gw_NeedsToShow = show4
    fmActionbar.gw_NeedsLayout = true
end

local function updateMultiBar(barName, buttonName, actionPage, state)
    local multibar = _G[barName]
    local settings = GetSetting(barName)
    local used_width = 0
    local used_height = settings["size"]
    local btn_padding = 0
    local btn_padding_y = 0
    local btn_this_row = 0

    local fmMultibar = CreateFrame("FRAME", "Gw" .. barName, UIParent, "GwMultibarTmpl")
    if actionPage ~= nil then
        fmMultibar:SetAttribute("actionpage", actionPage)
        fmMultibar:SetFrameStrata("LOW")
    end
    fmMultibar.gw_Buttons = {}

    local hideActionBarBG = GetSetting("HIDEACTIONBAR_BACKGROUND_ENABLED")
    for i = 1, 12 do
        local btn = _G[buttonName .. i]
        fmMultibar.gw_Buttons[i] = btn

        if btn ~= nil then
            if actionPage ~= nil then
                -- reparent button to our action bar
                btn:SetParent(fmMultibar)
            end
            btn:SetScript("OnUpdate", nil) -- disable the default button update handler

            btn:SetSize(settings.size, settings.size)
            updateHotkey(btn)
            setActionButtonStyle(buttonName .. i, nil, hideActionBarBG)

            btn:ClearAllPoints()
            btn:SetPoint("TOPLEFT", fmMultibar, "TOPLEFT", btn_padding, -btn_padding_y)
            btn.noGrid = nil

            btn_padding = btn_padding + settings.size + settings.margin
            btn_this_row = btn_this_row + 1
            used_width = btn_padding

            if btn_this_row == settings.ButtonsPerRow then
                btn_padding_y = btn_padding_y + settings.size + settings.margin
                btn_this_row = 0
                btn_padding = 0
                used_height = btn_padding_y
            end

            if IsEquippedAction(btn.action) then
                local borname = buttonName .. i .. "Border"
                if _G[borname] then
                    _G[borname]:SetVertexColor(0, 1.0, 0, 1)
                end
            end
        end
    end

    fmMultibar:SetScript("OnUpdate", nil)
    fmMultibar:ClearAllPoints()
    fmMultibar:SetPoint(settings.point, UIParent, settings.relativePoint, settings.xOfs, settings.yOfs)
    fmMultibar:SetSize(used_width, used_height)

    -- set fader logic
    createFaderAnim(fmMultibar, state)

    -- disable default multibar behaviors
    multibar:UnregisterAllEvents()
    multibar:SetScript("OnUpdate", nil)
    multibar:EnableMouse(false)

    return fmMultibar
end
GW.AddForProfiling("Actionbars2", "updateMultiBar", updateMultiBar)

local function stance_OnEvent(self, event)
    if InCombatLockdown() then
        return
    end

    if GetNumShapeshiftForms() < 1 then
        GwStanceBarButton:Hide()
    else
        GwStanceBarButton:Show()
    end
end
GW.AddForProfiling("Actionbars2", "stance_OnEvent", stance_OnEvent)

local function setStanceBar()
    for i = 1, 12 do
        if _G["StanceButton" .. i] ~= nil then
            if i > 1 then
                _G["StanceButton" .. i]:ClearAllPoints()
                local last = i - 1

                _G["StanceButton" .. i]:SetPoint("BOTTOM", _G["StanceButton" .. last], "TOP", 0, 2)
            end

            _G["StanceButton" .. i]:SetSize(38, 38)
            setActionButtonStyle("StanceButton" .. i, true)
        end
    end

    CreateFrame("Button", "GwStanceBarButton", UIParent, "GwStanceBarButton")

    GwStanceBarButton:SetPoint("TOPRIGHT", ActionButton1, "TOPLEFT", -5, 2)
    CreateFrame("Frame", "GwStanceBarContainer", UIParent, nil)
    GwStanceBarContainer:SetPoint("BOTTOM", GwStanceBarButton, "TOP", 0, 0)

    StanceBarFrame:SetParent(GwStanceBarContainer)

    StanceButton1:ClearAllPoints()
    StanceButton1:SetPoint("BOTTOM", StanceBarFrame, "BOTTOM", 0, 0)

    StanceBarFrame:SetPoint("BOTTOMLEFT", GwStanceBarButton, "TOPLEFT", 0, 0)
    StanceBarFrame:SetPoint("BOTTOMRIGHT", GwStanceBarButton, "TOPRIGHT", 0, 0)

    GwStanceBarButton:RegisterEvent("CHARACTER_POINTS_CHANGED")
    GwStanceBarButton:RegisterEvent("PLAYER_ALIVE")
    GwStanceBarButton:RegisterEvent("UPDATE_SHAPESHIFT_FORM")
    GwStanceBarButton:RegisterEvent("UNIT_POWER_FREQUENT")
    GwStanceBarButton:RegisterEvent("UNIT_HEALTH")
    GwStanceBarButton:SetScript("OnEvent", stance_OnEvent)

    if GetNumShapeshiftForms() < 1 then
        GwStanceBarButton:Hide()
    else
        GwStanceBarButton:Show()
    end

    GwStanceBarContainer:Hide()

    GwStanceBarButton:SetFrameRef("GwStanceBarContainer", GwStanceBarContainer)

    GwStanceBarButton:SetAttribute(
        "_onclick",
        [=[
        if self:GetFrameRef('GwStanceBarContainer'):IsVisible() then
            self:GetFrameRef('GwStanceBarContainer'):Hide()
        else
            self:GetFrameRef('GwStanceBarContainer'):Show()
        end
    ]=]
    )
end
GW.AddForProfiling("Actionbars2", "setStanceBar", setStanceBar)

local function setPossessBar()
    PossessBarFrame:ClearAllPoints()
    PossessBarFrame:SetPoint("BOTTOM", MainMenuBarArtFrame, "TOP", -110, 40)
end
GW.AddForProfiling("Actionbars2", "setPossessBar", setPossessBar)

local function vehicleLeave_OnUpdate()
    if InCombatLockdown() then
        return
    end
    MainMenuBarVehicleLeaveButton:SetPoint("LEFT", ActionButton12, "RIGHT", 0, 0)
end
GW.AddForProfiling("Actionbars2", "vehicleLeave_OnUpdate", vehicleLeave_OnUpdate)

local function vehicleLeave_OnShow()
    MainMenuBarVehicleLeaveButton:SetScript("OnUpdate", vehicleLeave_OnUpdate)
end
GW.AddForProfiling("Actionbars2", "vehicleLeave_OnShow", vehicleLeave_OnShow)

local function vehicleLeave_OnHide()
    MainMenuBarVehicleLeaveButton:SetScript("OnUpdate", nil)
end
GW.AddForProfiling("Actionbars2", "vehicleLeave_OnHide", vehicleLeave_OnHide)

local function setLeaveVehicleButton()
    MainMenuBarVehicleLeaveButton:SetParent(MainMenuBar)
    MainMenuBarVehicleLeaveButton:ClearAllPoints()
    MainMenuBarVehicleLeaveButton:SetPoint("LEFT", ActionButton12, "RIGHT", 0, 0)

    MainMenuBarVehicleLeaveButton:HookScript("OnShow", vehicleLeave_OnShow)
    MainMenuBarVehicleLeaveButton:HookScript("OnHide", vehicleLeave_OnHide)
end
GW.AddForProfiling("Actionbars2", "setLeaveVehicleButton", setLeaveVehicleButton)

actionBarEquipUpdate = function()
    local bars = {
        "MultiBarBottomRightButton",
        "MultiBarBottomLeftButton",
        "MultiBarRightButton",
        "MultiBarLeftButton",
        "ActionButton"
    }
    for b = 1, #bars do
        local barname = bars[b]
        for i = 1, 12 do
            local button = _G[barname .. i]
            if button ~= nil then
                if IsEquippedAction(button.action) then
                    local borname = barname .. i .. "Border"
                    if _G[borname] then
                        Wait(
                            0.05,
                            function()
                                _G[borname]:SetVertexColor(0, 1.0, 0, 1)
                            end
                        )
                    end
                end
            end
        end
    end
end
GW.AddForProfiling("Actionbars2", "actionBarEquipUpdate", actionBarEquipUpdate)

local function actionButtonFlashing(btn, elapsed)
    local flashtime = btn.flashtime
    flashtime = flashtime - elapsed

    if (flashtime <= 0) then
        local overtime = -flashtime
        if (overtime >= ATTACK_BUTTON_FLASH_TIME) then
            overtime = 0
        end
        flashtime = ATTACK_BUTTON_FLASH_TIME - overtime

        local flashTexture = btn.Flash
        if (flashTexture:IsShown()) then
            flashTexture:Hide()
        else
            flashTexture:Show()
        end
    end

    btn.flashtime = flashtime
end
GW.AddForProfiling("Actionbars2", "actionButtonFlashing", actionButtonFlashing)

local function actionButtons_OnUpdate(self, elapsed, testRange)
    for i = 1, 12 do
        local btn = self.gw_Buttons[i]
        -- override of /Interface/FrameXML/ActionButton.lua ActionButton_OnUpdate
        if (ActionButton_IsFlashing(btn)) then
            actionButtonFlashing(btn, elapsed)
        end

        if testRange then
            local valid = IsActionInRange(btn.action)
            local checksRange = (valid ~= nil)
            local inRange = checksRange and valid
            if (not checksRange or inRange) then
                btn.gw_RangeIndicator:Hide()
            else
                btn.gw_RangeIndicator:Show()
            end
        end
    end
end
GW.AddForProfiling("Actionbars2", "actionButtons_OnUpdate", actionButtons_OnUpdate)

local out_R, out_G, out_B = RED_FONT_COLOR:GetRGB()
local in_R, in_G, in_B = LIGHTGRAY_FONT_COLOR:GetRGB()
local function multiButtons_OnUpdate(self, elapsed, testRange)
    for i = 1, 12 do
        local btn = self.gw_Buttons[i]
        -- override of /Interface/FrameXML/ActionButton.lua ActionButton_OnUpdate
        if (ActionButton_IsFlashing(btn)) then
            actionButtonFlashing(btn, elapsed)
        end

        if testRange then
            local valid = IsActionInRange(btn.action)
            local checksRange = (valid ~= nil)
            local inRange = checksRange and valid
            if checksRange and not inRange then
                btn.HotKey:SetVertexColor(out_R, out_G, out_B)
            else
                btn.HotKey:SetVertexColor(in_R, in_G, in_B)
            end
        end
    end
end
GW.AddForProfiling("Actionbars2", "multiButtons_OnUpdate", multiButtons_OnUpdate)

local updateCap = 60 / 1000 -- cap updates to 60 FPS
actionBar_OnUpdate = function(self, elapsed)
    local testRange = false
    local testFade = false
    self.rangeTimer = self.rangeTimer - elapsed
    self.fadeTimer = self.fadeTimer - elapsed
    self.elapsedTimer = self.elapsedTimer - elapsed

    if self.elapsedTimer > 0 then
        return
    end
    self.elapsedTimer = updateCap

    if self.rangeTimer <= 0 then
        testRange = true
        self.rangeTimer = TOOLTIP_UPDATE_TIME
    end

    if self.fadeTimer <= 0 then
        testFade = true
        self.fadeTimer = 0.1
    end

    -- fade bars in/out as required
    if testFade then
        fadeCheck(self)
    end

    -- update action bar buttons
    actionButtons_OnUpdate(self, elapsed, testRange)

    -- update multibar buttons
    if self.gw_Bar1.gw_FadeShowing then
        multiButtons_OnUpdate(self.gw_Bar1, elapsed, testRange)
    end
    if self.gw_Bar2.gw_FadeShowing then
        multiButtons_OnUpdate(self.gw_Bar2, elapsed, testRange)
    end
    if self.gw_Bar3.gw_FadeShowing then
        multiButtons_OnUpdate(self.gw_Bar3, elapsed, testRange)
    end
    if self.gw_Bar4.gw_FadeShowing then
        multiButtons_OnUpdate(self.gw_Bar4, elapsed, testRange)
    end
end
GW.AddForProfiling("Actionbars2", "actionBar_OnUpdate", actionBar_OnUpdate)

-- overrides for the alert frame subsystem update loop in Interface/FrameXML/AlertFrames.lua
local function adjustFixedAnchors(self, relativeAlert)
    if self.anchorFrame:IsShown() then
        local pt, relTo, relPt, xOf, _ = self.anchorFrame:GetPoint()
        local name = self.anchorFrame:GetName()
        if pt == "BOTTOM" and relTo:GetName() == "UIParent" and relPt == "BOTTOM" then
            if name == "TalkingHeadFrame" then
                self.anchorFrame:ClearAllPoints()
                self.anchorFrame:SetPoint(pt, relTo, relPt, xOf, GwAlertFrameOffsetter:GetHeight())
            elseif name == "GroupLootContainer" then
                self.anchorFrame:ClearAllPoints()
                if TalkingHeadFrame and TalkingHeadFrame:IsShown() then
                    self.anchorFrame:SetPoint(pt, relTo, relPt, xOf, GwAlertFrameOffsetter:GetHeight() + 140)
                else
                    self.anchorFrame:SetPoint(pt, relTo, relPt, xOf, GwAlertFrameOffsetter:GetHeight())
                end
            end
        end
        return self.anchorFrame
    end
    return relativeAlert
end
GW.AddForProfiling("Actionbars2", "adjustFixedAnchors", adjustFixedAnchors)

local function updateAnchors(self)
    self:CleanAnchorPriorities()

    local relativeFrame = GwAlertFrameOffsetter
    for i, alertFrameSubSystem in ipairs(self.alertFrameSubSystems) do
        if alertFrameSubSystem.AdjustAnchors == AlertFrameExternallyAnchoredMixin.AdjustAnchors then
            relativeFrame = adjustFixedAnchors(alertFrameSubSystem, relativeFrame)
        else
            relativeFrame = alertFrameSubSystem:AdjustAnchors(relativeFrame)
        end
    end
end
GW.AddForProfiling("Actionbars2", "updateAnchors", updateAnchors)

local function LoadActionBars()
    local HIDE_ACTIONBARS_CVAR = GetSetting("HIDEACTIONBAR_BACKGROUND_ENABLED")
    if HIDE_ACTIONBARS_CVAR then
        HIDE_ACTIONBARS_CVAR = 0
    else
        HIDE_ACTIONBARS_CVAR = 1
    end
    SetCVar("alwaysShowActionBars", HIDE_ACTIONBARS_CVAR)

    for _, frame in pairs(
        {
            "MainMenuBar",
            "MainMenuBarArtFrame",
            "MultiBarLeft",
            "MultiBarRight",
            "MultiBarBottomRight",
            "MultiBarBottomLeft",
            "StanceBarFrame",
            "PossessBarFrame",
            "MULTICASTACTIONBAR_YPOS",
            "MultiCastActionBarFrame",
            "PETACTIONBAR_YPOS",
            "PETACTIONBAR_XPOS",
            "OBJTRACKER_OFFSET_X"
        }
    ) do
        UIPARENT_MANAGED_FRAME_POSITIONS[frame] = nil
    end

    -- init our bars
    local fmActionbar = updateMainBar(showBotRight)
    fmActionbar.gw_Bar1 =
        updateMultiBar("MultiBarBottomLeft", "MultiBarBottomLeftButton", BOTTOMLEFT_ACTIONBAR_PAGE, true)
    fmActionbar.gw_Bar2 =
        updateMultiBar("MultiBarBottomRight", "MultiBarBottomRightButton", BOTTOMRIGHT_ACTIONBAR_PAGE, true)
    fmActionbar.gw_Bar3 = updateMultiBar("MultiBarRight", "MultiBarRightButton", RIGHT_ACTIONBAR_PAGE)
    fmActionbar.gw_Bar4 = updateMultiBar("MultiBarLeft", "MultiBarLeftButton", LEFT_ACTIONBAR_PAGE)

    RegisterMovableFrame("GwMultiBarRight", GwMultiBarRight, "MultiBarRight", "VerticalActionBarDummy")
    RegisterMovableFrame("GwMultiBarLeft", GwMultiBarLeft, "MultiBarLeft", "VerticalActionBarDummy")

    -- hook existing multibars to track settings changes
    hooksecurefunc("SetActionBarToggles", trackBarChanges)
    trackBarChanges()

    -- do stuff to other pieces of the blizz UI
    hideBlizzardsActionbars()
    setMicroButtons()
    setStanceBar()
    setPossessBar()
    setLeaveVehicleButton()

    -- hook hotkey update calls so we can override styling changes
    hooksecurefunc("ActionButton_UpdateHotkeys", updateHotkey)

    -- frames using the alert frame subsystem have their positioning managed by UIParent
    -- the secure code for that lives mostly in Interface/FrameXML/UIParent.lua
    -- we can override the alert frame subsystem update loop in Interface/FrameXML/AlertFrames.lua
    -- doing it there avoids any taint issues
    -- we also exclude a few frames from the auto-positioning stuff regardless
    GwAlertFrameOffsetter:SetHeight(205)
    UIPARENT_MANAGED_FRAME_POSITIONS["ExtraActionBarFrame"] = nil
    UIPARENT_MANAGED_FRAME_POSITIONS["ZoneAbilityFrame"] = nil
    UIPARENT_MANAGED_FRAME_POSITIONS["GroupLootContainer"] = nil
    UIPARENT_MANAGED_FRAME_POSITIONS["TalkingHeadFrame"] = nil

    if not IsFrameModified("ExtraActionBarFrame") then
        GW.Debug("moving ExtraActionBarFrame")
        ExtraActionBarFrame:ClearAllPoints()
        ExtraActionBarFrame:SetPoint("BOTTOM", UIParent, "BOTTOM", 0, 130)
        ExtraActionBarFrame:SetFrameStrata("MEDIUM")
    end
    if not IsFrameModified("ZoneAbilityFrame") then
        GW.Debug("moving ZoneAbilityFrame")
        ZoneAbilityFrame:ClearAllPoints()
        ZoneAbilityFrame:SetPoint("BOTTOM", UIParent, "BOTTOM", 0, 130)
    end
    AlertFrame.UpdateAnchors = updateAnchors
end
GW.LoadActionBars = LoadActionBars
