---------------------------------------------------------------
-- Hotkey management
---------------------------------------------------------------
-- This handler automatically renders icons instead of hotkey
-- strings on action buttons in the interface.

local _, db = ...;
local HotkeyMixin, HotkeyHandler = {}, CPAPI.CreateEventHandler({'Frame', '$parentHotkeyHandler', ConsolePort}, {
	'CVAR_UPDATE';
	'UPDATE_BINDINGS';
	'MODIFIER_STATE_CHANGED';
})
db:Register('Hotkeys', HotkeyHandler)
HotkeyHandler.Textures = CreateTexturePool(HotkeyHandler, 'ARTWORK')
HotkeyHandler.Widgets = CreateFramePool('Frame', HotkeyHandler, nil, function(pool, self)
	self:Hide()
	self:ClearAllPoints()
	if self.Release then
		self:Release()
		self:ClearOwner()
	end
end)

---------------------------------------------------------------
-- Events
---------------------------------------------------------------
function HotkeyHandler:ADDON_LOADED(...)
	-- need to run this on every addon loading
	self:OnInterfaceUpdated()
end

function HotkeyHandler:CVAR_UPDATE(...)
	-- not even sure this fires for gamepad stuff
end

function HotkeyHandler:UPDATE_BINDINGS(...)
	self:OnInterfaceUpdated()
end

function HotkeyHandler:MODIFIER_STATE_CHANGED(...)
	for widget in self.Widgets:EnumerateActive() do
		-- TODO: dispatch modifier
	end
end

function HotkeyHandler:OnInterfaceUpdated()
	-- hotkey rendering is expensive, make sure it doesn't run unnecessarily
	if not self.timeLock then
		self.timeLock = true
		C_Timer.After(0.5, function()
			self:OnActiveDeviceChanged()
			self.timeLock = nil
		end)
	end
end

function HotkeyHandler:OnActiveDeviceChanged()
	local device = db.Gamepad:GetActiveDevice()
	if device then
		self:UpdateHotkeys(device)
	end
end

db:RegisterCallback('Gamepad/Active', HotkeyHandler.OnActiveDeviceChanged, HotkeyHandler)

---------------------------------------------------------------
-- API
---------------------------------------------------------------
function HotkeyHandler:GetIconsForModifier(modifiers, device, style)
	for i, modifier in ipairs(modifiers) do
		local button = db('Gamepad/Index/Modifier/Key/'..modifier)
		modifiers[i] = button and device:GetIconForButton(button, style) or nil
	end
	return modifiers
end

function HotkeyHandler:GetHotkeyData(device, btnID, modID, styleMain, styleMod)
	return {
		button = device:GetIconForButton(btnID, styleMain);
		modifier = self:GetIconsForModifier({strsplit('-', modID)}, device, styleMod);
	}
end

function HotkeyHandler:GetButtonSlug(device, btnID, modID, split)
	local icon, isTextAbbr = ('|T%s:0:0:0:0:32:32:8:24:8:24|t')
	local data = self:GetHotkeyData(device, btnID, modID, split and 64 or 32, 32)
	if not data.button then
		data.button = _G[('KEY_ABBR_%s'):format(btnID)] or btnID:gsub('^PAD', '');
		isTextAbbr = true;
	end
	local slug = split and {} or '';
	for i, mod in db.table.ripairs(data.modifier) do
		if mod then
			if split then
				slug[#slug + 1] = icon:format(mod)
			else
				slug = slug .. icon:format(mod)
			end
		end
	end
	if split then
		return slug, data;
	end
	return slug .. (isTextAbbr and data.button or icon:format(data.button));
end

function HotkeyHandler:GetButtonSlugForBinding(binding, split)
	local device = db('Gamepad/Active')
	if not device then return end;
	local key = db('Gamepad'):GetBindingKey(binding)
	if key then
		local slug = {strsplit('-', key)}
		local btnID = tremove(slug)
		return self:GetButtonSlug(device, btnID, table.concat(slug, '-'), split)
	end
end

function HotkeyHandler:GetWidget()
	local frame, newObj = self.Widgets:Acquire()
	if newObj then
		Mixin(frame, HotkeyMixin)
		frame.Textures = self.Textures
	end
	return frame
end

function HotkeyHandler:Disable()
	self:UnregisterAllEvents()
	self.Widgets:ReleaseAll()
end

function HotkeyHandler:Enable()
	for _, event in ipairs(self.Events) do
		self:RegisterEvent(event)
	end
	self:OnActiveDeviceChanged()
end

---------------------------------------------------------------
-- Binding logic
---------------------------------------------------------------
function HotkeyHandler:UpdateHotkeys(device)
	self.Widgets:ReleaseAll()
	assert(device, 'No device specified when attempting to update hotkeys.')
	if db('disableHotkeyRendering') then return end
	self:ReplaceHotkeyFont()

	local bindings = db.Gamepad:GetBindings()
	local bindingToActionID = {}

	for btnID, set in pairs(bindings) do
		for modID, binding in pairs(set) do
			local actionBarID = db('Actionbar/Binding/'..binding)
			if actionBarID then
				bindingToActionID[binding] = self:GetHotkeyData(device, btnID, modID, 32, 32)
			else
				local widget = _G[(gsub(gsub(binding, 'CLICK ', ''), ':.+', ''))]
				if C_Widget.IsFrameWidget(widget) then
					self:GetWidget():SetData(self:GetHotkeyData(device, btnID, modID, 32, 32), widget)
				end
			end
		end
	end

	-- draw on action buttons
	for owner, action in db.Actionbar:GetActionButtons() do
		local data = bindingToActionID[db('Actionbar/Action/'..action)]
		if data then
			self:GetWidget():SetData(data, owner)
		end
	end
end

---------------------------------------------------------------
-- Replace font on Blizzard hotkey template
---------------------------------------------------------------
-- The original font draws an outline around each character,
-- pushing the text width over 36 px for double modifier
-- combinations. Removing the outline places 3 icons at exactly
-- 36 px without reducing the icon fidelity.
function HotkeyHandler:ReplaceHotkeyFont()
	NumberFontNormalSmallGray:SetFont('FONTS\\ARIALN.TTF', 12)
end

---------------------------------------------------------------
-- Hotkey mixin
---------------------------------------------------------------
HotkeyMixin.template = 'Default'; -- TODO: remove hardcoded

function HotkeyMixin:SetData(data, owner)
	if owner:GetAttribute('ignoregamepadhotkey') then
		return
	end
	
	self.data = data
	self:SetSize(1, 1)

	self:Release()
	self:SetOwner(owner)

	-- TODO: allow more templates
	local signature = 'return function(self, button, modifier, owner)' 
	local render, msg = loadstring(signature..self.Templates[self.template])
	if render then
		return render()(self, data.button, data.modifier, owner)
	end
	error('Hotkey template failed to compile:\n' .. msg)
end


function HotkeyMixin:Acquire()
	local texture = self.Textures:Acquire()
	texture:SetParent(self)
	return texture
end

function HotkeyMixin:Release()
	for obj in self.Textures:EnumerateActive() do
		if ( obj:GetParent() == self ) then
			self.Textures:Release(obj)
		end
	end
end

function HotkeyMixin:SetOwner(owner)
	self:SetParent(owner)
	if owner.HotKey then
		self.preAlpha = owner.HotKey:GetAlpha()
		self.preShown = owner.HotKey:IsShown()
		owner.HotKey:SetAlpha(0)
		owner.HotKey:Hide()
	end
	self:Show()
end

function HotkeyMixin:ClearOwner()
	local owner = self:GetParent()
	if owner and owner.HotKey and self.preAlpha then
		owner.HotKey:SetAlpha(self.preAlpha)
		owner.HotKey:SetShown(self.preShown)
	end
	self.preShown = nil;
	self.preAlpha = nil;
	self:SetParent(HotkeyHandler)
end

---------------------------------------------------------------
-- Hotkey templates
---------------------------------------------------------------
-- TODO: write more templates, allow some kind of savedvar for this
HotkeyMixin.Templates = {
	Default = [[
		self:SetPoint('TOPRIGHT', owner, 0, 0)
		local cur = self:Acquire()

		cur:SetSize(24, 24)
		cur:SetPoint('TOPRIGHT', 4, 4)
		cur:SetTexture(button)
		cur:Show()

		for i = 1, #modifier do
			local mod = self:Acquire()
			mod:SetSize(24, 24)
			mod:SetPoint('RIGHT', cur, 'LEFT', 14, 0)
			mod:SetTexture(modifier[i])
			mod:Show()
			cur = mod
		end
	end]];
};
db('Hotkeys/Template', HotkeyMixin.Templates)