------------------------------------------------
--                  CT_Core                   --
--                                            --
-- Core addon for doing basic and popular     --
-- things in an intuitive way.                --
-- Please do not modify or otherwise          --
-- redistribute this without the consent of   --
-- the CTMod Team. Thank you.                 --
------------------------------------------------

local _G = getfenv(0);
local module = _G.CT_Core;
local NUM_CHAT_WINDOWS = NUM_CHAT_WINDOWS;

--------------------------------------------
-- Quest Levels

local displayLevels = false;

local oldGetQuestLogTitle = GetQuestLogTitle;
local questLogPrefixes = {
	[GROUP] = "+",
	[RAID] = "R",
	[PVP] = "P",
	[LFG_TYPE_DUNGEON] = "D",
};

function GetQuestLogTitle(...)
	local questLogTitleText, level, questTag, unknown, isHeader, isCollapsed, isComplete, isDaily = oldGetQuestLogTitle(...);
	
	if ( displayLevels and questLogTitleText and level and level > 0 and not isHeader ) then
		local prefix = questLogPrefixes[questTag or ""] or "";
		questLogTitleText = string.format("[%d%s] %s", level, prefix, questLogTitleText);
	end
	return questLogTitleText, level, questTag, unknown, isHeader, isCollapsed, isComplete, isDaily;
end

local function toggleDisplayLevels(enable)
	displayLevels = enable;
	pcall(QuestLog_Update);
end

--------------------------------------------
-- Hail Mod

local function hail()
	local targetName = UnitName("target");
	if ( targetName ) then
		SendChatMessage("Hail, " .. targetName .. (((UnitIsDead("target") or UnitIsCorpse("target")) and "'s Corpse") or ""));
	else
		SendChatMessage("Hail");
	end
end

module.hail = hail;
module:setSlashCmd(hail, "/hail");

--------------------------------------------
-- Block trades when bank is open

local blockTrades;

do
	local blockSave;

	local function resetBlock()
		if (blockSave) then
			SetCVar("blockTrades", blockSave);
			blockSave = nil;
		end
	end

	function module:blockTrades(block)
		if ( block ) then
			blockSave = GetCVar("blockTrades");
			SetCVar("blockTrades", 1);
		else
			resetBlock();
		end
	end

	module:regEvent("PLAYER_LEAVING_WORLD", resetBlock);
	module:regEvent("BANKFRAME_CLOSED", resetBlock);
	module:regEvent("GUILDBANKFRAME_CLOSED", resetBlock);

	module:regEvent("BANKFRAME_OPENED", function()
		module:blockTrades(blockTrades);
	end);
	module:regEvent("GUILDBANKFRAME_OPENED", function()
		module:blockTrades(blockTrades);
	end);
end

local function toggleBlockTrades(enable)
	blockTrades = enable;

	if ( (BankFrame and BankFrame:IsVisible()) or (GuildBankFrame and GuildBankFrame:IsVisible()) ) then
		module:blockTrades(blockTrades);
	end
end

--------------------------------------------
-- Common code for Quest Tracker, Achievement Tracker, Tooltip Reanchoring

	local tooltipText = "Left-click to drag\nRight-click to rotate.";
	local tooltipsTooltipText = "|c00FFFFFFTooltips|r\n" .. tooltipText;
	local questTooltipText = "|c00FFFFFFQuests|r\n" .. tooltipText;
	local achievementTooltipText = "|c00FFFFFFAchievements|r\n" .. tooltipText;
	local combinedTooltipText = "|c00FFFFFFQuests & achievements|r\n" .. tooltipText;
	local function getPositions(anchor, anchorMode)
		if ( anchorMode == 2 ) then
			return "ANCHOR_CURSOR";
		else
			if ( anchor == 1 ) then
				return "ANCHOR_NONE", "TOPLEFT", "BOTTOMLEFT", "Top Left";
			elseif ( anchor == 2 ) then
				return "ANCHOR_NONE", "TOPRIGHT", "BOTTOMRIGHT", "Top Right";
			elseif ( anchor == 3 ) then
				return "ANCHOR_NONE", "BOTTOMRIGHT", "TOPRIGHT", "Bottom Right";
			elseif ( anchor == 4 ) then
				return "ANCHOR_NONE", "BOTTOMLEFT", "TOPLEFT", "Bottom Left";
			end
		end
	end
	local function onMouseDownFunc(self, button)
		if ( button == "LeftButton" ) then
			module:moveMovable(self.movable);
		end
	end
	local function anchorFrameSkeleton()
		-- Updates the text

		return "button#st:HIGH#tl:mid:350:-200#s:100:30", {
			"backdrop#tooltip#0:0:0:0.75",
			"font#v:GameFontNormal#i:text",
			["onleave"] = module.hideTooltip,
			["onmousedown"] = onMouseDownFunc
		};
	end

-- End common code


local combinedAnchor;
local combinedTracker_Reanchor;

--------------------------------------------
-- Quest Tracker

local questAnchorMode = 1;
local questAnchorEnabled;
local questAnchorFrame;
local questAnchorDisplay;
local questWatchFrame = QuestWatchFrame;

local function questTracker_UpdateAnchorVisibility()
	if ( questAnchorFrame ) then
		if ( questAnchorEnabled and questAnchorDisplay ) then
			questAnchorFrame:Show();
		else
			questAnchorFrame:Hide();
		end
	end
end

local function questTracker_UpdateText()
	if ( questAnchorFrame ) then
		local _, text;
		_, _, _, text = getPositions(questAnchorMode);
		questAnchorFrame.text:SetText(text);
	end
end

local function questTracker_Reanchor()
	combinedTracker_Reanchor();
end

function CT_Core_QuestTracker_ResetAnchor()
	if (questAnchorFrame) then
		questAnchorFrame:ClearAllPoints();
		questAnchorFrame:SetPoint("CENTER", "UIParent", "CENTER", 0, 0);
		module:stopMovable(questAnchorFrame.movable);
	end
end

local function questTracker_UpdateAnchorMode(mode)
	if ( mode ) then
		questAnchorMode = mode;
		questTracker_Reanchor();
		questTracker_UpdateText();
	end
end

local function questTracker_CreateAnchorFrame()
	local movable = "TRACKERANCHOR";	
	questAnchorFrame = module:getFrame(anchorFrameSkeleton);
	questTracker_UpdateText(questAnchorFrame);
	questTracker_UpdateAnchorVisibility();
	
	module:registerMovable(movable, questAnchorFrame);
	questAnchorFrame.movable = movable;
	questAnchorFrame:SetScript("OnEnter", function(self)
		if (not combinedAnchor) then
			module:displayTooltip(self, questTooltipText);
		else
			module:displayTooltip(self, combinedTooltipText);
		end
	end);
	questAnchorFrame:SetScript("OnMouseUp", function(self, button)
		if ( button == "LeftButton" ) then
			module:stopMovable(self.movable);
		elseif ( button == "RightButton" ) then
			-- Update anchor & text
			questAnchorMode = questAnchorMode + 1;
			if ( questAnchorMode > 4 ) then
				questAnchorMode = 1;
			end
			module:setOption("questTrackerMode", questAnchorMode, true);
			questTracker_UpdateAnchorMode(questAnchorMode);

			-- Display tooltip & play sound
			self:GetScript("OnEnter")(self);
			PlaySound("UChatScrollButton");
		end
	end);
end

local function questTracker_UpdateHook()
	if ( questAnchorEnabled and not questAnchorFrame ) then
		questTracker_CreateAnchorFrame();
	end
	questTracker_UpdateAnchorVisibility();
	questTracker_Reanchor();
end

local function questTracker_ToggleAnchor(enable)
	questAnchorEnabled = enable;
	questTracker_UpdateHook();
end

local function questTracker_ToggleAnchorVisibility(show)
	questAnchorDisplay = show;
	questTracker_UpdateAnchorVisibility();
end

--------------------------------------------
-- Achievement Tracker

local achievementAnchorMode = 1;
local achievementAnchorEnabled;
local achievementAnchorFrame;
local achievementAnchorDisplay;
local achievementWatchFrame = AchievementWatchFrame;

local function achievementTracker_UpdateAnchorVisibility()
	if ( achievementAnchorFrame ) then
		if ( achievementAnchorEnabled and achievementAnchorDisplay ) then
			achievementAnchorFrame:Show();
		else
			achievementAnchorFrame:Hide();
		end
	end
end

local function achievementTracker_UpdateText()
	if ( achievementAnchorFrame ) then
		local _, text;
		_, _, _, text = getPositions(achievementAnchorMode);
		achievementAnchorFrame.text:SetText(text);
	end
end

local function achievementTracker_Reanchor()
	combinedTracker_Reanchor();
end

function CT_Core_AchievementTracker_ResetAnchor()
	if (achievementAnchorFrame) then
		achievementAnchorFrame:ClearAllPoints();
		achievementAnchorFrame:SetPoint("CENTER", "UIParent", "CENTER", 0, 0);
		module:stopMovable(achievementAnchorFrame.movable);
	end
end

local function achievementTracker_UpdateAnchorMode(mode)
	if ( mode ) then
		achievementAnchorMode = mode;
		achievementTracker_Reanchor();
		achievementTracker_UpdateText();
	end
end

local function achievementTracker_CreateAnchorFrame()
	local movable = "ACHIEVEMENTANCHOR";	
	achievementAnchorFrame = module:getFrame(anchorFrameSkeleton);
	achievementTracker_UpdateText(achievementAnchorFrame);
	achievementTracker_UpdateAnchorVisibility();
	
	module:registerMovable(movable, achievementAnchorFrame);
	achievementAnchorFrame.movable = movable;
	achievementAnchorFrame:SetScript("OnEnter", function(self)
		module:displayTooltip(self, achievementTooltipText);
	end);
	achievementAnchorFrame:SetScript("OnMouseUp", function(self, button)
		if ( button == "LeftButton" ) then
			module:stopMovable(self.movable);
		elseif ( button == "RightButton" ) then
			-- Update anchor & text
			achievementAnchorMode = achievementAnchorMode + 1;
			if ( achievementAnchorMode > 4 ) then
				achievementAnchorMode = 1;
			end
			module:setOption("achievementTrackerMode", achievementAnchorMode, true);
			achievementTracker_UpdateAnchorMode(achievementAnchorMode);

			-- Display tooltip & play sound
			self:GetScript("OnEnter")(self);
			PlaySound("UChatScrollButton");
		end
	end);
end

local function achievementTracker_UpdateHook()
	if ( achievementAnchorEnabled and not achievementAnchorFrame ) then
		achievementTracker_CreateAnchorFrame();
	end
	achievementTracker_UpdateAnchorVisibility();
	achievementTracker_Reanchor();
end

local function achievementTracker_ToggleAnchor(enable)
	achievementAnchorEnabled = enable;
	achievementTracker_UpdateHook();
end

local function achievementTracker_ToggleAnchorVisibility(show)
	achievementAnchorDisplay = show;
	achievementTracker_UpdateAnchorVisibility();
end

--------------------------------------------
-- Combined quest / achievement tracker functions.

local questWatchFrameWidth;

local function CT_Core_Other_QuestWatch_Update()
	if (questWatchFrame:IsShown()) then
		questWatchFrameWidth = questWatchFrame:GetWidth();
	end
end
hooksecurefunc("QuestWatch_Update", CT_Core_Other_QuestWatch_Update);

combinedTracker_Reanchor = function()
	if ( questAnchorEnabled ) then
		-- Quest & Achivement trackers are unlocked.

		questWatchFrame:ClearAllPoints();
		if ( not questAnchorFrame ) then
			questTracker_CreateAnchorFrame();
		end

		achievementWatchFrame:ClearAllPoints();
		if ( not achievementAnchorFrame ) then
			achievementTracker_CreateAnchorFrame();
		end

		if ( not combinedAnchor ) then
			-- The trackers are to be positioned separately.
			local y1adjust;

			-- Position the Quest Tracker.
			local _, pt, relpt = getPositions(questAnchorMode);
			if (questAnchorMode == 1 or questAnchorMode == 2) then
				-- Below the anchor
				y1adjust = 12;
			else
				y1adjust = 2;
			end
			questWatchFrame:SetPoint(pt, questAnchorFrame, relpt, 0, y1adjust);
			if (questWatchFrameWidth) then
				questWatchFrame:SetWidth(questWatchFrameWidth);
			end

			-- Position the Achievement Tracker.
			local _, pt, relpt = getPositions(achievementAnchorMode);
			if (achievementAnchorMode == 1 or achievementAnchorMode == 2) then
				-- Below the anchor
				y1adjust = 0;
			else
				y1adjust = 5;
			end
			if (achievementWatchFrame.desiredWidth) then
	 			achievementWatchFrame:SetWidth(achievementWatchFrame.desiredWidth);
			end
			achievementWatchFrame:SetPoint(pt, achievementAnchorFrame, relpt, 0, y1adjust);
		else
			-- The Quest and Achivement Watch Frames are to be moved as one.
			local frame1, frame2;
			local y1adjust;
			local _, pt, relpt = getPositions(questAnchorMode);
			if ( achievementWatchFrame:IsShown() ) then
				if ( achievementWatchFrame:GetWidth() > questWatchFrame:GetWidth() or not questWatchFrame:IsShown() ) then
					if (achievementWatchFrame.desiredWidth) then
			 			achievementWatchFrame:SetWidth(achievementWatchFrame.desiredWidth);
					end
					if ( not questWatchFrame:IsShown() ) then
						if (questAnchorMode == 1 or questAnchorMode == 2) then
							-- Below the anchor
							y1adjust = 0;
						else
							y1adjust = 5;
						end
						achievementWatchFrame:SetPoint(pt, questAnchorFrame, relpt, 0, y1adjust);
					else
						if (questAnchorMode == 1 or questAnchorMode == 2) then
							-- Below the anchor
							y1adjust = -achievementWatchFrame:GetHeight();
						else
							y1adjust = 0;
						end
						questWatchFrame:SetPoint(pt, questAnchorFrame, relpt, 0, y1adjust);
						achievementWatchFrame:SetPoint("BOTTOMLEFT", questWatchFrame, "TOPLEFT", 0, 0);
			 			questWatchFrame:SetWidth(achievementWatchFrame:GetWidth());
					end
				else
					if (questAnchorMode == 1 or questAnchorMode == 2) then
						-- Below the anchor
						y1adjust = -achievementWatchFrame:GetHeight();
					else
						y1adjust = 5;
					end
					questWatchFrame:SetPoint(pt, questAnchorFrame, relpt, 0, y1adjust);
					achievementWatchFrame:SetPoint("BOTTOMLEFT", questWatchFrame, "TOPLEFT", 0, 0);
					achievementWatchFrame:SetWidth(questWatchFrame:GetWidth());
				end
			else
				-- Only quest wach frame is shown
				if (questAnchorMode == 1 or questAnchorMode == 2) then
					-- Below the anchor
					y1adjust = 13;
				else
					y1adjust = 0;
				end
				questWatchFrame:SetPoint(pt, questAnchorFrame, relpt, 0, y1adjust);
			end
		end
	else
		-- Quest & Achievement trackers are not unlocked.

		-- Refer to the castingbar_ToggleStatus() function in this lua file for the reason why
		-- we want to execute this call to UIParent_ManageFramePositions() only when the uiScale
		-- is not nil.
		if (GetCVar("uiScale")) then
			-- Use Blizzard's function to make sure they are in the right positions.
			UIParent_ManageFramePositions();
		end
	end
end

local function combinedTracker_ToggleCombined(enable)
	-- Enable/disable use of a single anchor.
	combinedAnchor = enable;
	if ( not combinedAnchor ) then
		-- Set achievement anchor to the same enabled/disabled and hidden/shown states as the quest anchor.
		achievementTracker_ToggleAnchor(questAnchorEnabled);
		achievementTracker_ToggleAnchorVisibility(questAnchorDisplay);
	else
		-- Disable and hide the achievement anchor.
		achievementTracker_ToggleAnchor(false);
		achievementTracker_ToggleAnchorVisibility(false);
	end
	questTracker_UpdateHook();
	achievementTracker_UpdateHook();
end

local function combinedTracker_ToggleAnchor(enable)
	-- Lock/unlock the anchors
	if ( not combinedAnchor ) then
		-- Set both anchors to the same enabled/disabled state.
		questTracker_ToggleAnchor(enable);
		achievementTracker_ToggleAnchor(enable);
	else
		-- Enable/disable the quest anchor. Disable the achievement anchor.
		questTracker_ToggleAnchor(enable);
		achievementTracker_ToggleAnchor(false);
	end
end

local function combinedTracker_ToggleAnchorVisibility(show)
	-- Toggle display of the anchors
	if ( not combinedAnchor ) then
		-- Set both anchors to the same hidden/shown state.
		questTracker_ToggleAnchorVisibility(show);
		achievementTracker_ToggleAnchorVisibility(show);
	else
		-- Hide/show the quest anchor. Hide the achievement anchor.
		questTracker_ToggleAnchorVisibility(show);
		achievementTracker_ToggleAnchorVisibility(false);
	end
end


local function CT_Core_Other_UIParent_ManageFramePositions()
	if ( questAnchorEnabled ) then
		combinedTracker_Reanchor();
	end
end
hooksecurefunc("UIParent_ManageFramePositions", CT_Core_Other_UIParent_ManageFramePositions);

--------------------------------------------
-- Tooltip Reanchoring

local tooltipAnchor = 1;
local tooltipAnchorMode = 0;
local tooltipAnchorDisplay = true;
local tooltipAnchorFrame;

local function updateTooltipAnchorVisibility()
	if ( tooltipAnchorFrame ) then
		if ( tooltipAnchorMode == 3 and tooltipAnchorDisplay ) then
			tooltipAnchorFrame:Show();
		else
			tooltipAnchorFrame:Hide();
		end
	end
end

local function updateTooltipText(self)
	local _, text;
	_, _, _, text = getPositions(tooltipAnchor, tooltipAnchorMode);
	self.text:SetText(text);
end

local function createTooltipAnchorFrame()
	local movable = "TOOLTIPANCHOR";	
	tooltipAnchorFrame = module:getFrame(anchorFrameSkeleton);
	updateTooltipText(tooltipAnchorFrame);
	updateTooltipAnchorVisibility();
	
	module:registerMovable(movable, tooltipAnchorFrame);
	tooltipAnchorFrame.movable = movable;
	tooltipAnchorFrame:SetScript("OnEnter", function(self) module:displayTooltip(self, tooltipsTooltipText, true); end);
	tooltipAnchorFrame:SetScript("OnMouseUp", function(self, button)
		if ( button == "LeftButton" ) then
			module:stopMovable(self.movable);
		elseif ( button == "RightButton" ) then
			-- Update anchor & text
			tooltipAnchor = tooltipAnchor + 1;
			if ( tooltipAnchor > 4 ) then
				tooltipAnchor = 1;
			end
			module:setOption("tooltipAnchor", tooltipAnchor, true);
			updateTooltipText(self);

			-- Display tooltip & play sound
			self:GetScript("OnEnter")(self);
			PlaySound("UChatScrollButton");
		end
	end);
end

local function reanchorTooltip(tooltip, parent)
	local owner, pt, relPt = getPositions(tooltipAnchor, tooltipAnchorMode);
	tooltip:SetOwner(parent, owner);
	if ( pt ) then
		if ( not tooltipAnchorFrame ) then
			createTooltipAnchorFrame();
		end
		tooltip:ClearAllPoints();
		tooltip:SetPoint(pt, tooltipAnchorFrame, relPt);
	end
end

function CT_Core_ResetTooltipAnchor()
	if (tooltipAnchorFrame) then
		tooltipAnchorFrame:ClearAllPoints();
		tooltipAnchorFrame:SetPoint("CENTER", "UIParent", "CENTER", 0, 0);
		module:stopMovable(tooltipAnchorFrame.movable);
	end
end

local oldGameTooltip_SetDefaultAnchor = GameTooltip_SetDefaultAnchor;
function GameTooltip_SetDefaultAnchor(tooltip, parent, ...)
	if ( tooltip == GameTooltip and tooltipAnchorMode and tooltipAnchorMode > 1 ) then
		reanchorTooltip(tooltip, parent);
	else
		oldGameTooltip_SetDefaultAnchor(tooltip, parent, ...);
	end
end

local function setTooltipRelocationStyle(tooltipStyle)
	tooltipAnchorMode = tooltipStyle;
	updateTooltipAnchorVisibility();
end

local function toggleTooltipAnchorVisibility(show)
	tooltipAnchorDisplay = show;
	
	if ( not tooltipAnchorFrame and show ) then
		createTooltipAnchorFrame();
	else
		updateTooltipAnchorVisibility();
	end
end

local function setTooltipAnchor(anchor)
	tooltipAnchor = anchor or tooltipAnchor;
	
	if ( tooltipAnchorDisplay ) then
		if ( not tooltipAnchorFrame ) then
			createTooltipAnchorFrame();
		end
		updateTooltipText(tooltipAnchorFrame);
	end
end

--------------------------------------------
-- Tick Mod

local tickFrame;
local tickDisplayType = 1;

local tickFormatHealth_1 = "Health: %d";
local tickFormatHealth_2 = "HP/Tick: %d";
local tickFormatHealth_3 = "HP: %d";
local tickFormatMana_1 = "Mana: %d";
local tickFormatMana_2 = "MP/Tick: %d";
local tickFormatMana_3 = "MP: %d";

local tickFrameWidth;
local tickCounter = 0.05;
local lastTickHealth, lastTickMana;

local function fadeObject(self)
	local alpha = self.alpha;
	if ( alpha and alpha > 0.25 ) then
		alpha = alpha - 0.03;
		self.alpha = alpha;
		self:SetAlpha(alpha);
		return true;
	end
end

local function fadeTicks(self, elapsed)
	tickCounter = tickCounter - elapsed;
	if ( tickCounter < 0 ) then
		local fadedHealth = fadeObject(self.health);
		local fadedMana = fadeObject(self.mana);
		if ( not fadedHealth and not fadedMana ) then
			self:SetScript("OnUpdate", nil);
		end
		tickCounter = 0.05;
	end
end

local function updateTickDisplay(key, diff)
	local obj = tickFrame[key];
	obj:SetText(format(obj.strFormat, diff));
	obj:SetAlpha(1);
	obj.alpha = 1;
	
	if ( tickFrameWidth ) then
		tickFrame:SetWidth(tickFrameWidth);
	end
	
	counter = 0.05;
	tickFrame:SetScript("OnUpdate", fadeTicks);
end

local function tickFrameSkeleton()
	return "button#tl:mid:350:-200#s:90:40", {
		"backdrop#tooltip",
		"font#i:health#t:0:-8",
		"font#i:mana#b:0:8",
		["onload"] = function(self)
			self:RegisterEvent("UNIT_HEALTH");
			self:RegisterEvent("UNIT_MANA");
			self:SetBackdropColor(0, 0, 0, 0.75);
			module:registerMovable("TICKMOD", self, true);
		end,
		["onevent"] = function(self, event, unit)
			if ( unit == "player" ) then
				if ( event == "UNIT_HEALTH" ) then
					local health = UnitHealth("player");
					local diff = health-lastTickHealth;
					if ( diff > 0 ) then
						updateTickDisplay("health", diff);
					end
					lastTickHealth = health;
				else
					local mana = UnitMana("player");
					local diff = mana-lastTickMana;
					if ( diff > 0 ) then
						updateTickDisplay("mana", diff);
					end
					lastTickMana = mana;
				end
			end
		end,
		["onenter"] = function(self)
			module:displayPredefinedTooltip(self, "DRAG");
		end,
		["onleave"] = module.hideTooltip,
		["onmousedown"] = function(self, button)
			if ( button == "LeftButton" ) then
				module:moveMovable("TICKMOD");
			end
		end,
		["onmouseup"] = function(self, button)
			if ( button == "LeftButton" ) then
				module:stopMovable("TICKMOD");
			elseif ( button == "RightButton" ) then
				module:resetMovable("TICKMOD");
				self:ClearAllPoints();
				self:SetPoint("CENTER", UIParent);
			end
		end
	};
end

local function updateTickFrameOptions()
	if ( not tickFrame ) then
		return;
	end
	
	-- Height
	local _, class = UnitClass("player");
	if ( UnitPowerType("player") == 0 or class == "DRUID" ) then
		tickFrame:SetHeight(40);
	else
		tickFrame:SetHeight(30);
	end
	
	-- Width & Format
	if ( not tickDisplayType or tickDisplayType == 1 ) then
		tickFrameWidth = 90;
		tickFrame.health.strFormat = tickFormatHealth_1;
		tickFrame.mana.strFormat = tickFormatMana_1;
	elseif ( tickDisplayType == 2 ) then
		tickFrameWidth = 100;
		tickFrame.health.strFormat = tickFormatHealth_2;
		tickFrame.mana.strFormat = tickFormatMana_2;
	elseif ( tickDisplayType == 3 ) then
		tickFrameWidth = 80;
		tickFrame.health.strFormat = tickFormatHealth_3;
		tickFrame.mana.strFormat = tickFormatMana_3;
	end
end

local function toggleTick(enable)
	if ( enable ) then
		if ( not tickFrame ) then
			tickFrame = module:getFrame(tickFrameSkeleton);
		end
		tickFrame:Show();
		updateTickFrameOptions();
		lastTickHealth, lastTickMana = UnitHealth("player"), UnitMana("player");

	elseif ( tickFrame ) then
		tickFrame:Hide();
	end
end

local function setTickDisplayType(mode)
	tickDisplayType = mode;
	updateTickFrameOptions();
end

--------------------------------------------
-- Casting Bar Timer

local displayTimers;

-- Create our frames
	local countDownText = CastingBarFrame:CreateFontString(nil, "ARTWORK", "GameFontHighlight");
	countDownText:SetPoint("TOPRIGHT", 2, 5);
	countDownText:SetPoint("BOTTOMLEFT", CastingBarFrame, "BOTTOMRIGHT", -56, 2);
	CastingBarFrame.countDownText = countDownText;
	CastingBarFrame.ctElapsed = 0;
	
	local countDownTargetText = TargetFrameSpellBar:CreateFontString(nil, "ARTWORK", "GameFontHighlight");
	countDownTargetText:SetPoint("TOPRIGHT", 0, 5);
	countDownTargetText:SetPoint("BOTTOMLEFT", TargetFrameSpellBar, "BOTTOMRIGHT", -60, 0);
	TargetFrameSpellBar.countDownText = countDownTargetText;
	TargetFrameSpellBar.ctElapsed = 0;

-- Preparation for repositioning the normal texts
	local castingBarText = CastingBarFrameText;
	local castingBarTargetText = TargetFrameSpellBarText;

-- Hook the CastingBarFrame's OnUpdate
local oldCastingBarFrame_OnUpdate = CastingBarFrame_OnUpdate;
function CastingBarFrame_OnUpdate(self, secondsElapsed)
	oldCastingBarFrame_OnUpdate(self, secondsElapsed);
	
	if (not self.ctElapsed) then
		return;
	end

	local elapsed = ( self.ctElapsed or 0 ) - secondsElapsed;
	if ( elapsed < 0 ) then
		if ( displayTimers ) then
			-- We need to update
			if ( self.casting ) then
				self.countDownText:SetText(format("(%.1fs)", max(self.maxValue - self.value,0)));
			elseif ( self.channeling ) then
				self.countDownText:SetText(format("(%.1fs)", max(self.value,0)));
			else
				self.countDownText:SetText("");
			end
		end
		self.ctElapsed = 0.1;
	else
		self.ctElapsed = elapsed;
	end
end

local function toggleCastingTimers(enable)
	displayTimers = enable;
	castingBarText:ClearAllPoints();
	castingBarTargetText:ClearAllPoints();
	if ( enable ) then
		countDownText:Show();
		countDownTargetText:Show();
		
		castingBarText:SetPoint("TOPLEFT", 0, 5);
		castingBarText:SetPoint("BOTTOMRIGHT", countDownText, "BOTTOMLEFT", 10, 0);
		
		castingBarTargetText:SetPoint("TOPLEFT", 2, 5);
		castingBarTargetText:SetPoint("BOTTOMRIGHT", countDownTargetText, "BOTTOMLEFT", 10, 0);
	else
		countDownText:Hide();
		countDownTargetText:Hide();
		
		castingBarText:SetWidth(0);
		castingBarText:SetPoint("TOP", 0, 5);
		
		castingBarTargetText:SetWidth(0);
		castingBarTargetText:SetPoint("TOP", 0, 5);
	end
end

--------------------------------------------
-- Player Notes

local guildNotes, friendNotes, ignoreNotes;
local playerNoteButtons;

local function updatePlayerNotes(save)
	if ( save ) then
		module:setOption("guildNotes", guildNotes, true);
		module:setOption("friendNotes", friendNotes, true);
		module:setOption("ignoreNotes", ignoreNotes, true);
	else
		guildNotes, friendNotes, ignoreNotes =
			module:getOption("guildNotes"),
			module:getOption("friendNotes"),
			module:getOption("ignoreNotes");
	end
end

local function updateNote(self, playerName)
	local text = (self.wideEditBox):GetText();
	if ( text ~= "" ) then
		currentNoteType[playerName] = text;
	else
		currentNoteType[playerName] = nil;
	end
	if ( currentNoteType == guildNotes ) then
		GuildStatus_Update();
	elseif ( currentNoteType == friendNotes ) then
		FriendsList_Update();
	else
		IgnoreList_Update();
	end
	updatePlayerNotes(true);
end

local function getNoteDialogTable()
	return {
		text = module:getText("EDITING"),
		button1 = TEXT(ACCEPT),
		button2 = TEXT(CANCEL),
		hasEditBox = 1,
		hasWideEditBox = 1,
		maxLetters = 128,
		whileDead = 1,
		OnAccept = function(self, playerName)
			updateNote(self, playerName);
		end,
		timeout = 0,
		EditBoxOnEnterPressed = function(self, playerName)
			updateNote(self:GetParent(), playerName);
			self:GetParent():Hide();
		end,
		EditBoxOnEscapePressed = function(self, playerName)
			self:GetParent():Hide();
		end,
		hideOnEscape = 1
	};
end

local function editNote(playerName, noteType)
	if ( not StaticPopupDialogs["PLAYERNOTE_EDIT"] ) then
		StaticPopupDialogs["PLAYERNOTE_EDIT"] = getNoteDialogTable();
	end
	
	local coloredName;
	if ( noteType == ignoreNotes ) then
		coloredName = "|c00FF0000"..playerName.."|r";
	else
		coloredName = "|c0000FF00"..playerName.."|r";
	end
	
	local dialog = StaticPopup_Show("PLAYERNOTE_EDIT", coloredName);
	local staticPopupName = StaticPopup_Visible("PLAYERNOTE_EDIT");
	local editBox = _G[staticPopupName.."WideEditBox"];
	
	currentNoteType = noteType;
	dialog.data = playerName;
	editBox:SetText(noteType[playerName] or "");
	editBox:HighlightText();
end

local function playerNoteSkeleton()
	return "button#s:16:16#st:TOOLTIP#cache", {
		["onload"] = function(self)
			local normalTexture = self:CreateTexture();
			local highlightTexture = self:CreateTexture();
			normalTexture:SetAllPoints(self);
			highlightTexture:SetAllPoints(self);
			highlightTexture:SetBlendMode("ADD");
			
			self.normalTexture = normalTexture;
			self:SetNormalTexture(normalTexture);
			self:SetHighlightTexture(highlightTexture);
			self:SetDisabledTexture("Interface\\Buttons\\UI-GuildButton-PublicNote-Disabled");
			normalTexture:SetTexture("Interface\\Buttons\\UI-GuildButton-PublicNote-Up");
			highlightTexture:SetTexture("Interface\\Buttons\\UI-GuildButton-PublicNote-Up");
		end,
		["onclick"] = function(self)
			editNote(self.name, self.type);
		end,
		["onenter"] = function(self)
			local tooltip = GameTooltip;
			tooltip:SetOwner(self, "ANCHOR_RIGHT");
			tooltip:ClearLines();
			tooltip:AddLine(module:getText("CLICKEDIT"), 1, 0.7, 0);
			tooltip:AddLine(self.note);
			tooltip:Show();
		end,
		["onleave"] = module.hideTooltip
	};
end

local function getPlayerNoteButton(index)
	local obj = playerNoteButtons[index];
	if ( not obj ) then
		obj = module:getFrame(playerNoteSkeleton);
		playerNoteButtons[index] = obj;
	end
	return obj;
end

local function updateDisplay(offset, maxNum, framePrefix, frameOffset, tbl, nameFunc, parent)
	local numButtons, index, frame, name = 0;
	
	if ( playerNoteButtons ) then
		numButtons = #playerNoteButtons;
	else
		playerNoteButtons = { };
	end
	
	for i=1, max(maxNum, numButtons), 1 do
		btn = getPlayerNoteButton(i);
		if ( i > maxNum ) then
			btn:Hide();
		else
			index = offset + i;
			name = nameFunc(index);
			frame = _G[framePrefix..i];
			
			local note = tbl[name];
			if ( note ) then
				btn.note = note;
				btn.normalTexture:SetVertexColor(1.0, 1.0, 1.0);
			else
				btn.note = "";
				btn.normalTexture:SetVertexColor(0.5, 0.5, 0.5);
			end

			btn.type = tbl;
			btn.name = name;
			
			btn:SetParent(parent or frame);
			btn:ClearAllPoints();
			btn:SetPoint("RIGHT", frame, "LEFT", frameOffset, 0);
			btn:SetFrameLevel(frame:GetFrameLevel()+1);
			btn:Show();
		end
	end
end

-- Hooks for knowing which icons to display
function CTCore_FriendsList_Update(...)
	if ( not FriendsListFrame:IsVisible() ) then
		return;
	end
	
	local maxDisplay = FRIENDS_TO_DISPLAY;
	local maxNum = GetNumFriends();
	if ( maxNum > 0 and not friendNotes ) then
		friendNotes = { };
	end
	
	updateDisplay(FauxScrollFrame_GetOffset(FriendsFrameFriendsScrollFrame),
		min(maxNum, maxDisplay), "FriendsFrameFriendButton", 
		( ( maxDisplay >= maxNum ) and 315 ) or 290, friendNotes, GetFriendInfo);
end
hooksecurefunc("FriendsList_Update", CTCore_FriendsList_Update);

function CTCore_IgnoreList_Update(...)
	if ( not IgnoreListFrame:IsVisible() ) then
		return;
	end
	
	local maxDisplay = IGNORES_TO_DISPLAY;
	local maxNum = GetNumIgnores();
	if ( maxNum > 0 and not ignoreNotes ) then
		ignoreNotes = { };
	end
	
	updateDisplay(FauxScrollFrame_GetOffset(FriendsFrameFriendsScrollFrame),
		min(maxNum, maxDisplay), "FriendsFrameIgnoreButton", 
		( ( maxDisplay >= maxNum ) and 315 ) or 290, ignoreNotes, GetIgnoreName);
end
hooksecurefunc("IgnoreList_Update", CTCore_IgnoreList_Update);

function CTCore_GuildStatus_Update(...)
	if ( not GuildFrame:IsVisible() ) then
		return;
	end
	
	local framePrefix;
	if ( FriendsFrame.playerStatusFrame ) then
		framePrefix = "GuildFrameGuildStatusButton";
	else
		framePrefix = "GuildFrameButton";
	end
	
	local maxDisplay = GUILDMEMBERS_TO_DISPLAY;
	local maxNum = GetNumGuildMembers();
	if ( maxNum > 0 and not guildNotes ) then
		guildNotes = { };
	end
	
	updateDisplay(FauxScrollFrame_GetOffset(GuildListScrollFrame), min(maxNum, maxDisplay),
		framePrefix, ( ( maxDisplay >= maxNum ) and 320 ) or 303,
		guildNotes, GetGuildRosterInfo, GuildFrame);
end
hooksecurefunc("GuildStatus_Update", CTCore_GuildStatus_Update);

--------------------------------------------
-- Alt+Right-Click to buy full stack

local oldMerchantItemButton_OnModifiedClick = MerchantItemButton_OnModifiedClick;
function MerchantItemButton_OnModifiedClick(self, ...)
	if ( IsAltKeyDown() ) then
		local id = self:GetID();
		local maxStack = GetMerchantItemMaxStack(id);
		local money = GetMoney();
		local _, _, price, quantity = GetMerchantItemInfo(id);
		
		if ( maxStack == 1 and quantity > 1 ) then
			-- We need to check max stack count
			local _, _, _, _, _, _, _, stackCount = GetItemInfo(GetMerchantItemLink(id));
			if ( stackCount and stackCount > 1 ) then
				maxStack = floor(stackCount/quantity);
			end
		end
	
		if ( maxStack*price > money ) then
			maxStack = floor(money/price);
		end
		BuyMerchantItem(id, maxStack);
	else
		oldMerchantItemButton_OnModifiedClick(self, ...);
	end
end

--------------------------------------------
-- Hide Gryphons & World Map Minimap Button

local function toggleGryphons(hide)
	if ( hide ) then
		MainMenuBarLeftEndCap:Hide();
		MainMenuBarRightEndCap:Hide();
	else
		MainMenuBarLeftEndCap:Show();
		MainMenuBarRightEndCap:Show();
	end
end

local function toggleWorldMap(hide)
	if ( hide ) then
		MiniMapWorldMapButton:Hide();
	else
		MiniMapWorldMapButton:Show();
	end
end

--------------------------------------------
-- Movable casting bar

local castingbarAnchorFrame;
local castingbarEnabled;
local castingbarMovable;
local castingbarAlwaysFrame;

function castingbar_Reanchor()
	CastingBarFrame:ClearAllPoints();
	CastingBarFrame:SetPoint("CENTER", castingbarAnchorFrame, "CENTER", 0, -2);
end

local function CT_Core_Other_castingbar_UIParent_ManageFramePositions()
	if (castingbarEnabled) then
		castingbar_Reanchor();
	end
end

local function castingbar_onMouseDownFunc(self, button)
	if ( button == "LeftButton" ) then
		module:moveMovable(self.movable);
	end
end

local function castingbar_FrameSkeleton()
	return "button#st:HIGH#tl:mid:350:-200#s:100:30", {
		"tooltip#0:0:0:0.75",
		"font#v:GameFontNormal#i:text",
		["onleave"] = module.hideTooltip,
		["onmousedown"] = castingbar_onMouseDownFunc,
	};
end

local function castingbar_AlwaysVisibility(self)
	if (not self) then
		self = CastingBarFrame;
	end
	if (self.channeling or self.casting) then
		return;
	end
	if (not castingbarAlwaysFrame) then
		self:Hide();
		return;
	end
	if (not (castingbarEnabled and castingbarMovable)) then
		self:Hide();
		castingbarAlwaysFrame:Hide();
		return;
	end
	self:SetAlpha(1);
	self:SetStatusBarColor(1.0, 0.7, 0.0);
	local selfName = self:GetName();
	local barText = _G[selfName.."Text"];
	if ( barText ) then
		barText:SetText("");
	end
	self:Show();
	castingbarAlwaysFrame:Show();
end

local function castingbar_CreateAnchorFrame()
	local movable = "CASTINGBARANCHOR2";	
	castingbarAnchorFrame = module:getFrame(castingbar_FrameSkeleton, UIParent, "CT_Core_CastingBarAnchorFrame");
	
	module:registerMovable(movable, castingbarAnchorFrame, true);
	castingbarAnchorFrame.movable = movable;
	castingbarAnchorFrame:SetScript("OnEnter", function(self)
		module:displayTooltip(self, "|c00FFFFFFCasting Bar|r\nLeft-click to drag.\nRight-click to reset.");
	end);
	castingbarAnchorFrame:SetScript("OnMouseUp", function(self, button)
		if ( button == "LeftButton" ) then
			module:stopMovable(self.movable);
		elseif ( button == "RightButton" ) then
			CT_Core_CastingBarAnchorFrame:ClearAllPoints();
			CT_Core_CastingBarAnchorFrame:SetPoint("BOTTOM", UIParent, "BOTTOM", 0, 124);
		end
	end);
	castingbarAnchorFrame:SetScript("OnEvent", function(self, event, arg1, ...)
		if (event == "PLAYER_ENTERING_WORLD") then
			hooksecurefunc("UIParent_ManageFramePositions", CT_Core_Other_castingbar_UIParent_ManageFramePositions);
			-- By now GetCVar("uiScale") has a value, so if Blizzard_CombatLog is already loaded
			-- then it won't cause an error when it tries to multiply by the uiScale.
			UIParent_ManageFramePositions();
		end
	end);
	castingbarAnchorFrame:SetWidth(CastingBarFrame:GetWidth() + 6);
	castingbarAnchorFrame:SetHeight(28);
	castingbarAnchorFrame:Hide();
	castingbarAnchorFrame:SetParent(CastingBarFrame);
	castingbarAnchorFrame:RegisterEvent("PLAYER_ENTERING_WORLD");

	castingbarAlwaysFrame = CreateFrame("Frame");
	castingbarAlwaysFrame:SetScript("OnUpdate", function(self, elapsed)
		castingbar_AlwaysVisibility();
	end);
end

local function castingbar_UpdateAnchorVisibility()
	if (not castingbarAnchorFrame) then
		castingbar_CreateAnchorFrame();
	end
	if (castingbarEnabled and castingbarMovable) then
		castingbarAnchorFrame:Show();
	else
		castingbarAnchorFrame:Hide();
	end
end

local function castingbar_ToggleMovable(movable)
	castingbarMovable = movable;
	castingbar_UpdateAnchorVisibility();
	castingbar_AlwaysVisibility();
end

local function castingbar_ToggleStatus(enable)
	castingbarEnabled = enable;
	castingbar_UpdateAnchorVisibility();
	castingbar_AlwaysVisibility();
	if (castingbarEnabled) then
		castingbar_Reanchor();
	else
		-- When entering the world for the FIRST time after starting the game, GetCVar("uiScale")
		-- returns nil when CT_Core loads (ie. at ADDON_LOADED event time). The game hasn't had
		-- time to update the setting yet. This is also the case for GetCVarBool("scriptErrors").
		--
		-- When the Blizzard_CombatLog addon gets loaded, it hooks the FCF_DockUpdate function
		-- which gets called by the UIParent_ManageFramePositions function in UIParent.lua.
		--
		-- If there is an addon that loads before CT_Core and causes the Blizzard_CombatLog addon
		-- to load, then we want to avoid calling UIParent_ManageFramePositions while GetCVar("uiScale")
		-- is nil. If we do call it when the uiScale is nil, then the Blizzard_CombatLog code will cause an error
		-- when it gets to the Blizzard_CombatLog_AdjustCombatLogHeight() function in Blizzard_CombatLog.lua.
		-- That code tries to multiply by GetCVar("uiScale"), and since it is still nil then there will
		-- be an error.
		--
		-- Blizzard's code won't display the error (see BasicControls.xml) because GetCVarBool("scriptErrors")
		-- is still nil when CT_Core loads. The user won't see the error unless they have an addon that loads
		-- before CT_Core and traps and displays errors.
		--
		-- To avoid this error we will only call UIParent_ManageFramePositions() when the uiScale has
		-- a value. This same test is also done in the function assigned to the combinedTracker_Reanchor
		-- variable. These are the two places in this addon where UIParent_ManageFramePositions() may get called
		-- at ADDON_LOADED time by CT_Libary (during the "init" options step).

		CastingBarFrame:ClearAllPoints();
		if (GetCVar("uiScale")) then
			UIParent_ManageFramePositions();
		end
	end
end

--------------------------------------------
-- General Initializer

local modFunctions = {
	["castingTimers"] = toggleCastingTimers,
	["questLevels"] = toggleDisplayLevels,
	["blockBankTrades"] = toggleBlockTrades,
	["tickMod"] = toggleTick,
	["tickModFormat"] = setTickDisplayType,
	["tooltipRelocation"] = setTooltipRelocationStyle,
	["tooltipRelocationAnchor"] = toggleTooltipAnchorVisibility,
	["tooltipAnchor"] = setTooltipAnchor,
	["questTracker"] = combinedTracker_ToggleAnchor,
	["questTrackerMode"] = questTracker_UpdateAnchorMode,
	["achievementTrackerMode"] = achievementTracker_UpdateAnchorMode,
	["hideGryphons"] = toggleGryphons,
	["hideWorldMap"] = toggleWorldMap,
	["castingbarEnabled"] = castingbar_ToggleStatus,
	["castingbarMovable"] = castingbar_ToggleMovable,
};

local modFunctionsTrue = {
	["questTrackerAnchor"] = combinedTracker_ToggleAnchorVisibility,
	["combinedAnchor"] = combinedTracker_ToggleCombined,
};


module.modupdate = function(self, type, value)
	if ( type == "init" ) then
		for key, value in pairs(modFunctions) do
			value(self:getOption(key));
		end
		
		for key, value in pairs(modFunctionsTrue) do
			value(self:getOption(key) ~= false);
		end
		updatePlayerNotes();
	else
		local func = modFunctions[type];
		if ( func ) then
			func(value);
		else
			func = modFunctionsTrue[type];
			if ( func ) then
				func(value ~= false);
			end
		end
	end
end
