------------------------------------------------
--                 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 module = { };
local _G = getfenv(0);

local MODULE_NAME = "CT_BarMod";
local MODULE_VERSION = strmatch(GetAddOnMetadata(MODULE_NAME, "version"), "^([%d.]+)");

module.name = MODULE_NAME;
module.version = MODULE_VERSION;

_G[MODULE_NAME] = module;
CT_Library:registerModule(module);

--------------------------------------------
-- Variables

-- Local Copies
local tremove = tremove;
local type = type;
local GetActionTexture = GetActionTexture;

-- Referenced at multiple locations
local currentButtonClass;
local actionButton = { };
local actionButtonList = { };
local savedButtons;
module.actionButtonList = actionButtonList;

--------------------------------------------
-- Helpers

local currentHover;

local function updateHover(_, self)
	if ( not self ) then
		self = currentHover;
		if ( not self ) then
			return;
		end
	end
	if ( GetCVar("UberTooltips") == "1" ) then
		GameTooltip_SetDefaultAnchor(GameTooltip, self);
	else
		GameTooltip:SetOwner(self, "ANCHOR_CURSOR");
	end

	if ( GameTooltip:SetAction(self.object.id) ) then
		currentHover = self;
	else
		currentHover = nil;
	end
end

local actionButtonObjectData;
local function actionButtonObjectSkeleton()
	if ( not actionButtonObjectData ) then
		actionButtonObjectData = {
			-- Background
			"texture#all#i:icon##BACKGROUND",
			
			-- Artwork
			"texture#all#i:flash#hidden#Interface\\Buttons\\UI-QuickslotRed",
			"font#s:36:10#tl:-2:-2#i:hotkey#v:NumberFontNormalSmallGray##r",
			"font#br:-2:2#i:count#v:NumberFontNormal##r",
			
			-- Overlay
			"font#s:36:10#b:0:2#i:name#v:GameFontHighlightSmallOutline##OVERLAY",
			"texture#s:62:62#mid#i:border#hidden#Interface\\Buttons\\UI-ActionButton-Border#OVERLAY",
			
			-- OnLoad
			["onload"] = function(self)
				local normalTexture = self:CreateTexture(nil, "ARTWORK");
				local pushedTexture = self:CreateTexture(nil, "ARTWORK");
				local highlightTexture = self:CreateTexture(nil, "ARTWORK");
				local checkedTexture = self:CreateTexture(nil, "ARTWORK");
				local cooldown = CreateFrame("Cooldown", nil, self, "CooldownFrameTemplate");
				
				normalTexture:SetWidth(66); normalTexture:SetHeight(66);
				normalTexture:SetPoint("CENTER", self, 0, -1);
				normalTexture:SetTexture("Interface\\Buttons\\UI-Quickslot2");
				
				pushedTexture:SetTexture("Interface\\Buttons\\UI-Quickslot-Depress");
				pushedTexture:SetAllPoints(self);
				
				highlightTexture:SetTexture("Interface\\Buttons\\ButtonHilight-Square");
				highlightTexture:SetBlendMode("ADD");
				highlightTexture:SetAllPoints(self);
				
				checkedTexture:SetTexture("Interface\\Buttons\\CheckButtonHilight");
				checkedTexture:SetBlendMode("ADD");
				checkedTexture:SetAllPoints(self);
				
				self:SetNormalTexture(normalTexture);
				self:SetPushedTexture(pushedTexture);
				self:SetHighlightTexture(highlightTexture);
				self:SetCheckedTexture(checkedTexture);
				
				self.border:SetBlendMode("ADD");
				self.cooldown = cooldown;
				self.normalTexture = normalTexture;
			end,
			
			-- OnEnter
			["onenter"] = function(self)
				updateHover(nil, self);
				module:schedule(1, true, updateHover);
			end,
			
			-- OnLeave
			["onleave"] = function(self)
				GameTooltip:Hide();
				currentHover = nil;
				module:unschedule(updateHover, true);
			end,
			
			-- OnMouseDown
			["onmousedown"] = function(self, ...)
				self.object:onmousedown(self, ...);
			end,
			
			-- OnMouseUp
			["onmouseup"] = function(self, ...)
				self.object:onmouseup(self, ...);
			end,
			
			-- OnDragStart
			["ondragstart"] = function(self, ...)
				self.object:ondragstart(self, ...);
			end,
			
			-- OnDragStop
			["ondragstop"] = function(self, ...)
				self.object:ondragstop(self, ...);
			end,
			
			-- OnReceiveDrag
			["onreceivedrag"] = function(self, ...)
				self.object:onreceivedrag(self, ...);
			end,
			
			-- PostClick
			["postclick"] = function(self, ...)
				self.object:postclick(self, ...);
			end
		};
	end
	return "checkbutton#s:36:36#v:SecureActionButtonTemplate", actionButtonObjectData;
end

local actionButtonObjectPool = { };
local function getActionButton(id)
	return tremove(actionButtonObjectPool) or module:getFrame(actionButtonObjectSkeleton, nil, "CT_BarModActionButton"..id);
end

local function getActionButtonId()
	local num = #actionButtonList;
	for i = 1, num, 1 do
		if ( not actionButtonList[i] ) then
			return i;
		end
	end
	return num + 1;
end

--------------------------------------------
-- Action Button Class

module.actionButtonClass = actionButton;

-- Create a new object
function actionButton:new(...)
	local button = { };
	setmetatable(button, newButtonMeta);
	button:constructor(...);
	return button;
end

-- Destroy an object
function actionButton:drop()
	self:destructor();
end

-- Constructor, run on object creation
function actionButton:constructor(id, group, noInherit, resetMovable)

	if ( noInherit ) then
		return;
	end
	
	id = id or getActionButtonId();
	local button = getActionButton(id);
	local obj = savedButtons[id];
	
	if ( resetMovable ) then
		module:resetMovable(id);
		self.scale = UIParent:GetScale();
	else
		self.scale = button:GetScale();
	end
	
	self.group = group or ceil(id / 12);
	
	button:RegisterForClicks("LeftButtonUp", "RightButtonUp", "MiddleButtonUp", "Button4Up", "Button5Up");
	button:RegisterForDrag("LeftButton", "RightButton");
	button:SetAttribute("type", "action");
	module:registerMovable(id, button);
	actionButtonList[id] = self;
	button.object = self;
	button.cooldown.object = self;
	
	self.button = button;
	self.id = id;
	self.name = button:GetName();
	
	self:savePosition();
	self:setMode(currentMode);
	self:setBinding();
end

-- Destructor, run on object destruction
function actionButton:destructor(noInherit)

	if ( noInherit ) then
		return;
	end
	
	self.button:Hide();
	actionButtonList[self.id] = nil;
	
	tinsert(self, actionButtonObjectPool);
end

-- General updater
function actionButton:update()
	-- Placeholder for derived classes
end

-- Update texture
function actionButton:updateTexture()
	self.button.icon:SetTexture(GetActionTexture(self.id));
end

-- Updates the options table for this button, or creates it
function actionButton:updateOptions()
	local id = self.id;
	local option = module:getOption(id);
	if ( option ) then
		-- Update table
	else
		-- Create new table
	end
end

-- Sets the editing mode
function actionButton:setMode(newMode)
	self:destructor(true);
	setmetatable(self, newButtonMeta);
	self:constructor(nil, nil, true);
	self:update();
end

-- Change a button's scale
function actionButton:setScale(scale)
	scale = min(max(scale, 0.35), 3);
	self:savePosition();
	self.scale = scale;
	self:updatePosition();
	
	if ( not self.moving ) then
		module:stopMovable(self.id);
	end
end

-- Start moving this button
function actionButton:move()
	self.moving = true;
	module:moveMovable(self.id);
end

-- Stop moving this button
function actionButton:stopMove()
	self.moving = nil;
	module:stopMovable(self.id);
	self:savePosition();
end

-- Save position for this session
function actionButton:savePosition()
	local scale, xPos, yPos = self.scale, self.button:GetCenter();
	self.xPos, self.yPos = xPos*scale, yPos*scale;
	self:updatePosition();
end

-- Update position, takes scale into account
function actionButton:updatePosition()
	local scale, xPos, yPos = self.scale, self.xPos, self.yPos;
	local button = self.button;
	
	button:SetScale(scale);
	button:ClearAllPoints();
	button:SetPoint("CENTER", nil, "BOTTOMLEFT", xPos/scale, yPos/scale);
end

-- Set binding depending on saved option
function actionButton:setBinding(binding)
	binding = binding or module:getOption("BINDING-"..self.id);
	if ( binding ) then
		SetBindingClick(binding, self.name);
	end
	self:updateBinding();
end

-- Fallback Placeholders
function actionButton:updateBinding() end
function actionButton:onmousedown() end
function actionButton:onmouseup() end
function actionButton:ondragstart() end
function actionButton:ondragstop() end
function actionButton:onreceivedrag() end
function actionButton:postclick() end

--------------------------------------------
-- Action Button List Handler

local lastMethod;
module.actionButtonList = actionButtonList;

local function doMethod(...)
	for key, value in pairs(actionButtonList) do
		value[lastMethod](value, select(2, ...));
	end
end
setmetatable(actionButtonList, { __index = function(key, value)
	local obj = currentButtonClass[value];
	if ( type(obj) == "function" ) then
		lastMethod = value; return doMethod;
	else return obj; end end });


--------------------------------------------
-- Event Handlers

local function eventHandler_SlotChanged(event, id)
	if ( id == 0 ) then
		actionButtonList:update();
	else
		local object = actionButtonList[id];
		if ( object ) then
			object:update();
		end
	end
end

module:regEvent("ACTIONBAR_SLOT_CHANGED", eventHandler_SlotChanged);

--------------------------------------------
-- Mode Handler

function module:setMode(newMode)
	if ( currentMode ~= newMode ) then
		if ( currentMode ) then
			module[currentMode.."Disable"](module);
		end
		newButtonMeta = module[newMode.."ButtonMeta"];
		currentButtonClass = module[newMode.."ButtonClass"];
		actionButtonList:setMode(newMode);
		currentMode = newMode;
		module[newMode.."Enable"](module);
	end
end

--------------------------------------------
-- Key Bindings Purger

module:regEvent("UPDATE_BINDINGS", function()
	local GetBindingAction, strmatch, binding, action = GetBindingAction, strmatch;
	for key, value in pairs(actionButtonList) do
		binding = module:getOption("BINDING-"..key);
		if ( binding ) then
			action = GetBindingAction(binding);
			if ( action and tostring(key) ~= strmatch(action,"^CLICK CT_BarModActionButton(%d+)") ) then
				module:setOption("BINDING-"..key, nil, true);
			end
		end
	end
end);

--------------------------------------------
-- Mod Initialization

module.update = function(self, type, value)
	if ( type == "init" ) then
		self:setMode("use");
		
		-- Set up our buttons
		--savedButtons = self:getOption("buttons");
		--if ( not buttons ) then
			savedButtons = { };
			module:setupPresetGroups();
		--else
		--	for key, value in ipairs(buttons) do
		--		actionButton:new();
		--	end
		--end
	end
	self:editUpdate(type, value);
	self:useUpdate(type, value);
	self:optionUpdate(type, value);
end