--[[
Copyright (c) 2006, Hendrik Leppkes < mail@nevcairiel.net >
All rights reserved.

Redistribution and use in source and binary forms, with or without 
modification, are permitted provided that the following conditions are 
met:

    * Redistributions of source code must retain the above copyright
       notice, this list of conditions and the following disclaimer.
    * Redistributions in binary form must reproduce the above 
       copyright notice, this list of conditions and the following 
       disclaimer in the documentation and/or other materials provided 
       with the distribution.
    * Neither the name of the Bartender3 Development Team nor the names of 
       its contributors may be used to endorse or promote products derived 
       from this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 
"AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 
OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 
SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 
LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 
DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 
THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE 
USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
]]

--[[ $Id: Bartender3_ButtonClass.lua 36999 2007-05-26 16:54:06Z nevcairiel $ ]]
local Bartender3 = Bartender3

local VERSION = tonumber(("$Revision: 36999 $"):match("%d+"))
if Bartender3.revision < VERSION then
	Bartender3.version = Bartender3.versionstring:format(VERSION)
	Bartender3.revision = VERSION
	Bartender3.date = ('$Date: 2007-05-26 12:54:06 -0400 (Sat, 26 May 2007) $'):match('%d%d%d%d%-%d%d%-%d%d')
end

local AceOO = AceLibrary("AceOO-2.0")

if not Bartender3.Class then Bartender3.Class = {} end
Bartender3.Class.Button = AceOO.Class("AceEvent-2.0", "AceHook-2.1")

local _G = getfenv(0)

local function ShortenKeyBinding(text)
	text = text:gsub("CTRL--", "|c00FF9966C|r")
	text = text:gsub("STRG--", "|c00CCCC00S|r")
	text = text:gsub("ALT--", "|c009966CCA|r")
	text = text:gsub("SHIFT--", "|c00CCCC00S|r")
	text = text:gsub("Num Pad ", "NP")
	text = text:gsub("Mouse Button ", "M")
	text = text:gsub("Middle Mouse", "MM")
	text = text:gsub("Backspace", "Bs")
	text = text:gsub("Spacebar", "Sp")
	text = text:gsub("Delete", "De")
	text = text:gsub("Home", "Ho")
	text = text:gsub("End", "En")
	text = text:gsub("Arrow", "")
	text = text:gsub("Insert", "Ins")
	text = text:gsub("Page Up", "Pu")
	text = text:gsub("Page Down", "Pd")
	text = text:gsub("Down ", "D")
	text = text:gsub("Up ", "U")
	text = text:gsub("Left ", "L")
	text = text:gsub("Right ", "R")
	
	return text
end

local function onEnterFunc(button)
	if not Bartender3.db.profile.HideTooltip then 
		button.class:SetTooltip() 
	end
end

local function onLeaveFunc(button)
	if not Bartender3.db.profile.HideTooltip then 
		button.updateTooltip = nil 
		GameTooltip:Hide()
	end
end

local function onAttributeChangedFunc(button)
	local self = button.class
	self:RefreshAction()
	self:UpdateButton()
end

local function onDragStartFunc(button)
	local self = button.class
	if ( not Bartender3.db.profile.ButtonLock or IsShiftKeyDown()) then
		PickupAction(self.action)
		self:UpdateButton()
		self:UpdateFlash()
	end
end

local function onUpdateFunc(button, elapsed) 
	local self = button.class
	self.elapsed = self.elapsed + elapsed
	if self.elapsed > 0.2 then
		self:OnUpdate(self.elapsed)
		self.elapsed = 0
	end
end

local function updateStateFunc(button)
	local self = button.class
	self:UpdateState()
end

function Bartender3.Class.Button.prototype:init(parent, id, button)
	Bartender3.Class.Button.super.prototype.init(self)
	self.parent = parent
	self.id = id
	self.rid = self.id - ( self.parent.id - 1 ) * 12
	
	self.showgrid = 0
	self.flashing = 0
	self.flashtime = 0
	self.outOfRange = false
	self.elapsed = 0
	self.action = 0
	
	self:CreateButtonFrame(button)
end

-- CreateButtonFrame will NOT anchor the button, you HAVE to do that.
function Bartender3.Class.Button.prototype:CreateButtonFrame(button)
	local name = button and button:GetName() or "BT3Button"..self.id
	self.frame = button or CreateFrame("CheckButton", name, self.parent.frame, "SecureActionButtonTemplate, ActionButtonTemplate")
	local frame = self.frame
	frame.class = self
	frame:RegisterForClicks("AnyUp")
	frame:RegisterForDrag("LeftButton", "RightButton")
	
	frame:SetScript("OnUpdate", onUpdateFunc)
	
	frame:SetScript("OnEnter", onEnterFunc)
	frame:SetScript("OnLeave", onLeaveFunc)
	frame:SetScript("OnAttributeChanged", onAttributeChangedFunc)
	frame:SetScript("OnDragStart", onDragStartFunc)
	
	frame:SetScript("PostClick", updateStateFunc)
	
	if button then -- clear blizz scripts and events
		frame:SetScript("OnEvent", nil)
		frame:SetScript("OnReceiveDrag", nil)
		
		frame:UnregisterAllEvents()
		-- register alias in global table
		_G[("BT3Button%d"):format(self.id)] = frame
	end
	
	frame.icon = _G[("%sIcon"):format(name)]
	frame.border = _G[("%sBorder"):format(name)]
	frame.cooldown = _G[("%sCooldown"):format(name)]
	frame.macroName = _G[("%sName"):format(name)]
	frame.hotkey = _G[("%sHotKey"):format(name)]
	frame.count = _G[("%sCount"):format(name)]
	frame.flash = _G[("%sFlash"):format(name)]
	frame.flash:Hide()
	
	--frame:SetPushedTexture("")
	frame:SetNormalTexture("")
	
	local oldNT = _G[("%sNormalTexture"):format(name)]
	oldNT:Hide()
	
	frame.normalTexture = frame:CreateTexture(("%sBTNT"):format(name))
	frame.normalTexture:SetAllPoints(oldNT)
	
	self.parent.frame:SetAttribute("addchild", frame)
	frame:SetAttribute("type", "action")
	frame:SetAttribute("action", self.id)
	frame:SetAttribute("useparent-unit", true)
	frame:SetAttribute("useparent-statebutton", true)
	
	self:SetStateAction(0, self.id)
	
	self:UpdateButton()
	
	self:RegisterBarEvents()
end

function Bartender3.Class.Button.prototype:SetStateAction(state, action)
	for i=1,2 do
		self.frame:SetAttribute(("*action-S%d%d"):format(state, i), action)
	end
end

function Bartender3.Class.Button.prototype:PagedID()
	if not self.action then self:RefreshAction() end
	return self.action
end

function Bartender3.Class.Button.prototype:RefreshAction()
	self.action = SecureButton_GetModifiedAttribute(self.frame, "action", SecureStateChild_GetEffectiveButton(self.frame)) or 1
end

function Bartender3.Class.Button.prototype:UpdateIcon()
	local frame = self.frame
	local texture = GetActionTexture(self.action)
	if ( texture ) then
		frame.icon:SetTexture(texture)
		frame.icon:Show()
		frame.normalTexture:SetTexture("Interface\\Buttons\\UI-Quickslot2")
		frame.normalTexture:SetTexCoord(0,0,0,0)
		frame.tex = texture
	else
		frame.icon:Hide()
		frame.cooldown:Hide()
		frame.normalTexture:SetTexture("Interface\\Buttons\\UI-Quickslot")
		frame.hotkey:SetVertexColor(0.6, 0.6, 0.6)
		frame.normalTexture:SetTexCoord(-0.1,1.1,-0.1,1.12)
		frame.tex = nil
	end
end

function Bartender3.Class.Button.prototype:UpdateButton()
	local frame = self.frame
	self:RefreshAction()
	self:UpdateIcon()
	self:UpdateCount()
	self:UpdateHotkeys()
	Bartender3:RefreshStyle(self.frame, self.parent)
	if ( HasAction(self.action) ) then
		self:RegisterButtonEvents()
		self:UpdateState()
		self:UpdateUsable()
		self:UpdateCooldown()
		self:UpdateFlash()
		self.frame:Enable()
		self.frame:SetScript("OnUpdate", onUpdateFunc)
	else
		self.frame:SetScript("OnUpdate", nil)
		self:UnregisterButtonEvents()
		
		if ( self.showgrid == 0 ) then
			if ( self.parent.config.ShowGrid == true ) then
				frame.normalTexture:Show()
				frame.cooldown:Hide()
			else
				frame.normalTexture:Hide()
				if frame.overlay then
					frame.overlay:Hide()
				end
			end
			self.frame:Disable()
		else
			frame.cooldown:Hide()
			frame.normalTexture:Show()
			if frame.overlay then
				frame.overlay:Show()
			end
		end
	end
	
	if ( IsEquippedAction(self.action) ) then
		frame.border:SetVertexColor(0, 1.0, 0, 0.75)
		frame.border:Show()
	else
		frame.border:Hide()
	end
	
	if ( GameTooltip:IsOwned(self.frame) ) then
		self:SetTooltip()
	else
		self.updateTooltip = nil
	end
	
	if self.parent.config.HideMacrotext then
		frame.macroName:SetText("")
	else
		frame.macroName:SetText(GetActionText(self.action))
	end
end

function Bartender3.Class.Button.prototype:UpdateHotkeys()
	local hotkey = self.frame.hotkey
	local oor = Bartender3.db.profile.OutOfRange or "none"
	local key1, key2 = GetBindingKey(((self.parent.id == 1) and "ACTIONBUTTON%d" or "BT3BUTTON%d"):format(self.id))
	local key = key1 or key2
	if ( GetBindingText(key, "KEY_", 1) == "" or self.parent.config.HideHotkey or not HasAction(self.action) ) then
		if ( HasAction(self.action) and oor == "hotkey" and ActionHasRange(self.action) ) then
			hotkey:SetText("°")
		else
			hotkey:SetText("")
		end
	else
		hotkey:SetText(ShortenKeyBinding(GetBindingText(key, "KEY_", 1)))
	end
end

function Bartender3.Class.Button.prototype:UpdateCount()
	if ( IsConsumableAction(self.action) ) then
		self.frame.count:SetText(GetActionCount(self.action))
	else
		self.frame.count:SetText("")
	end
end

function Bartender3.Class.Button.prototype:UpdateState()
	local actionID = self.action
	if ( IsCurrentAction(actionID) or IsAutoRepeatAction(actionID) ) then
		self.frame:SetChecked(1)
	else
		self.frame:SetChecked(0)
	end
	if ( self.showgrid == 0 and not HasAction(self.action) ) then
		if ( self.parent.config.ShowGrid ~= true ) then
			self.frame.normalTexture:Hide()
		end
	end
end

function Bartender3.Class.Button.prototype:UpdateUsable()
	local oor = Bartender3.db.profile.OutOfRange or "none"
	local frame = (oor == "button") and self.frame.icon or self.frame.hotkey
	local id = self.action
	local isUsable, notEnoughMana = IsUsableAction(id)
	if ( isUsable and (not self.outOfRange or not (oor ~= "none"))) then
		self.frame.icon:SetVertexColor(1.0, 1.0, 1.0)
		self.frame.hotkey:SetVertexColor(1.0, 1.0, 1.0)
	elseif ( (oor ~= "none") and self.outOfRange ) then
		if oor == "button" then
			self.frame.icon:SetVertexColor(0.8, 0.1, 0.1)
			self.frame.hotkey:SetVertexColor(1.0, 1.0, 1.0)
		else
			self.frame.hotkey:SetVertexColor(0.8, 0.1, 0.1)
			self.frame.icon:SetVertexColor(1.0, 1.0, 1.0)
		end
	elseif ( (oor ~= "none") and notEnoughMana ) then
		self.frame.icon:SetVertexColor(0.1, 0.3, 1.0)
	else
		self.frame.icon:SetVertexColor(0.4, 0.4, 0.4)
	end
end

function Bartender3.Class.Button.prototype:UpdateCooldown()
	local start, duration, enable = GetActionCooldown(self.action)
	if not self.parent.faded then
		CooldownFrame_SetTimer(self.frame.cooldown, start, duration, enable)
	end
end

function Bartender3.Class.Button.prototype:UpdateFlash()
	local pagedID = self.action
	if ( (IsAttackAction(pagedID) and IsCurrentAction(pagedID)) or IsAutoRepeatAction(pagedID) ) then
		self:StartFlash()
	else
		self:StopFlash()
	end
end

local ATTACK_BUTTON_FLASH_TIME = ATTACK_BUTTON_FLASH_TIME

function Bartender3.Class.Button.prototype:OnUpdate(elapsed)
	if not self.frame.tex then self:UpdateIcon() end
	
	if ( self.flashing == 1 ) then
		self.flashtime = self.flashtime - elapsed;
		if ( self.flashtime <= 0 ) then
			local overtime = -self.flashtime;
			if ( overtime >= ATTACK_BUTTON_FLASH_TIME ) then
				overtime = 0;
			end
			self.flashtime = ATTACK_BUTTON_FLASH_TIME - overtime;

			local flashTexture = self.frame.flash
			if ( flashTexture:IsVisible() ) then
				flashTexture:Hide()
			else
				flashTexture:Show()
			end
		end
	end
	
	if ( self.outOfRange ~= ( IsActionInRange( self.action ) == 0)) then
		self.outOfRange = not self.outOfRange
		self:UpdateUsable()
	end

	if ( not self.updateTooltip ) then
		return
	end

	self.updateTooltip = self.updateTooltip - elapsed
	if ( self.updateTooltip > 0 ) then
		return
	end

	if ( GameTooltip:IsOwned(self.frame) ) then
		self:SetTooltip()
	else
		self.updateTooltip = nil
	end
end

function Bartender3.Class.Button.prototype:SetTooltip()
	local frame = self.frame
	if ( GetCVar("UberTooltips") == "1" ) then
		GameTooltip_SetDefaultAnchor(GameTooltip, self.frame)
	else
		GameTooltip:SetOwner(frame, "ANCHOR_RIGHT")
	end
	
	if ( GameTooltip:SetAction(self.action) ) then
		self.updateTooltip = TOOLTIP_UPDATE_TIME
	else
		self.updateTooltip = nil
	end
end

function Bartender3.Class.Button.prototype:StartFlash()
	self.flashing = 1
	self.flashtime = 0
	self:UpdateState()
end

function Bartender3.Class.Button.prototype:StopFlash()
	self.flashing = 0
	self.frame.flash:Hide()
	self:UpdateState()
end

function Bartender3.Class.Button.prototype:ShowGrid(override)
	local button = self.frame
	if not override then self.showgrid = self.showgrid+1 end
	
	button.normalTexture:Show()
	if button.overlay then
		button.overlay:Show()
	end
	button:Enable()
end

function Bartender3.Class.Button.prototype:HideGrid(override)
	local button = self.frame
	if not override then self.showgrid = self.showgrid-1 end
	if ( self.showgrid == 0 and not HasAction(self.action) ) then
		if ( not self.parent.config.ShowGrid ) then
			button.normalTexture:Hide()
			if button.overlay then
				button.overlay:Hide()
			end
		end
		button:Disable()
	end
end

function Bartender3.Class.Button.prototype:ReleaseButton()
	local bt3button = Bartender3.actionbuttons[self.id]
	bt3button.state = "free"
	self.frame:Hide()
	self.frame:SetParent(UIParent)
end

function Bartender3.Class.Button.prototype:AssignButton(parent, id)
	self.frame:SetParent(parent.frame)
	parent.frame:SetAttribute("addchild", self.frame)
	self.parent = parent
	self.id = id
	self.frame:SetAttribute("action", id)
	self:SetStateAction(0, self.id)
	self.frame:Show()
end

function Bartender3.Class.Button.prototype:RegisterBarEvents()
	self:RegisterEvent("PLAYER_ENTERING_WORLD", "BaseEventHandler")
	self:RegisterEvent("ACTIONBAR_PAGE_CHANGED", "BaseEventHandler")
	self:RegisterEvent("ACTIONBAR_SLOT_CHANGED", "BaseEventHandler")
	self:RegisterEvent("UPDATE_BINDINGS", "BaseEventHandler")
	self:RegisterEvent("ACTIONBAR_SHOWGRID", "BaseEventHandler")
	self:RegisterEvent("ACTIONBAR_HIDEGRID", "BaseEventHandler")
	self:RegisterEvent("UPDATE_SHAPESHIFT_FORM", "BaseEventHandler")
end

function Bartender3.Class.Button.prototype:RegisterButtonEvents()
	if self.eventsregistered then return end
	self.eventsregistered = true
	self:RegisterEvent("PLAYER_TARGET_CHANGED", "ButtonEventHandler")
	self:RegisterEvent("PLAYER_AURAS_CHANGED", "ButtonEventHandler")
	self:RegisterEvent("ACTIONBAR_UPDATE_USABLE", "ButtonEventHandler")
	self:RegisterEvent("ACTIONBAR_UPDATE_COOLDOWN", "ButtonEventHandler")
	self:RegisterEvent("ACTIONBAR_UPDATE_STATE", "ButtonEventHandler")
	self:RegisterEvent("UPDATE_INVENTORY_ALERTS", "ButtonEventHandler")
	self:RegisterEvent("PLAYER_ENTER_COMBAT", "ButtonEventHandler")
	self:RegisterEvent("PLAYER_LEAVE_COMBAT", "ButtonEventHandler")
	self:RegisterEvent("START_AUTOREPEAT_SPELL", "ButtonEventHandler")
	self:RegisterEvent("STOP_AUTOREPEAT_SPELL", "ButtonEventHandler")
	self:RegisterEvent("CRAFT_SHOW", "ButtonEventHandler")
	self:RegisterEvent("CRAFT_CLOSE", "ButtonEventHandler")
	self:RegisterEvent("TRADE_SKILL_SHOW", "ButtonEventHandler")
	self:RegisterEvent("TRADE_SKILL_CLOSE", "ButtonEventHandler")
end

function Bartender3.Class.Button.prototype:UnregisterButtonEvents()
	if not self.eventsregistered then return end
	self.eventsregistered = nil
	self:UnregisterEvent("PLAYER_TARGET_CHANGED")
	self:UnregisterEvent("PLAYER_AURAS_CHANGED")
	self:UnregisterEvent("ACTIONBAR_UPDATE_USABLE")
	self:UnregisterEvent("ACTIONBAR_UPDATE_COOLDOWN")
	self:UnregisterEvent("ACTIONBAR_UPDATE_STATE")
	self:UnregisterEvent("UPDATE_INVENTORY_ALERTS")
	self:UnregisterEvent("PLAYER_ENTER_COMBAT")
	self:UnregisterEvent("PLAYER_LEAVE_COMBAT")
	self:UnregisterEvent("START_AUTOREPEAT_SPELL")
	self:UnregisterEvent("STOP_AUTOREPEAT_SPELL")
	self:UnregisterEvent("CRAFT_SHOW")
	self:UnregisterEvent("CRAFT_CLOSE")
	self:UnregisterEvent("TRADE_SKILL_SHOW")
	self:UnregisterEvent("TRADE_SKILL_CLOSE")
end

--[[
	Following Events are always set and will always be called - i call them the base events
]]
function Bartender3.Class.Button.prototype:BaseEventHandler(e)
	if not self.parent.config.Enabled or self.parent.config.Hide then return end
	local e = event
	
	if ( e == "PLAYER_ENTERING_WORLD" or e == "ACTIONBAR_PAGE_CHANGED") then
		self:UpdateButton()
	elseif ( e == "ACTIONBAR_SLOT_CHANGED" ) then
		local button = arg1
		if ( button == 0 or button == self.action ) then
			self:UpdateButton()
		end
	elseif ( e == "UPDATE_BINDINGS" ) then
		self:UpdateHotkeys()
	elseif ( e == "ACTIONBAR_SHOWGRID" ) then
		self:ShowGrid()
	elseif ( e == "ACTIONBAR_HIDEGRID" ) then
		self:HideGrid()
	elseif ( e == "UPDATE_SHAPESHIFT_FORM" ) then
		self:RefreshAction()
	end
end

--[[
	Following Events are only set when the Button in question has a valid action - i call them the button events
]]
function Bartender3.Class.Button.prototype:ButtonEventHandler(e)
	if not self.parent.config.Enabled or self.parent.config.Hide then return end
	local e = event
	local actionId = self.action
	
	if ( event == "PLAYER_TARGET_CHANGED" or event == "PLAYER_AURAS_CHANGED" ) then
		self:UpdateUsable()
		self:UpdateHotkeys()
	elseif ( event == "ACTIONBAR_UPDATE_USABLE" or event == "UPDATE_INVENTORY_ALERTS" or event == "ACTIONBAR_UPDATE_COOLDOWN" ) then
		self:UpdateUsable()
		self:UpdateCooldown()
	elseif ( event == "CRAFT_SHOW" or event == "CRAFT_CLOSE" or event == "TRADE_SKILL_SHOW" or event == "TRADE_SKILL_CLOSE" ) then
		self:UpdateState()
	elseif ( event == "ACTIONBAR_UPDATE_STATE" ) then
		self:UpdateState()
	elseif ( event == "PLAYER_ENTER_COMBAT" ) then
		if ( IsAttackAction(actionId) ) then
			self:StartFlash()
		end
	elseif ( event == "PLAYER_LEAVE_COMBAT" ) then
		if ( IsAttackAction(actionId) ) then
			self:StopFlash()
		end
	elseif ( event == "START_AUTOREPEAT_SPELL" ) then
		if ( IsAutoRepeatAction(actionId) ) then
			self:StartFlash()
		end
	elseif ( event == "STOP_AUTOREPEAT_SPELL" ) then
		if ( self.flashing == 1 and not IsAttackAction(actionId) ) then
			self:StopFlash()
		end
	end
end


