--[[
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 29243 2007-02-28 13:08:16Z nevcairiel $ ]]
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 = string.gsub(text, "CTRL--", "|c00FF9966C|r")
	text = string.gsub(text, "STRG--", "|c00CCCC00S|r")
	text = string.gsub(text, "ALT--", "|c009966CCA|r")
	text = string.gsub(text, "SHIFT--", "|c00CCCC00S|r")
	text = string.gsub(text, "Num Pad ", "NP")
	text = string.gsub(text, "Mouse Button ", "M")
	text = string.gsub(text, "Middle Mouse", "MM")
	text = string.gsub(text, "Backspace", "Bs")
	text = string.gsub(text, "Spacebar", "Sp")
	text = string.gsub(text, "Delete", "De")
	text = string.gsub(text, "Home", "Ho")
	text = string.gsub(text, "End", "En")
	text = string.gsub(text, "Arrow", "")
	text = string.gsub(text, "Insert", "Ins")
	text = string.gsub(text, "Page Up", "Pu")
	text = string.gsub(text, "Page Down", "Pd")
	text = string.gsub(text, "Down ", "D")
	text = string.gsub(text, "Up ", "U")
	text = string.gsub(text, "Left ", "L")
	text = string.gsub(text, "Right ", "R")
	
	return text
end

function Bartender3.Class.Button.prototype:init(parent, id)
	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:CreateButtonFrame()
end

-- CreateButtonFrame will NOT anchor the button, you HAVE to do that.
function Bartender3.Class.Button.prototype:CreateButtonFrame()
	local name = "BT3Button"..self.id
	self.frame = 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", function(bself, elapsed) 
		self.elapsed = self.elapsed + elapsed
		if self.elapsed > 0.2 then
			self:OnUpdate(self.elapsed)
			self.elapsed = 0
		end
	end )
	
	frame:SetScript("OnEnter", function() if not Bartender3.db.profile.HideTooltip then self:SetTooltip() end end)
	frame:SetScript("OnLeave", function() if not Bartender3.db.profile.HideTooltip then this.updateTooltip = nil; GameTooltip:Hide(); end end)
	frame:SetScript("OnAttributeChanged", function() self.cached_id = nil; self:UpdateButton() end)
	frame:SetScript("OnDragStart", function()
		if ( not Bartender3.db.profile.ButtonLock or IsShiftKeyDown()) then
			PickupAction(self:PagedID())
			self:UpdateButton()
			self:UpdateFlash()
		end
	end)
	--frame:SetScript("OnReceiveDrag", function()
--		self:UpdateButton()
--		self:UpdateFlash()
--	end)
	
	frame.icon = _G[name.."Icon"]
	frame.border = _G[name.."Border"]
	frame.cooldown = _G[name.."Cooldown"]
	frame.macroName = _G[name.."Name"]
	frame.hotkey = _G[name.."HotKey"]
	frame.count = _G[name.."Count"]
	frame.flash = _G[name.."Flash"]
	frame.flash:Hide()
	
	frame:SetPushedTexture("")
	frame:SetNormalTexture("")
	
	local oldNT = _G[name.."NormalTexture"]
	oldNT:Hide()
	
	frame.normalTexture = frame:CreateTexture(name .. "BTNT")
	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" .. state .. i, action)
	end
end

function Bartender3.Class.Button.prototype:PagedID()
	if not self.cached_id then
		self.cached_id = SecureButton_GetModifiedAttribute(self.frame, "action", SecureStateChild_GetEffectiveButton(self.frame)) or 1
	end
	return self.cached_id
end

function Bartender3.Class.Button.prototype:UpdateIcon()
	local frame = self.frame
	local texture = GetActionTexture(self:PagedID())
	if ( texture ) then
		frame.icon:SetTexture(texture)
		frame.icon:Show()
		frame.normalTexture:SetTexture("Interface\\Buttons\\UI-Quickslot2")
		frame.normalTexture:SetTexCoord(0,0,0,0)
	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)
	end
end

function Bartender3.Class.Button.prototype:UpdateButton()
	local frame = self.frame
	
	self:UpdateIcon()
	self:UpdateCount()
	self:UpdateHotkeys()
	Bartender3:RefreshStyle(self.frame, self.parent)
	if ( HasAction(self:PagedID()) ) then
		self:RegisterButtonEvents()
		self:UpdateState()
		self:UpdateUsable()
		self:UpdateCooldown()
		self:UpdateFlash()
		self.frame:Enable()
		self.rangeTimer = Bartender3.db.profile.OutOfRange and -1 or nil
	else
		self.rangeTimer = 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:PagedID()) ) 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:PagedID()))
	end
end

function Bartender3.Class.Button.prototype:UpdateHotkeys()
	local hotkey = self.frame.hotkey
	local key1, key2 = GetBindingKey(((self.parent.id == 1) and "ACTIONBUTTON" or "BT3BUTTON") .. self.id)
	local key = key1 or key2
	if ( GetBindingText(key, "KEY_", 1) == "" or self.parent.config.HideHotkey or ( not HasAction(self:PagedID()) ) ) then
		hotkey:SetText("")
	else
		hotkey:SetText(ShortenKeyBinding(GetBindingText(key, "KEY_", 1)))
	end
end

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

function Bartender3.Class.Button.prototype:UpdateState()
	local actionID = self:PagedID()
	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:PagedID()) ) 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
	local id = self:PagedID()
	local isUsable, notEnoughMana = IsUsableAction(id)
	if ( isUsable and (not self.outOfRange or not oor)) then
		self.frame.icon:SetVertexColor(1.0, 1.0, 1.0)
	elseif ( oor and self.outOfRange ) then
		self.frame.icon:SetVertexColor(0.8, 0.1, 0.1)
	elseif ( oor 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:PagedID())
	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:PagedID()
	if ( (IsAttackAction(pagedID) and IsCurrentAction(pagedID)) or IsAutoRepeatAction(pagedID) ) then
		self:StartFlash()
	else
		self:StopFlash()
	end
end

function Bartender3.Class.Button.prototype:OnUpdate(elapsed)
	if not self.parent.config.Enabled or self.parent.config.Hide then return end
	
	self:UpdateState()
	self:UpdateIcon()
	
	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.rangeTimer ) then
		self.rangeTimer = self.rangeTimer - elapsed
		if ( self.rangeTimer <= 0 and self.outOfRange ~= ( IsActionInRange( self:PagedID() ) == 0)) then
			self.outOfRange = not self.outOfRange
			self:UpdateUsable()
			self.rangeTimer = TOOLTIP_UPDATE_TIME
		end
	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:PagedID()) ) 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:PagedID()) ) 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("ACTIONBAR_UPDATE_USABLE", "ButtonEventHandler")
	self:RegisterEvent("ACTIONBAR_UPDATE_COOLDOWN", "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")
end

function Bartender3.Class.Button.prototype:UnregisterButtonEvents()
	if not self.eventsregistered then return end
	self.eventsregistered = nil
	self:UnregisterEvent("ACTIONBAR_UPDATE_USABLE")
	self:UnregisterEvent("ACTIONBAR_UPDATE_COOLDOWN")
	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")
end

--[[
	Following Events are always set and will always be called - i call them the BAR 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:PagedID() ) 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.cached_id = nil
	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
	
	if ( event == "ACTIONBAR_UPDATE_USABLE" or event == "UPDATE_INVENTORY_ALERTS" or event == "ACTIONBAR_UPDATE_COOLDOWN" ) then
		self:UpdateUsable()
		self:UpdateCooldown()
	elseif ( event == "PLAYER_ENTER_COMBAT" ) then
		if ( IsAttackAction(self:PagedID()) ) then
			self:StartFlash()
		end
	elseif ( event == "PLAYER_LEAVE_COMBAT" ) then
		if ( IsAttackAction(self:PagedID()) ) then
			self:StopFlash()
		end
	elseif ( event == "START_AUTOREPEAT_SPELL" ) then
		if ( IsAutoRepeatAction(self:PagedID()) ) then
			self:StartFlash()
		end
	elseif ( event == "STOP_AUTOREPEAT_SPELL" ) then
		if ( self.flashing == 1 and not IsAttackAction(self:PagedID()) ) then
			self:StopFlash()
		end
	end
end


