local ADDON, Private = ...
local Core = Wheel("LibModule"):GetModule(ADDON)
if (not Core) then 
	return 
end

-- Note that there's still a lot of hardcoded things in this file,
-- and they will most likely NOT be moved into the layout, 
-- as bar layouts in our UIs are very non-typical,
-- and more often than not iconic, integral elements of the design.
local L = Wheel("LibLocale"):GetLocale(ADDON)
local Module = Core:NewModule("ActionBarMain", "LibEvent", "LibMessage", "LibDB", "LibFrame", "LibSound", "LibTooltip", "LibSecureButton", "LibWidgetContainer", "LibPlayerData", "LibClientBuild", "LibForge")

-- Lua API
local _G = _G
local ipairs = ipairs
local math_floor = math.floor
local pairs = pairs
local table_remove = table.remove
local tonumber = tonumber
local tostring = tostring

-- WoW API
local FindActiveAzeriteItem = C_AzeriteItem and C_AzeriteItem.FindActiveAzeriteItem
local GetAzeriteItemXPInfo = C_AzeriteItem and C_AzeriteItem.GetAzeriteItemXPInfo
local GetPowerLevel = C_AzeriteItem and C_AzeriteItem.GetPowerLevel
local GetTotemTimeLeft = GetTotemTimeLeft
local HasOverrideActionBar = HasOverrideActionBar
local HasTempShapeshiftActionBar = HasTempShapeshiftActionBar
local HasVehicleActionBar = HasVehicleActionBar
local InCombatLockdown = InCombatLockdown
local IsMounted = IsMounted
local UnitLevel = UnitLevel
local UnitOnTaxi = UnitOnTaxi
local UnitRace = UnitRace

-- Private API
local Colors = Private.Colors
local GetConfig = Private.GetConfig
local GetFont = Private.GetFont
local GetLayout = Private.GetLayout
local GetMedia = Private.GetMedia

-- Constants for client version
local IsClassic = Module:IsClassic()
local IsRetail = Module:IsRetail()

-- Player Class
local _,playerClass = UnitClass("player")

-- Blizzard textures for generic styling
local BLANK_TEXTURE = [[Interface\ChatFrame\ChatFrameBackground]]

-- Various string formatting for our tooltips and bars
local shortXPString = "%s%%"
local longXPString = "%s / %s"
local fullXPString = "%s / %s (%s)"
local restedString = " (%s%% %s)"
local shortLevelString = "%s %.0f"

-- Cache of buttons
local Cache = {} -- cache buttons to separate different ranks of same spell
local Buttons = {} -- all action buttons
local PetButtons = {} -- all pet buttons
local HoverButtons = {} -- all action buttons that can fade out

-- Hover frames
-- *Not related to the explorer mode.
local ActionBarHoverFrame, PetBarHoverFrame
local FadeOutHZ, FadeOutDuration = 1/20, 1/5

-- Time constants
local DAY, HOUR, MINUTE = 86400, 3600, 60

-- Is ConsolePort enabled in the addon listing?
local IsConsolePortEnabled = Module:IsAddOnEnabled("ConsolePort")

-- Track combat status
local IN_COMBAT

-- Secure Code Snippets
-- TODO: Turn these into formatstrings,
-- and fill in layout options from the layout cache
-- instead of using hardcoded values directly. 
local secureSnippets = {
	-- Arrange the main and extra actionbar buttons
	arrangeButtons = [=[

		local UICenter = self:GetFrameRef("UICenter"); 
		local extraButtonsCount = tonumber(self:GetAttribute("extraButtonsCount")) or 0;
		local buttonSize, buttonSpacing, iconSize = 64, 8, 44;
		local row2mod = 1-2/5; -- horizontal offset for upper row

		for id,button in ipairs(Buttons) do 
			local buttonID = button:GetID(); 
			local barID = Pagers[id]:GetID(); 

			-- Brave New World.
			local layoutID = button:GetAttribute("layoutID");
			if (layoutID <= 7) then
				button:SetPoint("BOTTOMLEFT", UICenter, "BOTTOMLEFT", 60 + ((layoutID-1) * (buttonSize + buttonSpacing)), 42)
			else
				local slot = floor((layoutID - 8)/2)

				-- Bottom row
				if (layoutID%2 == 0) then

					button:SetPoint("BOTTOMLEFT", UICenter, "BOTTOMLEFT", 60 + ((slot+7) * (buttonSize + buttonSpacing)), 42 )

				-- Top row
				else

					button:SetPoint("BOTTOMLEFT", UICenter, "BOTTOMLEFT", 60 + ((slot+7 + row2mod) * (buttonSize + buttonSpacing)), 42 + buttonSize + buttonSpacing)
				end

			end


			if (false) then
				-- Primary Bar
				if (barID == 1) then 
					button:ClearAllPoints(); 

					if (buttonID > 10) then
						button:SetPoint("BOTTOMLEFT", UICenter, "BOTTOMLEFT", 60 + ((buttonID-2-1 + row2mod) * (buttonSize + buttonSpacing)), 42 + buttonSize + buttonSpacing)
					else
						button:SetPoint("BOTTOMLEFT", UICenter, "BOTTOMLEFT", 60 + ((buttonID-1) * (buttonSize + buttonSpacing)), 42)
					end 

				-- Secondary Bar
				elseif (barID == self:GetAttribute("BOTTOMLEFT_ACTIONBAR_PAGE")) then 
					button:ClearAllPoints(); 

					-- 3x2 complimentary buttons
					if (extraButtonsCount <= 11) then 
						if (buttonID < 4) then 
							button:SetPoint("BOTTOMLEFT", UICenter, "BOTTOMLEFT", 60 + (((buttonID+10)-1) * (buttonSize + buttonSpacing)), 42 )
						else
							button:SetPoint("BOTTOMLEFT", UICenter, "BOTTOMLEFT", 60 + (((buttonID-3+10)-1 +row2mod) * (buttonSize + buttonSpacing)), 42 + buttonSize + buttonSpacing)
						end

					-- 6x2 complimentary buttons
					else 
						if (buttonID < 7) then 
							button:SetPoint("BOTTOMLEFT", UICenter, "BOTTOMLEFT", 60 + (((buttonID+10)-1) * (buttonSize + buttonSpacing)), 42 )
						else
							button:SetPoint("BOTTOMLEFT", UICenter, "BOTTOMLEFT", 60 + (((buttonID-6+10)-1 +row2mod) * (buttonSize + buttonSpacing)), 42 + buttonSize + buttonSpacing)
						end
					end 
				end
			end
		end 

		-- lua callback to update the hover frame anchors to the current layout
		self:CallMethod("UpdateFadeAnchors"); 
	
	]=],

	-- Arrange the pet action bar buttons
	arrangePetButtons = [=[
		local UICenter = self:GetFrameRef("UICenter");
		local buttonSize, buttonSpacing = 64*3/4, 2;
		local startX, startY = -(buttonSize*10 + buttonSpacing*9)/2, 200;

		for id,button in ipairs(PetButtons) do
			button:ClearAllPoints();
			button:SetPoint("BOTTOMLEFT", UICenter, "BOTTOM", startX + ((id-1) * (buttonSize + buttonSpacing)), startY);
		end

		-- lua callback to update the explorer mode anchors to the current layout
		self:CallMethod("UpdateExplorerModeAnchors"); 

	]=],

	-- Placeholder.
	arrangeSideButtons = [=[
		local UICenter = self:GetFrameRef("UICenter"); 
		local sideBar1Enabled = self:GetAttribute("sideBar1Enabled");
		local sideBar2Enabled = self:GetAttribute("sideBar2Enabled");
		local sideBar3Enabled = self:GetAttribute("sideBar3Enabled");
		local sideBarCount = (sideBar1Enabled and 1 or 0) + (sideBar2Enabled and 1 or 0) + (sideBar3Enabled and 1 or 0);
		local buttonSize, buttonSpacing, iconSize = 64, 8, 44;

		for id,button in ipairs(Buttons) do 
			local buttonID = button:GetID(); 
			local barID = Pagers[id]:GetID(); 

			-- First Side Bar
			if (barID == self:GetAttribute("RIGHT_ACTIONBAR_PAGE")) then

				if (sideBar1Enabled) then
					button:ClearAllPoints(); 

					-- 12x1
					if (sideBarCount > 1) then
						-- This is always the first when it's enabled

					-- 6x2
					else

					end
				end

			-- Second Side Bar
			elseif (barID == self:GetAttribute("LEFT_ACTIONBAR_PAGE")) then

				if (sideBar2Enabled) then
					button:ClearAllPoints(); 

					if (sideBarCount > 1) then

						-- 12x1, 2nd
						if (sideBar1Enabled) then

						-- 12x1, 1st
						else

						end

					-- 6x2, 1st
					else

					end
				end

			-- Third Side Bar
			elseif (barID == self:GetAttribute("BOTTOMRIGHT_ACTIONBAR_PAGE")) then

				if (sideBar3Enabled) then
					button:ClearAllPoints(); 

					-- 12x1, 3rd
					if (sideBarCount > 2) then

					-- 12x1, 2nd
					elseif (sideBarCount > 1) then

					-- 6x2, 1st
					else

					end
				end
			end 
		end
	]=],

	-- Saved setting changed.
	attributeChanged = [=[
		-- 'name' appears to be turned to lowercase by the restricted environment(?), 
		-- but we're doing it manually anyway, just to avoid problems. 
		if name then 
			name = string.lower(name); 
		end 

		if (name == "change-extrabuttonsvisibility") then 
			self:SetAttribute("extraButtonsVisibility", value); 
			self:CallMethod("UpdateFadeAnchors"); 
			self:CallMethod("UpdateFading"); 
		
		elseif (name == "change-petbarvisibility") then 
				self:SetAttribute("petBarVisibility", value); 
				self:CallMethod("UpdateFadeAnchors"); 
				self:CallMethod("UpdateFading"); 
	
		elseif (name == "change-extrabuttonscount") then 
			local extraButtonsCount = tonumber(value) or 0; 
			local visible = extraButtonsCount + 7; 
	
			-- Update button visibility counts
			for i = 8,24 do 
				local pager = Pagers[i]; 
				if (i > visible) then 
					if pager:IsShown() then 
						pager:Hide(); 
					end 
				else 
					if (not pager:IsShown()) then 
						pager:Show(); 
					end 
				end 
			end 

			self:SetAttribute("extraButtonsCount", extraButtonsCount); 
			self:RunAttribute("arrangeButtons"); 

			-- tell lua about it
			self:CallMethod("UpdateButtonCount"); 

		elseif (name == "change-castondown") then 
			self:SetAttribute("castOnDown", value and true or false); 
			self:CallMethod("UpdateCastOnDown"); 

		elseif (name == "change-petbarenabled") then 
			self:SetAttribute("petBarEnabled", value and true or false); 

			for i = 1,10 do
				local pager = PetPagers[i]; 
				if value then 
					if (not pager:IsShown()) then 
						pager:Show(); 
					end 
				else 
					if pager:IsShown() then 
						pager:Hide(); 
					end 
				end 
			end

			-- lua callback to update the explorer mode anchors to the current layout
			self:CallMethod("UpdateExplorerModeAnchors"); 
			self:CallMethod("UpdateFadeAnchors"); 
			self:CallMethod("UpdateFading"); 
			
		elseif (name == "change-buttonlock") then 
			self:SetAttribute("buttonLock", value and true or false); 

			-- change all button attributes
			for id, button in ipairs(Buttons) do 
				button:SetAttribute("buttonLock", value);
			end

			-- change all pet button attributes
			for id, button in ipairs(PetButtons) do 
				button:SetAttribute("buttonLock", value);
			end
		end 

	]=]
}

-- Aimed to be compact and displayed on buttons
local formatCooldownTime = function(time)
	if (time > DAY) then -- more than a day
		time = time + DAY/2
		return "%d%s", time/DAY - time/DAY%1, "d"
	elseif (time > HOUR) then -- more than an hour
		time = time + HOUR/2
		return "%d%s", time/HOUR - time/HOUR%1, "h"
	elseif (time > MINUTE) then -- more than a minute
		time = time + MINUTE/2
		return "%d%s", time/MINUTE - time/MINUTE%1, "m"
	elseif (time > 10) then -- more than 10 seconds
		return "%d", time - time%1
	elseif (time >= 1) then -- more than 5 seconds
		return "|cffff8800%d|r", time - time%1
	elseif (time > 0) then
		return "|cffff0000%d|r", time*10 - time*10%1
	else
		return ""
	end	
end

-- ActionButton Template (Custom Methods)
----------------------------------------------------
local ActionButton = {}

ActionButton.UpdateMouseOver = function(self)
	if (self.isMouseOver) then 
		if (self.Darken) then 
			self.Darken:SetAlpha(self.Darken.highlight)
		end 
		if (self.Border) then 
			self.Border:SetVertexColor(Colors.highlight[1], Colors.highlight[2], Colors.highlight[3], 1)
		end 
		if (self.Glow) then 
			self.Glow:Show()
		end 
	else 
		if self.Darken then 
			self.Darken:SetAlpha(self.Darken.normal)
		end 
		if self.Border then 
			self.Border:SetVertexColor(Colors.ui[1], Colors.ui[2], Colors.ui[3], 1)
		end 
		if self.Glow then 
			self.Glow:Hide()
		end 
	end 
end 
ActionButton.PostEnter = ActionButton.UpdateMouseOver
ActionButton.PostLeave = ActionButton.UpdateMouseOver

ActionButton.SetRankVisibility = function(self, visible)
	if (not IsClassic) then
		return
	end
	local cache = Cache[self]

	-- Show rank on self
	if (visible) then 

		-- Create rank text if needed
		if (not self.Rank) then 
			local count = self.Count
			local rank = self:CreateFontString()
			rank:SetParent(count:GetParent())
			--rank:SetFontObject(count:GetFontObject()) -- nah, this one changes based on count!
			rank:SetFontObject(Private.GetFont(14,true)) -- use the smaller font
			rank:SetDrawLayer(count:GetDrawLayer())
			rank:SetTextColor(Colors.quest.gray[1], Colors.quest.gray[2], Colors.quest.gray[3])
			rank:SetPoint(count:GetPoint())
			self.Rank = rank
		end
		self.Rank:SetText(cache.spellRank)

	-- Hide rank on self, if it exists. 
	elseif (not visible) and (self.Rank) then 
		self.Rank:SetText("")
	end 
end

ActionButton.PostUpdate = function(self)
	self:UpdateMouseOver()

	-- The following is only for classic
	if (not IsClassic) then
		return
	end

	local cache = Cache[self]
	if (not cache) then 
		Cache[self] = {}
		cache = Cache[self]
	end

	-- Retrieve the previous info, if any.
	local oldCount = cache.spellCount -- counter of the amount of multiples
	local oldName = cache.spellName -- used as identifier for multiples
	local oldRank = cache.spellRank -- rank of this instance of the multiple

	-- Update cached info 
	cache.spellRank = self:GetSpellRank()
	cache.spellName = GetSpellInfo(self:GetSpellID())

	-- Button spell changed?
	if (cache.spellName ~= oldName) then 

		-- We had a spell before, and there were more of it.
		-- We need to find the old ones, update their counts,
		-- and hide them if there's only a single one left. 
		if (oldRank and (oldCount > 1)) then 
			local newCount = oldCount - 1
			for button,otherCache in pairs(Cache) do 
				-- Ignore self, as we no longer have the same counter. 
				if (button ~= self) and (otherCache.spellName == oldName) then 
					otherCache.spellCount = newCount
					button:SetRankVisibility((newCount > 1))
				end
			end
		end 
	end 

	-- Counter for number of duplicates of the current spell
	local howMany = 0
	if (cache.spellRank) then 
		for button,otherCache in pairs(Cache) do 
			if (otherCache.spellName == cache.spellName) then 
				howMany = howMany + 1
			end 
		end
	end 

	-- Update stored counter
	cache.spellCount = howMany

	-- Update all rank texts and counters
	for button,otherCache in pairs(Cache) do 
		if (otherCache.spellName == cache.spellName) then 
			otherCache.spellCount = howMany
			button:SetRankVisibility((howMany > 1))
		end 
	end
end 

ActionButton.PostCreate = function(self, ...)
	-- Coding for troglodytes.
	local layout = Module.layout
	local forge = layout and layout.WidgetForge and layout.WidgetForge.ActionButton
	if (forge) then
		Module:Forge("Widget", self, forge)
	end
end 

-- PetButton Template (Custom Methods)
----------------------------------------------------
local PetButton = {}

PetButton.PostCreate = function(self, ...)
	-- Troglodytes will return!
	local layout = Module.layout
	local forge = layout and layout.WidgetForge and layout.WidgetForge.PetActionButton
	if (forge) then
		Module:Forge("Widget", self, forge)
	end
end 

PetButton.UpdateMouseOver = ActionButton.UpdateMouseOver
PetButton.PostUpdate = ActionButton.UpdateMouseOver
PetButton.PostEnter = ActionButton.PostEnter
PetButton.PostLeave = ActionButton.PostLeave

-- Bar Creation
----------------------------------------------------
Module.SpawnActionBars = function(self)
	local db = self.db
	local proxy = self:GetSecureUpdater()

	-- Private test mode to show all
	local FORCED = false 

	local buttonID = 0 -- current buttonID when spawning
	local numPrimary = 7 -- Number of primary buttons always visible
	local firstHiddenID = db.extraButtonsCount + numPrimary -- first buttonID to be hidden
	
	-- Primary Action Bar
	for id = 1,NUM_ACTIONBAR_BUTTONS do 
		buttonID = buttonID + 1
		Buttons[buttonID] = self:SpawnActionButton("action", self.frame, ActionButton, 1, id)
		HoverButtons[Buttons[buttonID]] = buttonID > numPrimary
	end 

	-- Secondary Action Bar (Bottom Left)
	for id = 1,NUM_ACTIONBAR_BUTTONS do 
		buttonID = buttonID + 1
		Buttons[buttonID] = self:SpawnActionButton("action", self.frame, ActionButton, BOTTOMLEFT_ACTIONBAR_PAGE, id)
		HoverButtons[Buttons[buttonID]] = true
	end 

	-- Layout helper
	for buttonID,button in pairs(Buttons) do
		button:SetAttribute("layoutID",buttonID)
	end
	
	-- First Side Bar (Bottom Right)
	if (false) then
		for id = 1,NUM_ACTIONBAR_BUTTONS do 
			buttonID = buttonID + 1
			Buttons[buttonID] = self:SpawnActionButton("action", self.frame, ActionButton, BOTTOMRIGHT_ACTIONBAR_PAGE, id)
		end

		-- Second Side bar (Right)
		for id = 1,NUM_ACTIONBAR_BUTTONS do 
			buttonID = buttonID + 1
			Buttons[buttonID] = self:SpawnActionButton("action", self.frame, ActionButton, RIGHT_ACTIONBAR_PAGE, id)
		end

		-- Third Side Bar (Left)
		for id = 1,NUM_ACTIONBAR_BUTTONS do 
			buttonID = buttonID + 1
			Buttons[buttonID] = self:SpawnActionButton("action", self.frame, ActionButton, LEFT_ACTIONBAR_PAGE, id)
		end
	end

	-- Apply common settings to the action buttons.
	for buttonID,button in ipairs(Buttons) do 
	
		-- Apply saved buttonLock setting
		button:SetAttribute("buttonLock", db.buttonLock)

		-- Link the buttons and their pagers 
		proxy:SetFrameRef("Button"..buttonID, Buttons[buttonID])
		proxy:SetFrameRef("Pager"..buttonID, Buttons[buttonID]:GetPager())

		-- Reference all buttons in our menu callback frame
		proxy:Execute(([=[
			table.insert(Buttons, self:GetFrameRef("Button"..%.0f)); 
			table.insert(Pagers, self:GetFrameRef("Pager"..%.0f)); 
		]=]):format(buttonID, buttonID))

		-- Hide buttons beyond our current maximum visible
		if (HoverButtons[button] and (buttonID > firstHiddenID)) then 
			button:GetPager():Hide()
		end 
	end 
end

Module.SpawnPetBar = function(self)
	local db = self.db
	local proxy = self:GetSecureUpdater()
	
	-- Spawn the Pet Bar
	for id = 1,NUM_PET_ACTION_SLOTS do
		PetButtons[id] = self:SpawnActionButton("pet", self.frame, PetButton, nil, id)
	end

	-- Apply common stuff to the pet buttons
	for id,button in pairs(PetButtons) do
		-- Apply saved buttonLock setting
		button:SetAttribute("buttonLock", db.buttonLock)

		-- Link the buttons and their pagers 
		proxy:SetFrameRef("PetButton"..id, PetButtons[id])
		proxy:SetFrameRef("PetPager"..id, PetButtons[id]:GetPager())

		if (not db.petBarEnabled) then
			PetButtons[id]:GetPager():Hide()
		end
		
		-- Reference all buttons in our menu callback frame
		proxy:Execute(([=[
			table.insert(PetButtons, self:GetFrameRef("PetButton"..%.0f)); 
			table.insert(PetPagers, self:GetFrameRef("PetPager"..%.0f)); 
		]=]):format(id, id))
		
	end
end

Module.SpawnStanceBar = function(self)
end

-- Hardcoded stuff here. Work in progress.
Module.SpawnTotemBar = function(self)
	local db = self.db

	-- Restrictions:
	-- 	Can't reposition or reparent in combat
	-- 	Can't remove button methods like SetPoint to prevent blizzard repositioning
	-- 	Can't really mess with PetFrame hide/show either, it needs to remain whatever Blizzard intended.

	local totemScale = 1.5 
	local width, height = 37*4 + (-4)*3, 37 -- (136*37) size of the totem buttons, plus space between them

	-- Just for my own reference:
	-- 	player castbar "BOTTOM", "UICenter", "BOTTOM", 0, 290
	-- 	player altpower "BOTTOM", "UICenter", "BOTTOM", 0, 340 ("CENTER", "UICenter", "CENTER", 0, -189)
	local totemHolderFrame = self:CreateFrame("Frame", nil, "UICenter")
	totemHolderFrame:SetSize(2,2)
	totemHolderFrame:Place("BOTTOM", self:GetFrame("Minimap"), "TOP", 0, 60)
	--totemHolderFrame:Place("BOTTOM", "UICenter", "BOTTOM", 0, 390)
	
	-- Scaling it up get a more fitting size,
	-- without messing with actual relative
	-- positioning of the buttons.
	local totemFrame = TotemFrame -- original size is 128x53
	totemFrame:SetParent(totemHolderFrame)
	totemFrame:SetScale(totemScale)
	totemFrame:SetSize(width, height)
	

	local hidden = CreateFrame("Frame")
	hidden:Hide()

	for i = 1,4 do -- MAX_TOTEMS = 4
		local buttonName = "TotemFrameTotem"..i
		local button = _G[buttonName]
		local buttonBackground = _G[buttonName.."Background"]
		local buttonIcon = _G[buttonName.."IconTexture"] -- doesn't support SetMask
		local buttonDuration = _G[buttonName.."Duration"]
		local buttonCooldown = _G[buttonName.."IconCooldown"] -- doesn't support SetMask

		buttonBackground:SetParent(hidden)
		buttonDuration:SetParent(hidden)
		buttonCooldown:SetReverse(false)
		
		local borderFrame, borderTexture
		for i = 1, button:GetNumChildren() do
			local child = select(i, button:GetChildren())
			if (child:GetObjectType() == "Frame") and (not child:GetName()) then
				for j = 1, child:GetNumRegions() do
					local region = select(j, child:GetRegions())
					if (region:GetObjectType() == "Texture") and (region:GetTexture() == [=[Interface\CharacterFrame\TotemBorder]=]) then
						region:ClearAllPoints()
						region:SetPoint("CENTER", 0, 0)
						region:SetTexture(GetMedia("actionbutton-border"))
						region:SetSize(256*.25,256*.25)
						borderFrame = child
						borderTexture = region
						break
					end
				end
			end
			if (borderFrame and borderTexture) then
				break
			end
		end
		button.borderFrame = borderFrame
		button.borderTexture = borderTexture

		local duration = borderFrame:CreateFontString()
		duration:SetDrawLayer("OVERLAY")
		duration:SetPoint("CENTER", button, "BOTTOMRIGHT", -8, 10)
		duration:SetFontObject(GetFont(9,true))
		duration:SetAlpha(.75)

		button.duration = duration
	end

	local totemButtonOnUpdate = function(button, elapsed)
		button.duration:SetFormattedText(formatCooldownTime(GetTotemTimeLeft(button.slot)))
	end

	local totemButtonUpdate = function(button, startTime, duration, icon)
		if (duration > 0) then
			button:SetScript("OnUpdate", totemButtonOnUpdate)
		else
			button:SetScript("OnUpdate", nil)
		end
	end
	hooksecurefunc("TotemButton_Update", totemButtonUpdate)

	local totemUpdate
	totemUpdate = function(self, event, ...)
		-- Trying the tainty way
		--if (InCombatLockdown()) then
		--	self:RegisterEvent("PLAYER_REGEN_ENABLED", totemUpdate)
		--	return
		--end
		--if (event == "PLAYER_REGEN_ENABLED") then
		--	self:UnregisterEvent("PLAYER_REGEN_ENABLED", totemUpdate)
		--end
		local point, anchor = totemFrame:GetPoint()
		if (anchor ~= totemHolderFrame) then
			totemFrame:ClearAllPoints()
			totemFrame:SetPoint("CENTER", totemHolderFrame, "CENTER", 0, 0)
		end
	end
	hooksecurefunc(TotemFrame, "SetPoint", totemUpdate)

	-- Initial update to position it
	totemUpdate()
end

Module.SpawnExitButton = function(self)
	local layout = self.layout

	local button = self:CreateFrame("Button", nil, "UICenter", "SecureActionButtonTemplate")
	button:SetFrameStrata("LOW") -- MEDIUM is too high, collides with Immersion
	button:SetFrameLevel(100)
	button:Place(unpack(layout.ExitButtonPlace))
	button:SetSize(unpack(layout.ExitButtonSize))
	button:SetAttribute("type", "macro")

	if (IsClassic) then
		button:SetAttribute("macrotext", "/dismount [mounted]")
	elseif (IsRetail) then
		button:SetAttribute("macrotext", "/leavevehicle [target=vehicle,exists,canexitvehicle]\n/dismount [mounted]")
	end

	-- Put our texture on the button
	button.texture = button:CreateTexture()
	button.texture:SetSize(unpack(layout.ExitButtonTextureSize))
	button.texture:SetPoint(unpack(layout.ExitButtonTexturePlace))
	button.texture:SetTexture(layout.ExitButtonTexturePath)

	button:SetScript("OnEnter", function(button)
		local tooltip = self:GetActionButtonTooltip()
		tooltip:Hide()
		tooltip:SetDefaultAnchor(button)

		if UnitOnTaxi("player") then 
			tooltip:AddLine(TAXI_CANCEL)
			tooltip:AddLine(TAXI_CANCEL_DESCRIPTION, Colors.quest.green[1], Colors.quest.green[2], Colors.quest.green[3])
		elseif IsMounted() then 
			tooltip:AddLine(BINDING_NAME_DISMOUNT)
			tooltip:AddLine(L["%s to dismount."]:format(L["<Left-Click>"]), Colors.quest.green[1], Colors.quest.green[2], Colors.quest.green[3])
		else
			tooltip:AddLine(LEAVE_VEHICLE)
			tooltip:AddLine(L["%s to leave the vehicle."]:format(L["<Left-Click>"]), Colors.quest.green[1], Colors.quest.green[2], Colors.quest.green[3])
		end 

		tooltip:Show()
	end)

	button:SetScript("OnLeave", function(button) 
		local tooltip = self:GetActionButtonTooltip()
		tooltip:Hide()
	end)

	-- Gotta do this the unsecure way, no macros exist for this yet. 
	button:HookScript("OnClick", function(self, button) 
		if (UnitOnTaxi("player") and (not InCombatLockdown())) then
			TaxiRequestEarlyLanding()
		end
	end)

	-- Register a visibility driver
	if (IsClassic) then
		RegisterAttributeDriver(button, "state-visibility", "[mounted]show;hide")
	elseif (IsRetail) then
		RegisterAttributeDriver(button, "state-visibility", "[target=vehicle,exists,canexitvehicle][mounted]show;hide")
	end

	self.VehicleExitButton = button
end

-- Getters
----------------------------------------------------
-- Return an iterator for actionbar buttons
Module.GetButtons = function(self)
	return pairs(Buttons)
end

-- Return an iterator for pet actionbar buttons
Module.GetPetButtons = function(self)
	return pairs(PetButtons)
end

-- Return the frames for the explorer mode mouseover
Module.GetExplorerModeFrameAnchors = function(self)
	return self:GetOverlayFrame(), self:GetOverlayFramePet()
end

-- Return the actionbar frame for the explorer mode mouseover
Module.GetOverlayFrame = function(self)
	return self.frameOverlay
end

-- Return the pet actionbar frame for the explorer mode mouseover
Module.GetOverlayFramePet = function(self)
	return self.frameOverlayPet
end

-- Return the frame for actionbutton mouseover fading
Module.GetFadeFrame = function(self)
	if (not ActionBarHoverFrame) then 
		ActionBarHoverFrame = self:CreateFrame("Frame")
		ActionBarHoverFrame.timeLeft = 0
		ActionBarHoverFrame.elapsed = 0
		ActionBarHoverFrame:SetScript("OnUpdate", function(self, elapsed) 
			self.elapsed = self.elapsed + elapsed
			self.timeLeft = self.timeLeft - elapsed
	
			if (self.timeLeft <= 0) then
				if FORCED or self.FORCED or self.always or (self.incombat and IN_COMBAT) or self.forced or self.flyout or self:IsMouseOver(0,0,0,0) then
					if (not self.isMouseOver) then 
						self.isMouseOver = true
						self.alpha = 1
						for id = 8,24 do 
							Buttons[id]:GetPager():SetAlpha(self.alpha)
						end 
					end 
				else 
					if (self.isMouseOver) then 
						self.isMouseOver = nil
						if (not self.fadeOutTime) then 
							self.fadeOutTime = FadeOutDuration
						end 
					end 
					if (self.fadeOutTime) then 
						self.fadeOutTime = self.fadeOutTime - self.elapsed
						if (self.fadeOutTime > 0) then 
							self.alpha = self.fadeOutTime / FadeOutDuration
						else 
							self.alpha = 0
							self.fadeOutTime = nil
						end 
						for id = 8,24 do 
							Buttons[id]:GetPager():SetAlpha(self.alpha)
						end 
					end 
				end 
				self.elapsed = 0
				self.timeLeft = FadeOutHZ
			end 
		end) 

		local actionBarGrid, petBarGrid, buttonLock
		ActionBarHoverFrame:SetScript("OnEvent", function(self, event, ...) 
			if (event == "ACTIONBAR_SHOWGRID") then 
				actionBarGrid = true
			elseif (event == "ACTIONBAR_HIDEGRID") then 
				actionBarGrid = nil
			elseif (event == "PET_BAR_SHOWGRID") then 
				petBarGrid = true
			elseif (event == "PET_BAR_HIDEGRID") then 
				petBarGrid = nil
			elseif (event == "buttonLock") then
				actionBarGrid = nil
				petBarGrid = nil
			end
			if (actionBarGrid or petBarGrid) then
				self.forced = true
			else
				self.forced = nil
			end 
		end)

		hooksecurefunc("ActionButton_UpdateFlyout", function(self) 
			if (HoverButtons[self]) then 
				ActionBarHoverFrame.flyout = self:IsFlyoutShown()
			end
		end)

		ActionBarHoverFrame:RegisterEvent("ACTIONBAR_HIDEGRID")
		ActionBarHoverFrame:RegisterEvent("ACTIONBAR_SHOWGRID")

		if (IsRetail) then
			ActionBarHoverFrame:RegisterEvent("PET_BAR_HIDEGRID")
			ActionBarHoverFrame:RegisterEvent("PET_BAR_SHOWGRID")
		end

		ActionBarHoverFrame.isMouseOver = true -- Set this to initiate the first fade-out
	end
	return ActionBarHoverFrame
end

-- Return the frame for pet actionbutton mouseover fading
Module.GetFadeFramePet = function(self)
	if (not PetBarHoverFrame) then
		PetBarHoverFrame = self:CreateFrame("Frame")
		PetBarHoverFrame.timeLeft = 0
		PetBarHoverFrame.elapsed = 0
		PetBarHoverFrame:SetScript("OnUpdate", function(self, elapsed) 
			self.elapsed = self.elapsed + elapsed
			self.timeLeft = self.timeLeft - elapsed
	
			if (self.timeLeft <= 0) then
				if FORCED or self.FORCED or self.always or (self.incombat and IN_COMBAT) or self.forced or self.flyout or self:IsMouseOver(0,0,0,0) then
					if (not self.isMouseOver) then 
						self.isMouseOver = true
						self.alpha = 1
						for id in pairs(PetButtons) do
							PetButtons[id]:GetPager():SetAlpha(self.alpha)
						end 
					end
				else 
					if (self.isMouseOver) then 
						self.isMouseOver = nil
						if (not self.fadeOutTime) then 
							self.fadeOutTime = FadeOutDuration
						end 
					end 
					if (self.fadeOutTime) then 
						self.fadeOutTime = self.fadeOutTime - self.elapsed
						if (self.fadeOutTime > 0) then 
							self.alpha = self.fadeOutTime / FadeOutDuration
						else 
							self.alpha = 0
							self.fadeOutTime = nil
						end 
						for id in pairs(PetButtons) do
							PetButtons[id]:GetPager():SetAlpha(self.alpha)
						end 
					end 
				end 
				self.elapsed = 0
				self.timeLeft = FadeOutHZ
			end 
		end) 

		PetBarHoverFrame:SetScript("OnEvent", function(self, event, ...) 
			if (event == "PET_BAR_SHOWGRID") then 
				self.forced = true
			elseif (event == "PET_BAR_HIDEGRID") or (event == "buttonLock") then
				self.forced = nil
			end 
		end)


		PetBarHoverFrame:RegisterEvent("PET_BAR_SHOWGRID")
		PetBarHoverFrame:RegisterEvent("PET_BAR_HIDEGRID")
		PetBarHoverFrame.isMouseOver = true -- Set this to initiate the first fade-out
	end
	return PetBarHoverFrame
end

-- Setters
----------------------------------------------------
Module.SetForcedVisibility = function(self, force)
	local actionBarHoverFrame = self:GetFadeFrame()
	actionBarHoverFrame.FORCED = force and true
end

-- Updates
----------------------------------------------------
Module.UpdateFading = function(self)
	local db = self.db

	-- Set action bar hover settings
	local actionBarHoverFrame = self:GetFadeFrame()
	actionBarHoverFrame.incombat = db.extraButtonsVisibility == "combat"
	actionBarHoverFrame.always = db.extraButtonsVisibility == "always"

	-- We're hardcoding these until options can be added
	local petBarHoverFrame = self:GetFadeFramePet()
	petBarHoverFrame.incombat = db.petBarVisibility == "combat"
	petBarHoverFrame.always = db.petBarVisibility == "always"
end 

Module.UpdateExplorerModeAnchors = function(self)
	local db = self.db
	local frame = self:GetOverlayFramePet()
	if (self.db.petBarEnabled) and (UnitExists("pet")) then
		frame:ClearAllPoints()
		frame:SetPoint("TOPLEFT", PetButtons[1], "TOPLEFT")
		frame:SetPoint("BOTTOMRIGHT", PetButtons[10], "BOTTOMRIGHT")
	else
		frame:ClearAllPoints()
		frame:SetAllPoints(self:GetFrame())
	end
end

Module.UpdateFadeAnchors = function(self)
	local db = self.db

	-- Parse buttons for hoverbutton IDs
	local first, last, left, right, top, bottom, mLeft, mRight, mTop, mBottom
	for id,button in ipairs(Buttons) do 
		-- If we pass number of visible hoverbuttons, just bail out
		if (id > db.extraButtonsCount + 7) then 
			break 
		end 

		local bLeft = button:GetLeft()
		local bRight = button:GetRight()
		local bTop = button:GetTop()
		local bBottom = button:GetBottom()
		
		if HoverButtons[button] then 
			-- Only counting the first encountered as the first
			if (not first) then 
				first = id 
			end 

			-- Counting every button as the last, until we actually reach it 
			last = id 

			-- Figure out hoverframe anchor buttons
			left = left and (Buttons[left]:GetLeft() < bLeft) and left or id
			right = right and (Buttons[right]:GetRight() > bRight) and right or id
			top = top and (Buttons[top]:GetTop() > bTop) and top or id
			bottom = bottom and (Buttons[bottom]:GetBottom() < bBottom) and bottom or id
		end 

		-- Figure out main frame anchor buttons, 
		-- as we need this for the explorer mode fade anchors!
		mLeft = mLeft and (Buttons[mLeft]:GetLeft() < bLeft) and mLeft or id
		mRight = mRight and (Buttons[mRight]:GetRight() > bRight) and mRight or id
		mTop = mTop and (Buttons[mTop]:GetTop() > bTop) and mTop or id
		mBottom = mBottom and (Buttons[mBottom]:GetBottom() < bBottom) and mBottom or id
	end 

	-- Setup main frame anchors for explorer mode! 
	local overlayFrame = self:GetOverlayFrame()
	overlayFrame:ClearAllPoints()
	overlayFrame:SetPoint("TOP", Buttons[mTop], "TOP", 0, 0)
	overlayFrame:SetPoint("BOTTOM", Buttons[mBottom], "BOTTOM", 0, 0)
	overlayFrame:SetPoint("LEFT", Buttons[mLeft], "LEFT", 0, 0)
	overlayFrame:SetPoint("RIGHT", Buttons[mRight], "RIGHT", 0, 0)

	-- If we have hoverbuttons, setup the anchors
	if (left and right and top and bottom) then 
		local actionBarHoverFrame = self:GetFadeFrame()
		actionBarHoverFrame:ClearAllPoints()
		actionBarHoverFrame:SetPoint("TOP", Buttons[top], "TOP", 0, 0)
		actionBarHoverFrame:SetPoint("BOTTOM", Buttons[bottom], "BOTTOM", 0, 0)
		actionBarHoverFrame:SetPoint("LEFT", Buttons[left], "LEFT", 0, 0)
		actionBarHoverFrame:SetPoint("RIGHT", Buttons[right], "RIGHT", 0, 0)
	end

	local petBarHoverFrame = self:GetFadeFramePet()
	if (self.db.petBarEnabled) then
		petBarHoverFrame:ClearAllPoints()
		petBarHoverFrame:SetPoint("TOPLEFT", PetButtons[1], "TOPLEFT")
		petBarHoverFrame:SetPoint("BOTTOMRIGHT", PetButtons[10], "BOTTOMRIGHT")
	else
		petBarHoverFrame:ClearAllPoints()
		petBarHoverFrame:SetAllPoints(self:GetFrame())
	end

	self:UpdateButtonGrids()
end

Module.UpdateButtonCount = function(self)
	-- Update our smart button grids
	self:UpdateButtonGrids()

	-- Announce the updated button count to the world
	self:SendMessage("GP_UPDATE_ACTIONBUTTON_COUNT")
end

Module.UpdateButtonGrids = function(self)
	local db = self.db 
	local numButtons = db.extraButtonsCount + 7
	local button, buttonHasContent, forceGrid

	if (IsRetail) then
		-- Completely hide grids in vehicles and event driven bars
		if (HasOverrideActionBar() or HasTempShapeshiftActionBar() or HasVehicleActionBar()) then
			for buttonID = numButtons,1,-1 do
				button = Buttons[buttonID]
				button.showGrid = nil
				button.overrideAlphaWhenEmpty = nil
				button:UpdateGrid()
			end
			return
		end
	end

	local gapStartTop, gapStartBottom
	local gapCountTop, gapCountBottom = 0, 0
	local buttonID = 1
	while (buttonID <= numButtons) do

		local onMain = (buttonID <= 7)
		local onBottom = (buttonID <= 24) and ((onMain) or (buttonID%2 == 0))
		local onTop = (buttonID <= 24) and ((not onMain) and (buttonID%2 > 0))

		button = Buttons[buttonID]
		button.showGrid = nil
		button.overrideAlphaWhenEmpty = nil

		if (button:HasContent()) then
			
			if (onBottom) then
				if (gapStartBottom) and (gapStartBottom > 1) then
					if (gapCountBottom == 1) then
						for id = gapStartBottom,gapStartBottom+gapCountBottom-1,2 do
							button = Buttons[id]
							button.showGrid = true
							button.overrideAlphaWhenEmpty = .95
						end
					end
				end
				gapStartBottom = nil
				gapCountBottom = 0

			elseif (onTop) then
				if (gapStartTop) and (gapStartTop > 9) then
					if (gapCountTop == 1) then
						for id = gapStartTop,gapStartTop+gapCountTop-1,2 do
							button = Buttons[id]
							button.showGrid = true
							button.overrideAlphaWhenEmpty = .95
						end
					end
				end
				gapStartTop = nil
				gapCountTop = 0
			end

		else

			-- We are on bottom row
			if (onBottom) then
				if (not gapStartBottom) then
					gapStartBottom = buttonID
				end
				gapCountBottom = gapCountBottom + 1

			-- We are on top Row
			else
				if (not gapStartTop) then
					gapStartTop = buttonID
				end
				gapCountTop = gapCountTop + 1
			end
		end
		buttonID = buttonID + 1
	end

	for buttonID = numButtons,1,-1 do
		button = Buttons[buttonID]
		button:UpdateGrid()
	end
end

-- Just a proxy for the secure arrangement method.
-- Only ever call this out of combat, as it does not check for it.
Module.UpdateButtonLayout = function(self)
	local proxy = self:GetSecureUpdater()
	if (proxy) then
		proxy:Execute(proxy:GetAttribute("arrangeButtons"))
		proxy:Execute(proxy:GetAttribute("arrangePetButtons"))
	end
end

Module.UpdateCastOnDown = function(self)
	if InCombatLockdown() then 
		return self:RegisterEvent("PLAYER_REGEN_ENABLED", "UpdateSettings")
	end
	if (event == "PLAYER_REGEN_ENABLED") then 
		self:UnregisterEvent("PLAYER_REGEN_ENABLED", "UpdateSettings")
	end 
	local db = self.db
	for button in self:GetAllActionButtonsOrdered() do
		button:RegisterForClicks(db.castOnDown and "AnyDown" or "AnyUp")
		button:Update()
	end 
end 

Module.UpdateConsolePortBindings = function(self)
	local CP = _G.ConsolePort
	if (not CP) then 
		return 
	end 
end

Module.UpdateBindings = function(self)
	if (IsConsolePortEnabled) then 
		self:UpdateConsolePortBindings()
	else
		self:UpdateActionButtonBindings()
	end
end

Module.UpdateTooltipSettings = function(self)
	local layout = self.layout
	local tooltip = self:GetActionButtonTooltip()
	tooltip.colorNameAsSpellWithUse = layout.TooltipColorNameAsSpellWithUse
	tooltip.hideItemLevelWithUse = layout.TooltipHideItemLevelWithUse
	tooltip.hideStatsWithUseEffect = layout.TooltipHideStatsWithUse
	tooltip.hideBindsWithUseEffect = layout.TooltipHideBindsWithUse
	tooltip.hideUniqueWithUseEffect = layout.TooltipHideUniqueWithUse
	tooltip.hideEquipTypeWithUseEffect = layout.TooltipHideEquipTypeWithUse
end 

Module.UpdateSettings = function(self, event, ...)
	self:UpdateFading()
	self:UpdateFadeAnchors()
	self:UpdateExplorerModeAnchors()
	self:UpdateCastOnDown()
	self:UpdateTooltipSettings()
end 

-- Initialization
----------------------------------------------------
Module.OnEvent = function(self, event, ...)
	if (event == "UPDATE_BINDINGS") then 
		self:UpdateBindings()
	elseif (event == "PLAYER_ENTERING_WORLD") then
		IN_COMBAT = false
		self:UpdateBindings()
	elseif (event == "PLAYER_REGEN_DISABLED") then
		IN_COMBAT = true 
	elseif (event == "PLAYER_REGEN_ENABLED") then
		IN_COMBAT = false
	elseif (event == "ACTIONBAR_SLOT_CHANGED") then
		self:UpdateButtonGrids()
	elseif (event == "GP_FORCED_ACTIONBAR_VISIBILITY_REQUESTED") then
		self:SetForcedVisibility(true)
	elseif (event == "GP_FORCED_ACTIONBAR_VISIBILITY_CANCELED") then
		self:SetForcedVisibility(false)
	elseif (event == "PET_BAR_UPDATE") then
		self:UpdateExplorerModeAnchors()
	else
		self:UpdateButtonGrids()
	end 
end 

Module.OnInit = function(self)
	self:PurgeSavedSettingFromAllProfiles(self:GetName(), 
		"buttonsPrimary", 
		"buttonsComplimentary", 
		"editMode", 
		"enableComplimentary", 
		"enableStance", 
		"enablePet", 
		"showBinds", 
		"showCooldown", 
		"showCooldownCount",
		"showNames",
		"visibilityPrimary",
		"visibilityComplimentary",
		"visibilityStance", 
		"visibilityPet"
	)
	self.db = GetConfig(self:GetName())
	self.layout = GetLayout(self:GetName())
	if (not self.layout) then
		return self:SetUserDisabled(true)
	end

	local OptionsMenu = Core:GetModule("OptionsMenu", true)
	if (OptionsMenu) then
		local callbackFrame = OptionsMenu:CreateCallbackFrame(self)
		callbackFrame:AssignSettings(self.db)
		callbackFrame:AssignProxyMethods("UpdateCastOnDown", "UpdateFading", "UpdateFadeAnchors", "UpdateExplorerModeAnchors", "UpdateButtonCount")

		-- Create tables to hold the buttons
		-- within the restricted environment.
		callbackFrame:Execute([=[ 
			Buttons = table.new();
			Pagers = table.new();
			PetButtons = table.new();
			PetPagers = table.new();
			StanceButtons = table.new();
		]=])

		-- Apply references and attributes used for updates.
		callbackFrame:AssignAttributes(
			"BOTTOMLEFT_ACTIONBAR_PAGE", BOTTOMLEFT_ACTIONBAR_PAGE,
			"BOTTOMRIGHT_ACTIONBAR_PAGE", BOTTOMRIGHT_ACTIONBAR_PAGE,
			"RIGHT_ACTIONBAR_PAGE", RIGHT_ACTIONBAR_PAGE,
			"LEFT_ACTIONBAR_PAGE", LEFT_ACTIONBAR_PAGE,
			"arrangeButtons", secureSnippets.arrangeButtons,
			"arrangePetButtons", secureSnippets.arrangePetButtons
		)

		callbackFrame:AssignCallback(secureSnippets.attributeChanged)
	end

	-- Create master frame. This one becomes secure.
	self.frame = self:CreateFrame("Frame", nil, "UICenter")

	-- Create overlay frames used for explorer mode.
	self.frameOverlay = self:CreateFrame("Frame", nil, "UICenter")
	self.frameOverlayPet = self:CreateFrame("Frame", nil, "UICenter")

	-- Apply overlay alpha to the master frame.
	hooksecurefunc(self.frameOverlay, "SetAlpha", function(_,alpha) self.frame:SetAlpha(alpha) end)

	-- Spawn the bars
	self:SpawnActionBars()
	self:SpawnPetBar()
	self:SpawnStanceBar()
	self:SpawnExitButton()

	-- Verified to only exist in retail.
	if (IsRetail) then
		self:SpawnTotemBar()
	end

	-- Arrange buttons
	-- *We're using the non-secure proxy method here,
	--  so take care to only ever do this out of combat.
	self:UpdateButtonLayout()

	-- Update saved settings
	self:UpdateBindings()
	self:UpdateSettings()
end 

Module.OnEnable = function(self)
	self:RegisterEvent("ACTIONBAR_SLOT_CHANGED", "OnEvent")
	self:RegisterEvent("PET_BAR_UPDATE", "OnEvent")
	self:RegisterEvent("PLAYER_ENTERING_WORLD", "OnEvent")
	self:RegisterEvent("PLAYER_REGEN_ENABLED", "OnEvent")
	self:RegisterEvent("PLAYER_REGEN_DISABLED", "OnEvent")
	self:RegisterEvent("UPDATE_BINDINGS", "OnEvent")

	if (IsRetail) then
		self:RegisterEvent("UPDATE_OVERRIDE_ACTIONBAR", "OnEvent")
		self:RegisterEvent("UPDATE_VEHICLE_ACTIONBAR", "OnEvent")
	end
end
