------------------------------------------------
--                 CT_BarMod                  --
--                                            --
-- Intuitive yet powerful action bar addon,   --
-- featuring per-button positioning as well   --
-- as scaling while retaining the concept of  --
-- grouped buttons and action bars.           --
-- Please do not modify or otherwise          --
-- redistribute this without the consent of   --
-- the CTMod Team. Thank you.                 --
------------------------------------------------

--------------------------------------------
-- Initialization

local _G = getfenv(0);
local module = _G.CT_BarMod;

-- Local Copies
local format = format;
local tostring = tostring;
local MouseIsOver = MouseIsOver;
local actionButtonList = module.actionButtonList;
local groupList = module.groupList;

-- Tooltip for figuring out spell name
TOOLTIP = CreateFrame("GameTooltip", "CT_BarModTooltip", nil, "GameTooltipTemplate");
TOOLTIP_TITLELEFT = _G.CT_BarModTooltipTextLeft1;
TOOLTIP_TITLERIGHT = _G.CT_BarModTooltipTextRight1;
TOOLTIP:SetOwner(WorldFrame, "ANCHOR_NONE");

--------------------------------------------
-- Key Bindings Handler

local bindingFrame, currentBinding, attemptedKey;

function getSpellName(actionId, noRank)
	if ( not HasAction(actionId) ) then
		return "|c00FF2222<|r|c00FFFFFFNone|r|c00FF2222>|r";
	end
	
	-- Set up our tooltip
	TOOLTIP:ClearLines();
	TOOLTIP:SetAction(actionId);
	
	-- Display the name
	local spellName, spellRank = TOOLTIP_TITLELEFT:GetText() or "", TOOLTIP_TITLERIGHT:GetText() or "";
	if ( not noRank ) then
		spellRank = spellRank:match("(%d+)$") or "";
		if ( spellRank ~= "" ) then
			spellRank = " (R"..spellRank..")";
		end
	else
		spellRank = "";
	end
	
	if ( spellName == "" ) then
		module:print("Failed on", actionId, TOOLTIP_TITLELEFT:GetText(), TOOLTIP_TITLERIGHT:GetText());
		spellName = "|c00FF2222<|r|c00FFFFFFNone|r|c00FF2222>|r";
	end
	return spellName..spellRank;
end

local function updateEntry(id, object, isGroup)
	local displayObj = bindingFrame[tostring(id)];
	if ( not displayObj ) then
		module:print(id, object, isGroup);
	end
	if ( isGroup ) then
		if ( object.hiddenDisplay ) then
			displayObj.header:SetText("+");
		else
			displayObj.header:SetText("-");
		end
		
		local spell = displayObj.spell;
		spell:SetFontObject(GameFontNormalLarge);
		spell:SetText("Group "..object.id);
		displayObj.binding:SetText("");
		displayObj.id = -1;
		displayObj.group = object;
	else
		local actionId = object.id;
		local spell = displayObj.spell;
		spell:SetFontObject(ChatFontNormal);
		spell:SetText(format("|c00FFD200%3d|r %s", actionId, getSpellName(actionId)));
		
		displayObj.header:SetText("");
		displayObj.binding:SetText(module:getOption("BINDING-"..actionId) or "");
		displayObj.id = actionId;
		displayObj.isGroup = il;
	end
	displayObj:Show();
	if ( id == 13 ) then
		return true;
	end
end

local function updateKeyBinds()
	local offset = FauxScrollFrame_GetOffset(CT_BarModOptionsKeyBindingsScrollFrame);
	local num, objects, shallBreak = 0, 0;
	for gid, group in ipairs(groupList) do
		objects = group.objects;
		if ( objects ) then
			num = num + 1;
			if ( num > offset ) then
				if ( updateEntry(num-offset, group, true) ) then
					break;
				end
			end
			if ( not group.hiddenDisplay ) then
				for bid, button in ipairs(objects) do
					num = num + 1;
					if ( num > offset ) then
						if ( updateEntry(num-offset, button) ) then
							shallBreak = true;
							break;
						end
					end
				end
				if ( shallBreak ) then break; end
			end
		end
	end
	
	-- Hide all other entries
	for i = num+1, 13, 1 do
		bindingFrame[tostring(i)]:Hide();
	end
end

local function captureBinding(obj, conflict)
	if ( obj and conflict ) then
		local bindingKey = GetBindingText(conflict, "BINDING_NAME_");
		local id = bindingKey:match("^CLICK CT_BarModActionButton(%d+)");
		if ( id ) then
			bindingKey = format("|c00FFD200%d|r |c00FFFFFF%s|r", id, getSpellName(id, true));
			local button = actionButtonList[tonumber(id)];
			if ( button ) then
				bindingKey = bindingKey .. " |c00FFFFFF(G"..button.group..")|r";
			end
		end
		
		bindingFrame.instruction:SetText(format(
			"The key |c00FFFFFF%s|r is used by\n|c00FFFFFF%s|r\n"..
			"|c0000FF00Enter|r to Overwrite / |c00FF0000Escape|r to Cancel.",
			attemptedKey, bindingKey));
	elseif ( obj ) then
		currentBinding = obj;
		attemptedKey = nil;
		bindingFrame:EnableKeyboard(true);
		bindingFrame.instruction:SetText(format(
			"Press a key to set the key binding for\n|c00FFFFFF%s|r\nPress |c00FF0000Escape|r to cancel.", getSpellName(obj.id)));
	else
		currentBinding, attemptedKey = nil;
		bindingFrame:EnableKeyboard(false);
		bindingFrame.instruction:SetText(
			"Click a button below to change its key binding.");
	end
	updateKeyBinds();
end

local prevOffset;
local function updateKeyBindingsScroll()
	local scrollFrame = CT_BarModOptionsKeyBindingsScrollFrame;
	if ( currentBinding ) then
		scrollFrame:SetVerticalScroll(prevOffset or 0);
		return;
	end
	
	-- Get number of entries
	local numEntries, objects = 0;
	for key, value in ipairs(groupList) do
		numEntries = numEntries + 1;
		if ( not value.hiddenDisplay ) then
			objects = value.objects;
			if ( objects ) then
				for k, v in ipairs(objects) do
					numEntries = numEntries + 1;
				end
			end
		end
	end
	
	-- Update scrollbar
	prevOffset = scrollFrame:GetVerticalScroll();
	FauxScrollFrame_Update(CT_BarModOptionsKeyBindingsScrollFrame, numEntries, 13, 25);
	updateKeyBinds();
	
	_G[scrollFrame:GetName().."ScrollChildFrame"]:SetHeight(scrollFrame:GetHeight());
end

local function selectObject(obj, conflict)
	local id;
	if ( obj ) then
		id = obj.id;
		if ( id == -1 ) then
			-- Group
			if ( not currentBinding ) then
				obj.group.hiddenDisplay = not obj.group.hiddenDisplay;
				updateKeyBindingsScroll();
				captureBinding();
			end
			return;
		end
	end
	
	local tempObj;
	for i = 1, 13, 1 do
		tempObj = bindingFrame[tostring(i)];
		if ( tempObj.id ~= id ) then
			tempObj.selected = false;
			if ( MouseIsOver(tempObj) ) then
				tempObj:GetScript("OnEnter")(tempObj);
			else
				tempObj:GetScript("OnLeave")(tempObj);
			end
		else
			tempObj.selected = true;
			if ( conflict ) then
				tempObj.background:SetVertexColor(1, 0.45, 0.1, 0.6);
			else
				tempObj.background:SetVertexColor(1, 0.87, 0.3, 0.4);
			end
		end
	end
	
	captureBinding(obj, conflict);
end

local function setBindingKey(actionId, key)
	local obj = actionButtonList[actionId];
	module:setOption("BINDING-"..actionId, key, true);
	if ( obj ) then
		obj:setBinding(key);
		SaveBindings(GetCurrentBindingSet());
	end
end

local function setBinding(key)
	if ( key == "UNKNOWN" or key == "SHIFT" or
	     key == "CTRL" or key == "ALT" ) then
		return;
	end
	
	if ( key == "LeftButton" ) then
		key = "BUTTON1";
	elseif ( key == "RightButton" ) then
		key = "BUTTON2";
	elseif ( key == "MiddleButton" ) then
		key = "BUTTON3";
	elseif ( key == "Button4" ) then
		key = "BUTTON4"
	elseif ( key == "Button5" ) then
		key = "BUTTON5"
	end
	
	if ( IsShiftKeyDown() ) then key = "SHIFT-"..key; end
	if ( IsControlKeyDown() ) then key = "CTRL-"..key; end
	if ( IsAltKeyDown() ) then key = "ALT-"..key; end
	
	if ( key == "ESCAPE" ) then
		selectObject();
		currentBinding, attemptedKey = nil;
		return;
	end
	
	if ( currentBinding and attemptedKey ) then
		if ( key == "ENTER" ) then
			setBindingKey(currentBinding.id, attemptedKey);
			selectObject();
			currentBinding, attemptedKey = nil;
		end
		return;
	end
	
	if ( not currentBinding ) then return; end
	
	local currKey = GetBindingAction(key);
	if ( currKey and currKey ~= "" ) then
		attemptedKey = key;
		selectObject(currentBinding, currKey);
	else
		setBindingKey(currentBinding.id, key);
		selectObject();
	end
end

--------------------------------------------
-- Options Frame

-- Update the sliders
local currentEditGroup, groupFrame = 1;
local function updateSliders()
	if ( not groupFrame ) then
		return;
	end
	
	local scale, opacity, spacing = module:getOption("barScale"..currentEditGroup) or 1,
					module:getOption("barOpacity"..currentEditGroup) or 1,
					module:getOption("barSpacing"..currentEditGroup) or 6
	groupFrame.scale:SetValue(scale);
	groupFrame.opacity:SetValue(opacity);
	groupFrame.spacing:SetValue(spacing);
end

-- Show/Hide headers
local function showGroupHeaders(self)
	for key, value in ipairs(groupList) do
		value:toggleHeader(true);
	end
	if ( self and CT_BottomBar and CT_BottomBar.show ) then
		CT_BottomBar.show();
	end
end
local function hideGroupHeaders(self)
	for key, value in ipairs(groupList) do
		value:toggleHeader(false);
	end
	if ( self and CT_BottomBar and CT_BottomBar.hide ) then
		CT_BottomBar.hide();
	end
end

module.show, module.hide = showGroupHeaders, hideGroupHeaders;

module.frame = function()
	local keyBindingTemplate = {
		"font#r:l:-2:0#v:GameFontNormalLarge#i:header",
		"font#r:-5:0#v:GameFontNormal#i:binding##r",
		"font#l:5:0#r:l:binding#v:ChatFontNormal#i:spell##l",
		"texture#all#i:background#1:1:1:1:1",
		
		["onload"] = function(self)
			self.background:SetVertexColor(1, 1, 1, 0);
			self.header:SetFont("FRIZQT__.TTF", 20,"OUTLINE, MONOCHROME")
		end,
		["onenter"] = function(self)
			if ( self.selected ) then return; end
			self.background:SetVertexColor(1, 1, 1, 0.2);
		end,
		["onleave"] = function(self)
			if ( self.selected ) then return; end
			self.background:SetVertexColor(1, 1, 1, 0);
		end,
		["onclick"] = function(self)
			if ( self.tempSkip ) then
				self.tempSkip = nil;
				return;
			elseif ( currentBinding ~= self ) then
				selectObject(self);
			end
		end,
		["onmousedown"] = function(self, button)
			if ( currentBinding == self ) then
				setBinding(button);
				self.tempSkip = true;
			end
		end
	};
	
	return "frame#all", {
		"font#t#s:0:30#l#r#(Note: Bars are only movable when the control panel is open)#0.6:0.6:0.6",
		"font#tl:5:-30#v:GameFontNormalLarge#General Options",
		"font#tl:20:-50#v:ChatFontNormal#Out of Range:",
		"dropdown#n:CT_BarModDropdown1#tl:90:-50#o:colorLack#Color Red#Fade Out#None",
		"checkbutton#tl:20:-70#o:displayCount:true#Display Cooldown Counts",
		"checkbutton#tl:20:-90#o:displayBindings:true#Display Key Bindings",
		"checkbutton#tl:20:-110#o:shiftParty:true#Shift Party Frame Right",
		"checkbutton#tl:20:-130#o:shiftShapeshift#Shift Pet/Stance Bars Upwards",
		"checkbutton#tl:20:-150#o:hideGrid#Hide Empty Button Grid",
		"checkbutton#tl:20:-170#o:buttonLock#Button Lock (Shift+Drag to Pick Up)",
		
		"font#tl:5:-210#v:GameFontNormalLarge#Display Options",
		"checkbutton#tl:20:-230#o:showGroup1:true#Display Group 1",
		"checkbutton#tl:20:-250#o:showGroup2:true#Display Group 2",
		"checkbutton#tl:20:-270#o:showGroup3:true#Display Group 3",
		"checkbutton#tl:20:-290#o:showGroup4:true#Display Group 4",
		"checkbutton#tl:20:-310#o:showGroup5:true#Display Group 5",
		
		["frame#tl:0:-350#br:tr:0:-515"] = {
			"font#tl:5:-5#v:GameFontNormalLarge#Bar Options",
			"font#tl:20:-25#v:ChatFontNormal#Select Bar Group:",
			"dropdown#tl:120:-25#n:CT_BarModDropdown2#o:editGroup#Group 1#Group 2#Group 3#Group 4#Group 5",
			"slider#t:0:-65#o:barScale:1#i:scale#s:175:17#Bar Scale - <value>#0.25:2:0.05",
			"slider#t:0:-100#o:barOpacity:1#i:opacity#s:175:17#Bar Opacity - <value>#0:1:0.05",
			"slider#t:0:-135#o:barSpacing:6#i:spacing#s:175:17#Bar Spacing - <value>#0:25:1",
			
			["onload"] = function(self)
				groupFrame = self;
				updateSliders();
			end
		},
		
		["frame#tl:0:-515#br:tr:0:-890"] = {
			"font#tl:5:-5#v:GameFontNormalLarge#Key Bindings",
			"font#mid:t:7:-50#v:GameFontNormal#i:instruction",
			["button#tl:20:-75#s:0:25#r:-20:0#i:1"] = keyBindingTemplate,
			["button#tl:20:-100#s:0:25#r:-20:0#i:2"] = keyBindingTemplate,
			["button#tl:20:-125#s:0:25#r:-20:0#i:3"] = keyBindingTemplate,
			["button#tl:20:-150#s:0:25#r:-20:0#i:4"] = keyBindingTemplate,
			["button#tl:20:-175#s:0:25#r:-20:0#i:5"] = keyBindingTemplate,
			["button#tl:20:-200#s:0:25#r:-20:0#i:6"] = keyBindingTemplate,
			["button#tl:20:-225#s:0:25#r:-20:0#i:7"] = keyBindingTemplate,
			["button#tl:20:-250#s:0:25#r:-20:0#i:8"] = keyBindingTemplate,
			["button#tl:20:-275#s:0:25#r:-20:0#i:9"] = keyBindingTemplate,
			["button#tl:20:-300#s:0:25#r:-20:0#i:10"] = keyBindingTemplate,
			["button#tl:20:-325#s:0:25#r:-20:0#i:11"] = keyBindingTemplate,
			["button#tl:20:-350#s:0:25#r:-20:0#i:12"] = keyBindingTemplate,
			["button#tl:20:-375#s:0:25#r:-20:0#i:13"] = keyBindingTemplate,
			
			["onload"] = function(self)
				bindingFrame = self;
				module:regEvent("UPDATE_BINDINGS", updateKeyBinds);
				
				local scrollFrame = CreateFrame("ScrollFrame", "CT_BarModOptionsKeyBindingsScrollFrame",
					self, "FauxScrollFrameTemplate");
				scrollFrame:SetPoint("TOPLEFT", self, 0, -80);
				scrollFrame:SetPoint("BOTTOMRIGHT", self, -19, -25);
				scrollFrame:SetScript("OnVerticalScroll", function()
					FauxScrollFrame_OnVerticalScroll(25, updateKeyBindingsScroll);
				end);
			end,
			["onshow"] = function(self)
				captureBinding();
				updateKeyBindingsScroll();
			end,
			["onkeydown"] = function(self, key)
				setBinding(key);
			end
		},
		
		["onshow"] = showGroupHeaders,
		["onhide"] = hideGroupHeaders,
	};
end

--------------------------------------------
-- Options Updater

local function updateGroups()
	for key, value in ipairs(groupList) do
		value:update("barScale", module:getOption("barScale"..key) or 1);
		value:update("barOpacity", module:getOption("barOpacity"..key) or 1);
		value:update("barSpacing", module:getOption("barSpacing"..key) or 6);
		value:update("showGroup", module:getOption("showGroup"..key) ~= false);
	end
end

local shiftShapeshift;

local oldSetPoint = ShapeshiftBarFrame.SetPoint;
function ShapeshiftBarFrame:SetPoint(...)
	local pt, rel, relpt, x, y = ...;
	if ( rel == "MainMenuBar" or rel == MainMenuBar ) then
		if ( shiftShapeshift and not SHOW_MULTI_ACTIONBAR_1 ) then
			if ( x and y ) then
				oldSetPoint(self, pt, rel, relpt, x, y + 40);
				return;
			end
		end
	end
	oldSetPoint(self, ...);
end

local oldSetPoint = PetActionBarFrame.SetPoint;
function PetActionBarFrame:SetPoint(...)
	local pt, rel, relpt, x, y = ...;
	if ( rel == "MainMenuBar" or rel == MainMenuBar ) then
		if ( shiftShapeshift and not SHOW_MULTI_ACTIONBAR_1 ) then
			if ( x and y ) then
				oldSetPoint(self, pt, rel, relpt, x, y + 40);
				return;
			end
		end
	end
	oldSetPoint(self, ...);
end

local function updateShapeshift(shift)
	shiftShapeshift = shift;
	HidePetActionBar();
	ShowPetActionBar();
end

module.optionUpdate = function(self, type, value)
	if ( type ~= "init" and value == nil ) then return; end
	
	if ( type == "editGroup" ) then
		self:setOption("editGroup", nil, true);
		currentEditGroup = value;
		updateSliders();
	
	elseif ( type == "barScale" or type == "barOpacity" or type == "barSpacing" ) then
		self:setOption(type, nil, true);
		self:setOption(type..currentEditGroup, value, true);
		
		local group = groupList[currentEditGroup];
		if ( group ) then
			group:update(type, value);
		end
		
	elseif ( type == "shiftParty" ) then
		local point, rel, relpoint, x, y = PartyMemberFrame1:GetPoint(1);
		if ( value ~= false ) then
			x = x + 37;
		else
			x = x - 37;
		end
		PartyMemberFrame1:SetPoint(point, rel, relpoint, x, y);
		
	elseif ( type == "shiftShapeshift" ) then
		updateShapeshift(value);
		
	elseif ( type == "init" ) then
		updateGroups();
		updateSliders();
		
		-- Shift Party
		local point, rel, relpoint, x, y = PartyMemberFrame1:GetPoint(1);
		if ( self:getOption("shiftParty") ~= false ) then
			x = x + 37;
		end
		PartyMemberFrame1:SetPoint(point, rel, relpoint, x, y);
		
		updateShapeshift(self:getOption("shiftShapeshift"));
		RegisterUnitWatch(PetActionBarFrame);
		PetActionBarFrame:SetAttribute("unit", "pet");
		
	else
		local group = type:match("^showGroup(%d+)$");
		if ( group ) then
			group = groupList[tonumber(group)];
			if ( group ) then
				if ( value ) then
					group:show();
				else
					group:hide();
				end
			end
		end
	end
end

module:regEvent("PLAYER_REGEN_ENABLED", updateGroups);