
-- globals
MULTICASTACTIONBAR_SLIDETIME = 0.09;
MULTICASTACTIONBAR_YPOS = 0;
MULTICASTACTIONBAR_XPOS = 30;
NUM_MULTI_CAST_PAGES = 3;
NUM_MULTI_CAST_BUTTONS_PER_PAGE = 4;		-- NOTE: must match MAX_TOTEMS!!!

-- locals
local MULTI_CAST_FLYOUT_BUTTON_INITIAL_OFFSET = 4;
local MULTI_CAST_FLYOUT_BUTTON_OFFSET = 2;
local MULTI_CAST_FLYOUT_CLOSE_SEC = 3;

local SLOT_EMPTY_TCOORDS = {
	[EARTH_TOTEM_SLOT] = {
		left	= 66 / 128,
		right	= 96 / 128,
		top		= 3 / 256,
		bottom	= 33 / 256,
	},
	[FIRE_TOTEM_SLOT] = {
		left	= 67 / 128,
		right	= 97 / 128,
		top		= 100 / 256,
		bottom	= 130 / 256,
	},
	[WATER_TOTEM_SLOT] = {
		left	= 39 / 128,
		right	= 69 / 128,
		top		= 209 / 256,
		bottom	= 239 / 256,
	},
	[AIR_TOTEM_SLOT] = {
		left	= 66 / 128,
		right	= 96 / 128,
		top		= 36 / 256,
		bottom	= 66 / 256,
	},
};
local SLOT_OVERLAY_TCOORDS = {
	[EARTH_TOTEM_SLOT] = {
		left	= 1 / 128,
		right	= 35 / 128,
		top		= 172 / 256,
		bottom	= 206 / 256,
	},
	[FIRE_TOTEM_SLOT] = {
		left	= 36 / 128,
		right	= 70 / 128,
		top		= 172 / 256,
		bottom	= 206 / 256,
	},
	[WATER_TOTEM_SLOT] = {
		left	= 1 / 128,
		right	= 35 / 128,
		top		= 207 / 256,
		bottom	= 240 / 256,
	},
	[AIR_TOTEM_SLOT] = {
		left	= 36 / 128,
		right	= 70 / 128,
		top		= 137 / 256,
		bottom	= 171 / 256,
	},
};

local FLYOUT_UP_BUTTON_TCOORDS = {
	["summon"] = {
		left	= 99 / 128,
		right	= 127 / 128,
		top		= 84 / 256,
		bottom	= 102 / 256,
	},
	[EARTH_TOTEM_SLOT] = {
		left	= 99 / 128,
		right	= 127 / 128,
		top		= 160 / 256,
		bottom	= 178 / 256,
	},
	[FIRE_TOTEM_SLOT] = {
		left	= 99 / 128,
		right	= 127 / 128,
		top		= 122 / 256,
		bottom	= 140 / 256,
	},
	[WATER_TOTEM_SLOT] = {
		left	= 99 / 128,
		right	= 127 / 128,
		top		= 199 / 256,
		bottom	= 217 / 256,
	},
	[AIR_TOTEM_SLOT] = {
		left	= 99 / 128,
		right	= 127 / 128,
		top		= 237 / 256,
		bottom	= 255 / 256,
	},
};
local FLYOUT_DOWN_BUTTON_TCOORDS = {
	["summon"] = {
		left	= 99 / 128,
		right	= 127 / 128,
		top		= 65 / 256,
		bottom	= 83 / 256,
	},
	[EARTH_TOTEM_SLOT] = {
		left	= 99 / 128,
		right	= 127 / 128,
		top		= 141 / 256,
		bottom	= 159 / 256,
	},
	[FIRE_TOTEM_SLOT] = {
		left	= 99 / 128,
		right	= 127 / 128,
		top		= 103 / 256,
		bottom	= 121 / 256,
	},
	[WATER_TOTEM_SLOT] = {
		left	= 99 / 128,
		right	= 127 / 128,
		top		= 180 / 256,
		bottom	= 198 / 256,
	},
	[AIR_TOTEM_SLOT] = {
		left	= 99 / 128,
		right	= 127 / 128,
		top		= 218 / 256,
		bottom	= 236 / 256,
	},
};
local FLYOUT_TOP_TCOORDS = {
	["summon"] = {
		left	= 33 / 128,
		right	= 65 / 128,
		top		= 1 / 256,
		bottom	= 23 / 256,
	},
	[EARTH_TOTEM_SLOT] = {
		left	= 0 / 128,
		right	= 32 / 128,
		top		= 46 / 256,
		bottom	= 68 / 256,
	},
	[FIRE_TOTEM_SLOT] = {
		left	= 33 / 128,
		right	= 65 / 128,
		top		= 46 / 256,
		bottom	= 68 / 256,
	},
	[WATER_TOTEM_SLOT] = {
		left	= 0 / 128,
		right	= 32 / 128,
		top		= 1 / 256,
		bottom	= 23 / 256,
	},
	[AIR_TOTEM_SLOT] = {
		left	= 0 / 128,
		right	= 32 / 128,
		top		= 91 / 256,
		bottom	= 113 / 256,
	},
};
local FLYOUT_MIDDLE_TCOORDS = {
	["summon"] = {
		left	= 33 / 128,
		right	= 65 / 128,
		top		= 23 / 256,
		bottom	= 43 / 256,
	},
	[EARTH_TOTEM_SLOT] = {
		left	= 0 / 128,
		right	= 32 / 128,
		top		= 68 / 256,
		bottom	= 88 / 256,
	},
	[FIRE_TOTEM_SLOT] = {
		left	= 33 / 128,
		right	= 65 / 128,
		top		= 68 / 256,
		bottom	= 88 / 256,
	},
	[WATER_TOTEM_SLOT] = {
		left	= 0 / 128,
		right	= 32 / 128,
		top		= 23 / 256,
		bottom	= 43 / 256,
	},
	[AIR_TOTEM_SLOT] = {
		left	= 0 / 128,
		right	= 32 / 128,
		top		= 113 / 256,
		bottom	= 133 / 256,
	},
};

-- knownMultiCastSummonSpells
-- index: TOTEM_MULTI_CAST_SUMMON_SPELLS 
-- value: spellId if the spell is known, nil otherwise
local knownMultiCastSummonSpells = { };
-- knownMultiCastRecallSpells
-- index: TOTEM_MULTI_CAST_RECALL_SPELLS 
-- value: spellId if the spell is known, nil otherwise
local knownMultiCastRecallSpells = { };

local function _ComputeMultiCastSlot(id)
	return mod(id - 1, NUM_MULTI_CAST_BUTTONS_PER_PAGE) + 1;
end

--
-- MultiCastActionBar
--

function MultiCastActionBarFrame_OnLoad(self)
	self:RegisterEvent("PLAYER_ENTERING_WORLD");
	self:RegisterEvent("UPDATE_MULTI_CAST_ACTIONBAR");

	self.currentPage = 1;
	MultiCastSummonSpellButton:SetID(self.currentPage);

	self.numActiveSlots = 0;
end

function MultiCastActionBarFrame_OnEvent(self, event, ...)
	if ( event == "PLAYER_ENTERING_WORLD" or event == "UPDATE_MULTI_CAST_ACTIONBAR" ) then
		MultiCastActionBarFrame_Update(self);
		if ( HasMultiCastActionBar() ) then
			ShowMultiCastActionBar();
			LockMultiCastActionBar();
		else
			UnlockMultiCastActionBar();
			HideMultiCastActionBar();
		end
	end
end

function MultiCastActionBarFrame_OnUpdate(self, elapsed)
	local yPos;
	if ( self.slideTimer and (self.slideTimer < self.timeToSlide) ) then
		self.completed = false;
		if ( self.mode == "show" ) then
			yPos = (self.slideTimer/self.timeToSlide) * MULTICASTACTIONBAR_YPOS;
			self:SetPoint("BOTTOMLEFT", self:GetParent(), "TOPLEFT", MULTICASTACTIONBAR_XPOS, yPos);
			self.state = "showing";
			self:Show();
		elseif ( self.mode == "hide" ) then
			yPos = (1 - (self.slideTimer/self.timeToSlide)) * MULTICASTACTIONBAR_YPOS;
			self:SetPoint("BOTTOMLEFT", self:GetParent(), "TOPLEFT", MULTICASTACTIONBAR_XPOS, yPos);
			self.state = "hiding";
		end
		self.slideTimer = self.slideTimer + elapsed;
	else
		self.completed = true;
		if ( self.mode == "show" ) then
			self:SetPoint("BOTTOMLEFT", self:GetParent(), "TOPLEFT", MULTICASTACTIONBAR_XPOS, MULTICASTACTIONBAR_YPOS);
			self.state = "top";
			--Move the chat frame and edit box up a bit
		elseif ( self.mode == "hide" ) then
			self:SetPoint("BOTTOMLEFT", self:GetParent(), "TOPLEFT", MULTICASTACTIONBAR_XPOS, 0);
			self.state = "bottom";
			self:Hide();
			--Move the chat frame and edit box back down to original position
		end
		self.mode = "none";
	end
end

function ShowMultiCastActionBar(doNotSlide)
	if ( (not MainMenuBar.busy) and (not UnitHasVehicleUI("player")) ) then	--Don't change while we're animating out MainMenuBar for vehicle UI
		if ( (MultiCastActionBarFrame.mode ~= "show" and MultiCastActionBarFrame.state ~= "top") or (not UIParent:IsShown())) then
			MultiCastActionBarFrame:Show();
			if ( MultiCastActionBarFrame.completed ) then
				MultiCastActionBarFrame.slideTimer = 0;
			end
			MultiCastActionBarFrame.timeToSlide = MULTICASTACTIONBAR_SLIDETIME;
			MultiCastActionBarFrame.mode = "show";
		end
	end
end

function HideMultiCastActionBar()
	if ( (not MainMenuBar.busy) and (not UnitHasVehicleUI("player")) ) then	--Don't change while we're animating out MainMenuBar for vehicle UI
		if ( (MultiCastActionBarFrame:IsShown()) or (not UIParent:IsShown())) then
			if ( MultiCastActionBarFrame.completed ) then
				MultiCastActionBarFrame.slideTimer = 0;
			end
			MultiCastActionBarFrame.timeToSlide = MULTICASTACTIONBAR_SLIDETIME;
			MultiCastActionBarFrame.mode = "hide";
		end
	end
end

function LockMultiCastActionBar()
	MultiCastActionBarFrame.locked = true;
end

function UnlockMultiCastActionBar()
	MultiCastActionBarFrame.locked = false;
end

function MultiCastActionBarFrame_Update(self)
	local currentPage = self.currentPage;

	-- update the action buttons and slot buttons
	local slot;
	local actionButton, slotButton;
	local actionIndex;
	local slotIndex = 1;
	local inverseSlotIndex;
	local pageOffset;
	local actionId;
	for i = 1, NUM_MULTI_CAST_BUTTONS_PER_PAGE do
		slot = SHAMAN_TOTEM_PRIORITIES[i];
		if ( GetTotemInfo(slot) and GetMultiCastTotemSpells(slot) ) then
			-- update the slot button
			slotButton = _G["MultiCastSlotButton"..slotIndex];
			MultiCastSlotButton_Update(slotButton, slot);
			-- update the action buttons using this totem slot on each page
			for j = 1, NUM_MULTI_CAST_PAGES do
				pageOffset = (j-1) * NUM_MULTI_CAST_BUTTONS_PER_PAGE;
				actionIndex = pageOffset + slotIndex;
				actionButton = _G["MultiCastActionButton"..actionIndex];
				actionButton.slotButton = slotButton;
				actionId = pageOffset + slot;
				MultiCastActionButton_Update(actionButton, actionId, actionIndex, slot);
				-- if this is the current page, store a reference to the action button on the slot button
				if ( j == currentPage ) then
					slotButton.actionButton = actionButton;
				end
			end
			slotIndex = slotIndex + 1;
		else
			inverseSlotIndex = NUM_MULTI_CAST_BUTTONS_PER_PAGE - i + slotIndex;
			-- hide the slot button
			slotButton = _G["MultiCastSlotButton"..inverseSlotIndex];
			MultiCastSlotButton_Update(slotButton, 0);
			-- hide the action buttons for this button index on each page
			for j = 1, NUM_MULTI_CAST_PAGES do
				pageOffset = (j-1) * NUM_MULTI_CAST_BUTTONS_PER_PAGE;
				actionIndex = pageOffset + inverseSlotIndex;
				actionButton = _G["MultiCastActionButton"..actionIndex];
				actionButton.slotButton = nil;
				actionId = pageOffset + slot;
				MultiCastActionButton_Update(actionButton, actionId, actionIndex, 0);
			end
			slotButton.actionButton = nil;
		end
	end

	self.numActiveSlots = slotIndex - 1;
	if ( self.numActiveSlots == 0 ) then
		self:Hide();
		return;
	end
	self:Show();

	-- update the multi cast spells
	MultiCastSummonSpellButton_Update(MultiCastSummonSpellButton);
	MultiCastRecallSpellButton_Update(MultiCastRecallSpellButton);
end

function HasMultiCastActionBar()
	return MultiCastActionBarFrame.numActiveSlots and MultiCastActionBarFrame.numActiveSlots > 0;
end

function HasMultiCastActionPage(page)
	return ( page >= 1 and page <= NUM_MULTI_CAST_PAGES and knownMultiCastSummonSpells[page] );
end

function ChangeMultiCastActionPage(page)
	local prevPage = MultiCastActionBarFrame.currentPage;
	if ( page ~= prevPage and HasMultiCastActionPage(page) ) then
		MultiCastActionBarFrame.currentPage = page;
		MultiCastSummonSpellButton:SetID(page);
		_G["MultiCastActionPage"..prevPage]:Hide();
		_G["MultiCastActionPage"..page]:Show();
		MultiCastActionBarFrame_Update(MultiCastActionBarFrame);
	end
end


--
-- MultiCastSlotButton
--


function MultiCastSlotButton_OnEvent(self, event, ...)
	if ( event == "MODIFIER_STATE_CHANGED" ) then
		if ( IsModifiedClick("SHOWMULTICASTFLYOUT") and self:IsMouseOver() ) then
			MultiCastSlotButton_OnEnter(self);
		end
	end
end

function MultiCastSlotButton_OnEnter(self)
	if ( MultiCastFlyoutFrame.parent ~= self ) then
		MultiCastFlyoutFrameOpenButton_Show(MultiCastFlyoutFrameOpenButton, "slot", self);
	end
--[[
	self:RegisterEvent("MODIFIER_STATE_CHANGED");
	if ( IsModifiedClick("SHOWMULTICASTFLYOUT") ) then
		local slot = self:GetID();
		MultiCastFlyoutFrame_ToggleFlyout(MultiCastFlyoutFrame, "slot", self);
	end
--]]
end

function MultiCastSlotButton_OnLeave(self)
	GameTooltip:Hide();
	MultiCastFlyoutFrameOpenButton_Hide(MultiCastFlyoutFrameOpenButton);
end

function MultiCastSlotButton_Update(self, slot)
	self:SetID(slot);
	if ( slot == 0 ) then
		self:Hide();
	else
		-- fixup textures
		local tcoords = SLOT_OVERLAY_TCOORDS[slot];
		self.overlayTex:SetTexCoord(tcoords.left, tcoords.right, tcoords.top, tcoords.bottom);
		tcoords = SLOT_EMPTY_TCOORDS[slot];
		self.background:SetTexCoord(tcoords.left, tcoords.right, tcoords.top, tcoords.bottom);

		self:Show();
	end
end


--
-- MultiCastActionButton
--

function MultiCastActionButton_OnLoad(self)
	-- fixup textures
	local name = self:GetName();
	local normalTexture = _G[name.."NormalTexture"];
	normalTexture:SetWidth(50);
	normalTexture:SetHeight(50);

	-- setup action button stuff
	self.buttonType = "MULTICASTACTIONBUTTON";
	self.buttonIndex = self:GetID();
	ActionBarActionButtonDerivedMixin.ActionBarActionButtonDerivedMixin_OnLoad(self);
end

function MultiCastActionButton_OnEvent(self, event, ...)
	ActionBarActionButtonDerivedMixin.ActionBarActionButtonDerivedMixin_OnEvent(self, event, ...);
	if ( event == "MODIFIER_STATE_CHANGED" ) then
		if ( IsModifiedClick("SHOWMULTICASTFLYOUT") and self:IsMouseOver() ) then
			MultiCastActionButton_OnEnter(self);
		end
	end
end

function MultiCastActionButton_OnShow(self)
	if ( not self.slotButton ) then
		self:Hide();
	end
end

function MultiCastActionButton_OnEnter(self)
	MultiCastSlotButton_OnEnter(self.slotButton);
	self:SetTooltip();
end

function MultiCastActionButton_OnLeave(self)
	GameTooltip:Hide();
	MultiCastFlyoutFrameOpenButton_Hide(MultiCastFlyoutFrameOpenButton);
end

function MultiCastActionButton_OnPostClick(self, button, down)
	self:UpdateState();
	MultiCastFlyoutFrame_Hide(MultiCastFlyoutFrame, true);
end

function MultiCastActionButton_Update(self, id, index, slot)
	self:SetID(id);
	self.buttonIndex = index;
	if ( slot == 0 ) then
		self:Hide();
	else
		self:UpdateAction();
		ActionBarActionButtonDerivedMixin.Update(self);
		self:UpdateHotkeys(self.buttonType);

		-- fixup textures
		local tcoords;
		tcoords = SLOT_OVERLAY_TCOORDS[slot];
		self.overlayTex:SetTexCoord(tcoords.left, tcoords.right, tcoords.top, tcoords.bottom);
		self:Show();
	end
end

function MultiCastActionButtonDown(id)
	local button = _G["MultiCastActionButton"..id];
	if ( button:GetButtonState() == "NORMAL" ) then
		button:SetButtonState("PUSHED");
	end
	TryUseActionButton(button, true);
end

function MultiCastActionButtonUp(id)
	local button = _G["MultiCastActionButton"..id];
	if ( button:GetButtonState() == "PUSHED" ) then
		button:SetButtonState("NORMAL");
		TryUseActionButton(button, false);
	end
	MultiCastFlyoutFrame_Hide(MultiCastFlyoutFrame, true);
end


--
-- MultiCastFlyoutButton
--

function MultiCastFlyoutButton_OnLoad(self)
	self:RegisterForClicks("AnyUp");

	local parent = self:GetParent();
	tinsert(parent.buttons, self);
end

function MultiCastFlyoutButton_OnClick(self)
	local parent = self:GetParent();

	if ( self.spellId ) then
		local type = parent.type;
		if ( type == "page" ) then
			ChangeMultiCastActionPage(self.page);
		elseif ( type == "slot" ) then
			SetMultiCastSpell(parent.parent.actionButton:CalculateAction(), self.spellId);
		end
	end

	MultiCastFlyoutFrame_Hide(parent, true);
end

function MultiCastFlyoutButton_OnEnter(self)
	MultiCastFlyoutButton_SetTooltip(self);
end

function MultiCastFlyoutButton_OnLeave(self)
	GameTooltip:Hide();
end

function MultiCastFlyoutButton_SetTooltip(self)
	if ( self.spellId ) then
		if ( self.spellId == 0 ) then
			if ( GetCVarBool("UberTooltips") ) then
				GameTooltip_SetDefaultAnchor(GameTooltip, self);
			else
				GameTooltip:SetOwner(self, "ANCHOR_RIGHT");
			end
			GameTooltip:SetText(MULTI_CAST_TOOLTIP_NO_TOTEM, HIGHLIGHT_FONT_COLOR.r, HIGHLIGHT_FONT_COLOR.g, HIGHLIGHT_FONT_COLOR.b);
			self.UpdateTooltip = nil;
		else
			if ( GetCVarBool("UberTooltips") ) then
				GameTooltip_SetDefaultAnchor(GameTooltip, self);
			else
				GameTooltip:SetOwner(self, "ANCHOR_RIGHT");
			end
			if ( GameTooltip:SetSpellByID(self.spellId, false, true) ) then
				self.UpdateTooltip = MultiCastFlyoutButton_SetTooltip;
			else
				self.UpdateTooltip = nil;
			end
		end
	end
end


--
-- MultiCastFlyoutFrame
--

function MultiCastFlyoutFrame_OnShow(self)
	MultiCastFlyoutFrameOpenButton_Hide(MultiCastFlyoutFrameOpenButton, true);
end

function MultiCastFlyoutFrame_OnHide(self)
	self.type = nil;
	self.parent = nil;
end

function MultiCastFlyoutFrame_OnEnter(self)
end

function MultiCastFlyoutFrame_OnLeave(self)
--[[
	if ( not self:IsMouseOver() ) then
		self.closeCountdownSec = MULTI_CAST_FLYOUT_CLOSE_SEC;
	end
--]]
end

function MultiCastFlyoutFrame_OnUpdate(self, elapsed)
--[[
	local closeCountdownSec = self.closeCountdownSec;
	if ( closeCountdownSec ) then
		closeCountdownSec = closeCountdownSec - elapsed;
		if ( closeCountdownSec <= 0 ) then
			self.closeCountdownSec = nil;
			self:Hide();
		else
			self.closeCountdownSec = closeCountdownSec;
		end
	end
--]]
end

function MultiCastFlyoutFrame_Hide(self, forceHide)
	if ( forceHide or not self:IsMouseOver() ) then
		if ( self.parent ) then
			if ( self.parent:IsMouseOver() ) then
				MultiCastFlyoutFrameOpenButton_Show(MultiCastFlyoutFrameOpenButton, self.type, self.parent);
			else
				self.parent:UnregisterEvent("MODIFIER_STATE_CHANGED");
			end
		end
		self:Hide();
	end
end

function MultiCastFlyoutFrame_ToggleFlyout(self, type, parent)
	if ( self:IsShown() and self.parent == parent ) then
		MultiCastFlyoutFrame_Hide(self, true);
	else
		local toptcoords;
		local midtcoords;
		local closetcoords;
		if ( type == "slot" ) then
			local actionId = parent:GetID();
			local slot = _ComputeMultiCastSlot(actionId);
			if ( MultiCastFlyoutFrame_LoadSlotSpells(self, slot, GetMultiCastTotemSpells(actionId)) ) then
				toptcoords = FLYOUT_TOP_TCOORDS[slot];
				midtcoords = FLYOUT_MIDDLE_TCOORDS[slot];
				closetcoords = FLYOUT_DOWN_BUTTON_TCOORDS[slot];
			end
		elseif ( type == "page" ) then
			if ( MultiCastFlyoutFrame_LoadPageSpells(self) ) then
				toptcoords = FLYOUT_TOP_TCOORDS["summon"];
				midtcoords = FLYOUT_MIDDLE_TCOORDS["summon"];
				closetcoords = FLYOUT_DOWN_BUTTON_TCOORDS["summon"];
			end
		end
		if ( toptcoords ) then
			self.type = type;
			self.parent = parent;
			self:SetPoint("BOTTOM", parent, "TOP", 0, 0);
			self:Show();

			self.top:SetTexCoord(toptcoords.left, toptcoords.right, toptcoords.top, toptcoords.bottom);
			self.middle:SetTexCoord(midtcoords.left, midtcoords.right, midtcoords.top, midtcoords.bottom);
			MultiCastFlyoutFrameCloseButton.normalTexture:SetTexCoord(closetcoords.left, closetcoords.right, closetcoords.top, closetcoords.bottom);
		end
	end
end

function MultiCastFlyoutFrame_LoadPageSpells(self)
	local numKnownSpells = 0;
	for i, spellId in ipairs(TOTEM_MULTI_CAST_SUMMON_SPELLS) do
		if ( knownMultiCastSummonSpells[i] ) then
			numKnownSpells = numKnownSpells + 1;
		end
	end
	if ( numKnownSpells == 0 ) then
		return false;
	end

	self.buttons = self.buttons or {};
	local buttons = self.buttons;
	local numButtons = #buttons;

	local totalHeight = 0;
	local button;
	local name, _, icon;
	local buttonIndex = 1;
	for i, spellId in ipairs(TOTEM_MULTI_CAST_SUMMON_SPELLS) do
		if ( knownMultiCastSummonSpells[i] ) then
			-- create the button
			if ( buttonIndex <= numButtons ) then
				button = buttons[buttonIndex];
				if ( buttonIndex == 1 ) then
					totalHeight = totalHeight + MULTI_CAST_FLYOUT_BUTTON_INITIAL_OFFSET;
				else
					totalHeight = totalHeight + MULTI_CAST_FLYOUT_BUTTON_OFFSET;
				end
			else
				button = CreateFrame("Button", "MultiCastFlyoutButton"..buttonIndex, self, "MultiCastFlyoutButtonTemplate");
				if ( buttonIndex == 1 ) then
					-- this is the first button, anchor it to the frame
					button:SetPoint("BOTTOM", self, "BOTTOM", 0, MULTI_CAST_FLYOUT_BUTTON_INITIAL_OFFSET);
					totalHeight = totalHeight + MULTI_CAST_FLYOUT_BUTTON_INITIAL_OFFSET;
				else
					-- this is not the first button, anchor it to the previous button
					button:SetPoint("BOTTOM", buttons[buttonIndex - 1], "TOP", 0, MULTI_CAST_FLYOUT_BUTTON_OFFSET);
					totalHeight = totalHeight + MULTI_CAST_FLYOUT_BUTTON_OFFSET;
				end
			end
			totalHeight = totalHeight + button:GetHeight();

			-- setup the button
			button.page = i;
			button.spellId = spellId;
			icon = C_Spell.GetSpellTexture(spellId);
			button.icon:SetTexture(icon);
			button.icon:SetTexCoord(0.0, 1.0, 0.0, 1.0);

			button:Show();

			buttonIndex = buttonIndex + 1;
		end
	end
	-- hide unused buttons
	for i = buttonIndex, numButtons do
		buttons[i]:Hide();
	end

	self:SetHeight(totalHeight + 2 + MultiCastFlyoutFrameCloseButton:GetHeight());

	return true;
end

function MultiCastFlyoutFrame_LoadSlotSpells(self, slot, ...)
	local numSpells = select("#", ...);
	if ( numSpells == 0 ) then
		return false;
	end
	-- add one to numSpells to represent the "none" slot
	numSpells = numSpells + 1;

	self.buttons = self.buttons or {};
	local buttons = self.buttons;
	local numButtons = #buttons;

	local totalHeight = 0;
	local button;
	local spellId;
	local name, _, icon;
	local tcoords;
	local tcoordLeft, tcoordRight, tcoordTop, tcoordBottom;
	for i = 1, numSpells do
		-- create the button
		if ( i <= numButtons ) then
			button = buttons[i];
			if ( i == 1 ) then
				totalHeight = totalHeight + MULTI_CAST_FLYOUT_BUTTON_INITIAL_OFFSET;
			else
				totalHeight = totalHeight + MULTI_CAST_FLYOUT_BUTTON_OFFSET;
			end
		else
			button = CreateFrame("Button", "MultiCastFlyoutButton"..i, self, "MultiCastFlyoutButtonTemplate");
			button:ClearAllPoints();
			if ( i == 1 ) then
				-- this is the first button, anchor it to the frame
				button:SetPoint("BOTTOM", self, "BOTTOM", 0, MULTI_CAST_FLYOUT_BUTTON_INITIAL_OFFSET);
				totalHeight = totalHeight + MULTI_CAST_FLYOUT_BUTTON_INITIAL_OFFSET;
			else
				-- this is not the first button, anchor it to the previous button
				button:SetPoint("BOTTOM", buttons[i - 1], "TOP", 0, MULTI_CAST_FLYOUT_BUTTON_OFFSET);
				totalHeight = totalHeight + MULTI_CAST_FLYOUT_BUTTON_OFFSET;
			end
		end
		totalHeight = totalHeight + button:GetHeight();

		-- setup the button
		if ( i == 1 ) then
			-- the first button clears your slot
			spellId = 0;
			icon = "Interface\\Buttons\\UI-TotemBar";
			tcoords = SLOT_EMPTY_TCOORDS[slot];
			tcoordLeft, tcoordRight, tcoordTop, tcoordBottom = tcoords.left, tcoords.right, tcoords.top, tcoords.bottom;
		else
			spellId = select(i - 1, ...);
			icon = C_Spell.GetSpellTexture(spellId);
			tcoordLeft, tcoordRight, tcoordTop, tcoordBottom = 0.0, 1.0, 0.0, 1.0;
		end
		button.spellId = spellId;
		button.icon:SetTexture(icon);
		button.icon:SetTexCoord(tcoordLeft, tcoordRight, tcoordTop, tcoordBottom);

		button:Show();
	end
	-- hide unused buttons
	for i = numSpells + 1, numButtons do
		buttons[i]:Hide();
	end

	self:SetHeight(totalHeight + 2 + MultiCastFlyoutFrameCloseButton:GetHeight());

	return true;
end


--
-- MultiCastFlyoutFrameCloseButton
--

function MultiCastFlyoutFrameCloseButton_OnClick(self)
	self:GetParent():Hide();
end


--
-- MultiCastFlyoutFrameOpenButton
--

function MultiCastFlyoutFrameOpenButton_OnClick(self)
	MultiCastFlyoutFrame_ToggleFlyout(MultiCastFlyoutFrame, self.type, self.parent);
end

function MultiCastFlyoutFrameOpenButton_OnLeave(self)
	self:Hide();
end

function MultiCastFlyoutFrameOpenButton_Show(self, type, parent)
	local tcoords;
	if ( type == "page" ) then
		tcoords = FLYOUT_UP_BUTTON_TCOORDS["summon"];
	elseif ( type == "slot" ) then
		local slot = _ComputeMultiCastSlot(parent:GetID());
		tcoords = FLYOUT_UP_BUTTON_TCOORDS[slot];
	end
	if ( tcoords ) then
		self.normalTexture:SetTexCoord(tcoords.left, tcoords.right, tcoords.top, tcoords.bottom);
		self.type = type;
		self.parent = parent;
		self:SetParent(parent);
		self:SetPoint("BOTTOM", parent, "TOP", 0, 0);
		self:Show();
	end
end

function MultiCastFlyoutFrameOpenButton_Hide(self, force)
	if ( force or (not self:IsMouseOver() and not self.parent:IsMouseOver()) ) then
		self.type = nil;
		self:Hide();
	end
end


--
-- MultiCastSpellButton
--

function MultiCastSpellButton_OnLoad(self)
	self:RegisterForClicks("AnyUp");

	local name = self:GetName();
	local normalTexture = _G[name.."NormalTexture"];
	normalTexture:SetWidth(50);
	normalTexture:SetHeight(50);
end

function MultiCastSpellButton_OnEvent(self, event, ...)
	if ( event == "UPDATE_BINDINGS" or event == "GAME_PAD_ACTIVE_CHANGED" ) then
		self:UpdateHotkeys(self.buttonType);
	elseif ( event == "ACTIONBAR_UPDATE_COOLDOWN" ) then
		MultiCastSpellButton_UpdateCooldown(self);
	elseif ( event == "ACTIONBAR_UPDATE_STATE" ) then
		MultiCastSpellButton_UpdateState(self);
	end
end

function MultiCastSpellButton_OnEnter(self)
	MultiCastSpellButton_SetTooltip(self);
end

function MultiCastSpellButton_OnLeave(self)
	GameTooltip:Hide();
end

function MultiCastSpellButton_SetTooltip(self)
	if ( GetCVarBool("UberTooltips") ) then
		GameTooltip_SetDefaultAnchor(GameTooltip, self);
	else
		GameTooltip:SetOwner(self, "ANCHOR_RIGHT");
	end
	if ( GameTooltip:SetSpellByID(self.spellId, false, true) ) then
		self.UpdateTooltip = MultiCastSpellButton_SetTooltip;
	else
		self.UpdateTooltip = nil;
	end
end

function MultiCastSpellButton_UpdateCooldown(self)
	local cooldown = _G[self:GetName().."Cooldown"];
	local cooldownInfo = C_Spell.GetSpellCooldown(self.spellId);
	if ( cooldownInfo ) then
		CooldownFrame_Set(cooldown, cooldownInfo.startTime, cooldownInfo.duration, cooldownInfo.isEnabled);
	else
		CooldownFrame_Clear(cooldown);
	end
end

function MultiCastSpellButton_UpdateState(self)
	if ( C_Spell.IsCurrentSpell(self.spellId) ) then
		self:SetChecked(true);
	else
		self:SetChecked(false);
	end
end


--
-- MultiCastSummonSpellButton
--

function MultiCastSummonSpellButton_OnLoad(self)
	self.buttonType = "MULTICASTSUMMONBUTTON";
	MultiCastSpellButton_OnLoad(self);
end

function MultiCastSummonSpellButton_OnEvent(self, event, ...)
	MultiCastSpellButton_OnEvent(self, event, ...);
--[[
	if ( event == "MODIFIER_STATE_CHANGED" ) then
		if ( IsModifiedClick("SHOWMULTICASTFLYOUT") and self:IsMouseOver() ) then
			MultiCastSummonSpellButton_OnEnter(self);
		end
	end
--]]
end

function MultiCastSummonSpellButton_OnClick(self)
	MultiCastSummonSpellButtonUp(self:GetID());
end

function MultiCastSummonSpellButton_OnEnter(self)
	MultiCastSpellButton_OnEnter(self);

	if ( MultiCastFlyoutFrame.parent ~= self ) then
		MultiCastFlyoutFrameOpenButton_Show(MultiCastFlyoutFrameOpenButton, "page", self);
	end
--[[
	self:RegisterEvent("MODIFIER_STATE_CHANGED");
	if ( IsModifiedClick("SHOWMULTICASTFLYOUT") ) then
		MultiCastFlyoutFrame_ToggleFlyout(MultiCastFlyoutFrame, "page", self);
	end
--]]
end

function MultiCastSummonSpellButton_OnLeave(self)
	MultiCastSpellButton_OnLeave(self);
	MultiCastFlyoutFrameOpenButton_Hide(MultiCastFlyoutFrameOpenButton);
end

function MultiCastSummonSpellButton_Update(self)
	-- first update which multi-cast spells we actually know
	for index, spellId in ipairs(TOTEM_MULTI_CAST_SUMMON_SPELLS) do
		local spellBank = Enum.SpellBookSpellBank.Player;
		local includeOverrides = false;
		knownMultiCastSummonSpells[index] = (C_SpellBook.IsSpellInSpellBook(spellId, spellBank, includeOverrides) and spellId) or nil;
	end

	-- update the spell button
	local spellId = knownMultiCastSummonSpells[self:GetID()];
	self.spellId = spellId;
	if ( HasMultiCastActionBar() and spellId ) then
		local icon = C_Spell.GetSpellTexture(spellId);
		local buttonName = self:GetName();
		_G[buttonName.."Icon"]:SetTexture(icon);

		if ( not self.eventsRegistered ) then
			self:RegisterEvent("UPDATE_BINDINGS");
			self:RegisterEvent("GAME_PAD_ACTIVE_CHANGED");
			self:RegisterEvent("ACTIONBAR_UPDATE_STATE");
			self:RegisterEvent("ACTIONBAR_UPDATE_COOLDOWN");
			self.eventsRegistered = true;
		end
		self:UpdateHotkeys(self.buttonType);
		MultiCastSpellButton_UpdateCooldown(self);

		if ( GameTooltip:GetOwner() == self ) then
			MultiCastSpellButton_SetTooltip(self);
		end

		-- reanchor the first slot button take make room for this button
		local width = self:GetWidth();
		local xOffset = width + 8 + 3;
		local page;
		for i = 1, NUM_MULTI_CAST_PAGES do
			page = _G["MultiCastActionPage"..i];
			page:SetPoint("BOTTOMLEFT", page:GetParent(), "BOTTOMLEFT", xOffset, 3);
		end
		MultiCastSlotButton1:SetPoint("BOTTOMLEFT", self:GetParent(), "BOTTOMLEFT", xOffset, 3);

		self:Show();
	else
		if ( self.eventsRegistered ) then
			self:UnregisterEvent("UPDATE_BINDINGS");
			self:UnregisterEvent("GAME_PAD_ACTIVE_CHANGED");
			self:UnregisterEvent("ACTIONBAR_UPDATE_STATE");
			self:UnregisterEvent("ACTIONBAR_UPDATE_COOLDOWN");
			self.eventsRegistered = false;
		end

		-- reanchor the first slot button take the place of this button
		local xOffset = 3;
		local page;
		for i = 1, NUM_MULTI_CAST_PAGES do
			page = _G["MultiCastActionPage"..i];
			page:SetPoint("BOTTOMLEFT", page:GetParent(), "BOTTOMLEFT", xOffset, 3);
		end
		MultiCastSlotButton1:SetPoint("BOTTOMLEFT", self:GetParent(), "BOTTOMLEFT", xOffset, 3);

		self:Hide();
	end
end

function MultiCastSummonSpellButtonUp(id)
	CastSpellByID(TOTEM_MULTI_CAST_SUMMON_SPELLS[id]);
end


--
-- MultiCastRecallSpellButton
--

function MultiCastRecallSpellButton_OnLoad(self)
	self.buttonType = "MULTICASTRECALLBUTTON";
	MultiCastSpellButton_OnLoad(self);
end

function MultiCastRecallSpellButton_OnClick(self)
	MultiCastRecallSpellButtonUp(self:GetID());
end

function MultiCastRecallSpellButton_Update(self)
	-- first update which multi-cast spells we actually know
	for index, spellId in ipairs(TOTEM_MULTI_CAST_RECALL_SPELLS) do
		local spellBank = Enum.SpellBookSpellBank.Player;
		local includeOverrides = false;
		knownMultiCastRecallSpells[index] = (C_SpellBook.IsSpellInSpellBook(spellId, spellBank, includeOverrides) and spellId) or nil;
	end

	-- update the spell button
	local spellId = knownMultiCastRecallSpells[self:GetID()];
	self.spellId = spellId;
	if ( HasMultiCastActionBar() and spellId ) then
		local icon = C_Spell.GetSpellTexture(spellId);
		local buttonName = self:GetName();
		_G[buttonName.."Icon"]:SetTexture(icon);

		if ( not self.eventsRegistered ) then
			self:RegisterEvent("UPDATE_BINDINGS");
			self:RegisterEvent("GAME_PAD_ACTIVE_CHANGED");
			self:RegisterEvent("ACTIONBAR_UPDATE_STATE");
			self:RegisterEvent("ACTIONBAR_UPDATE_COOLDOWN");
			self.eventsRegistered = true;
		end
		self:UpdateHotkeys(self.buttonType);
		MultiCastSpellButton_UpdateCooldown(self);

		if ( GameTooltip:GetOwner() == self ) then
			MultiCastSpellButton_SetTooltip(self);
		end

		-- anchor to the last shown slot
		local activeSlots = MultiCastActionBarFrame.numActiveSlots;
		if ( activeSlots > 0 ) then
			self:SetPoint("LEFT", _G["MultiCastSlotButton"..activeSlots], "RIGHT", 8, 0);
		end

		self:Show();
	else
		if ( self.eventsRegistered ) then
			self:UnregisterEvent("UPDATE_BINDINGS");
			self:UnregisterEvent("GAME_PAD_ACTIVE_CHANGED");
			self:UnregisterEvent("ACTIONBAR_UPDATE_STATE");
			self:UnregisterEvent("ACTIONBAR_UPDATE_COOLDOWN");
			self.eventsRegistered = false;
		end

		self:Hide();
	end
end

function MultiCastRecallSpellButtonUp(id)
	CastSpellByID(TOTEM_MULTI_CAST_RECALL_SPELLS[id]);
end
