--====================================================================
-- CastCursor @ 2018 MiChaeL
--====================================================================

local addonName = ...

local addon = CreateFrame("Frame", "CastCursor", UIParent, "UIDropDownMenuTemplate")

--====================================================================

local UIParent = UIParent
local GetTime = GetTime
local UnitCastingInfo = UnitCastingInfo or CastingInfo
local UnitChannelInfo = UnitChannelInfo or ChannelInfo
local GetSpellCooldown = GetSpellCooldown
local GetCursorPosition = GetCursorPosition
local unpack, floor, cos, sin, max = unpack, floor, cos, sin, max

local isRetail = select(4, GetBuildInfo())>=20000

--====================================================================

local Defaults = {
	cast = {
		visible = true,
		radius = 25,
		sublayer = 1,
		thickness = 5,
		color = { 1, .7, 0, 1 },
		texture = [[Interface\Addons\CastCursor\media\ring1]],
	},
	gcd = {
		visible = true,
		radius = 20,
		sublayer = 0,
		thickness = 5,
		color = { .7, 1, 0, 1 },
		texture = [[Interface\Addons\CastCursor\media\ring1]],
	},
	minimapIcon = { hide = true },
}

--====================================================================

local QUAD_POINTS = {
	{ 'TOPLEFT',     'TOP'    },
	{ 'TOPRIGHT',    'RIGHT'  },
	{ 'BOTTOMRIGHT', 'BOTTOM' },
	{ 'BOTTOMLEFT',  'LEFT'   },
}

local QUAD_COORD_FULL = {
	{ 0,0, 0,1, 1,0, 1,1 },
	{ 0,1, 1,1, 0,0, 1,0 },
	{ 1,1, 1,0, 0,1, 0,0 },
	{ 1,0, 0,0, 1,1, 0,1 },
}

local QUAD_COORD_FUNC = {
	function(t, r, x1, x2, y1, y2) -- Quadrant1: TOPRIGHT
		t:SetTexCoord(x1,1-y2, x1,1-y1, x2,1-y2, x2,1-y1)
		t:SetSize(x2*r, (1-y1)*r)
	end,
	function(t, r, x1, x2, y1, y2) -- Quadrant2: BOTTOMRIGHT
		t:SetTexCoord(x1,1-y1, x2,1-y1, x1,1-y2, x2,1-y2) 
		t:SetSize((1-y1)*r, x2*r)
	end,
	function(t, r, x1, x2, y1, y2) -- Quadrant3: BOTTOMLEFT
		t:SetTexCoord(x2,1-y1, x2,1-y2, x1,1-y1, x1,1-y2)
		t:SetSize(x2*r, (1-y1)*r)
	end,
	function(t, r, x1, x2, y1, y2) -- Quadrant4: TOPLEFT
		t:SetTexCoord(x2,1-y2, x1,1-y2, x2,1-y1, x1,1-y1)
		t:SetSize((1-y1)*r, x2*r)
	end,
}

--====================================================================

local function OnEvent(self,event,...)
	self[event](self,event,...) 
end

local function Start(self, d, m)
	local textures = self.textures
	local quad = floor(4*d/m)
	for i=1,4 do
		local tex = textures[i]
		if i>quad then
			tex:SetTexCoord(0,0,1,1)
			tex:Hide()	
		else
			tex:SetTexCoord(unpack(QUAD_COORD_FULL[i]))
			tex:SetSize(self.radius, self.radius)
			tex:Show()
		end		
	end
	self.quad = quad
	self.dur  = max(d,0)
	self.max  = m
	self:Show()
end

local function Update(self, elapsed)
	local x, y = GetCursorPosition()
	self:ClearAllPoints()
	self:SetPoint( "CENTER", UIParent, "BOTTOMLEFT", x / self.scaleDivisor , y / self.scaleDivisor )

	local dur = self.dur + elapsed
	if dur>=self.max then self:Hide(); return end
	self.dur = dur
	
	local radius = self.radius
	local angle  = 360 * dur / self.max
	local qangle = angle % 90
	local quad   = floor(angle/90) + 1
	local tex    = self.textures[quad]
	local pquad  = self.quad
	if quad>pquad then
		if pquad>0 then
			local ptex = self.textures[pquad]
			ptex:SetTexCoord(unpack(QUAD_COORD_FULL[pquad]))
			ptex:SetSize(radius, radius)
		end	
		tex:Show()
		self.quad = quad
	end
	
	if qangle>0 then
		local f = qangle<=45 and self.factor or 1
		QUAD_COORD_FUNC[quad]( tex, radius, 0, sin(qangle)*f, cos(qangle)*f, 1 )
	end
end

local function Setup(frame)
	local cfg     = frame.db
	local radius  = cfg.radius
	local r,g,b,a = unpack(cfg.color)
	frame:SetAlpha(a or 1)
	frame:SetFrameStrata("TOOLTIP")
	frame:ClearAllPoints()
	frame:SetSize(radius*2, radius*2)
	frame.textures = frame.textures or {}
	local hide = not addon.testmode or not cfg.visible
	for i=1,4 do
		local tex = frame.textures[i] or frame:CreateTexture(nil, "OVERLAY")
		tex:ClearAllPoints()
		tex:SetDrawLayer("OVERLAY", cfg.sublayer or 0)
		tex:SetTexture(cfg.texture or [[Interface\Addons\CastCursor\ring.tga]])
		tex:SetVertexColor(r, g, b)
		tex:SetTexCoord(unpack(QUAD_COORD_FULL[i]))
		tex:SetSize(radius, radius)
		tex:SetPoint(QUAD_POINTS[i][1], frame, QUAD_POINTS[i][2])
		if hide then tex:Hide() end
		frame.textures[i] = tex 
	end
	frame.quad   = 0
	frame.radius = radius
	frame.factor = (radius-cfg.thickness)/radius
	frame.scaleDivisor = frame:GetScale() * UIParent:GetEffectiveScale()
	if not addon.testmode then
		frame:SetScript("OnEvent", cfg.visible and OnEvent or nil)	
		frame:SetScript("OnUpdate", Update)
		frame:Hide()
	end
	return frame
end

--====================================================================
-- Casting/Channeling Ring
--====================================================================

local Cast = CreateFrame("Frame", nil, UIParent)

Cast:RegisterUnitEvent("UNIT_SPELLCAST_START", "player")
Cast:RegisterUnitEvent("UNIT_SPELLCAST_DELAYED", "player")
function Cast:UNIT_SPELLCAST_START(event, unit)
	local name, _, _, start, finish, _, castID = UnitCastingInfo("player")
	if name then
		self.castID = castID
		Start(self, GetTime() - start/1000, (finish - start) / 1000 )
	else
		self:Hide()
	end
end
Cast.UNIT_SPELLCAST_DELAYED = Cast.UNIT_SPELLCAST_START

Cast:RegisterUnitEvent("UNIT_SPELLCAST_STOP", "player")
Cast:RegisterUnitEvent("UNIT_SPELLCAST_FAILED", "player")
Cast:RegisterUnitEvent("UNIT_SPELLCAST_INTERRUPTED", "player")
Cast:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_STOP", "player")
function Cast:UNIT_SPELLCAST_STOP(event, unit, castID)
	if castID == self.castID then
		self:Hide()
	end	
end
Cast.UNIT_SPELLCAST_FAILED = Cast.UNIT_SPELLCAST_STOP
Cast.UNIT_SPELLCAST_INTERRUPTED = Cast.UNIT_SPELLCAST_STOP
Cast.UNIT_SPELLCAST_CHANNEL_STOP = Cast.UNIT_SPELLCAST_STOP

Cast:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_START", "player")
Cast:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_UPDATE", "player")
function Cast:UNIT_SPELLCAST_CHANNEL_START(event, unit)
	local name, _, _, start, finish = UnitChannelInfo("player")
	if name then
		self.castID = nil
		Start(self, GetTime() - start/1000, (finish - start) / 1000 )
	else
		self:Hide()
	end
end
Cast.UNIT_SPELLCAST_CHANNEL_UPDATE = Cast.UNIT_SPELLCAST_CHANNEL_START

--====================================================================
-- GCD Ring
--====================================================================

local GCD = CreateFrame("Frame", nil, UIParent)

GCD:RegisterUnitEvent("UNIT_SPELLCAST_START", "player")
GCD:RegisterUnitEvent("UNIT_SPELLCAST_SUCCEEDED", "player")
function GCD:UNIT_SPELLCAST_START(event, unit, guid, spellID)
	local start, duration = GetSpellCooldown( isRetail and 61304 or spellID )
	if duration>0 and (isRetail or duration<=1.51) then
		Start(self, GetTime() - start, duration )
	end
end
GCD.UNIT_SPELLCAST_SUCCEEDED = GCD.UNIT_SPELLCAST_START

GCD:RegisterUnitEvent("UNIT_SPELLCAST_STOP", "player")
GCD:RegisterUnitEvent("UNIT_SPELLCAST_INTERRUPTED", "player")
function GCD:UNIT_SPELLCAST_STOP(event, unit, castID)
	self:Hide()
end
GCD.UNIT_SPELLCAST_INTERRUPTED = GCD.UNIT_SPELLCAST_STOP

--====================================================================
-- Run
--====================================================================

addon:RegisterEvent("ADDON_LOADED")
addon:SetScript("OnEvent", function(self, event, name)
	if name ~= addonName then return end
	CastCursorDB  = CastCursorDB or CopyTable(Defaults)
	self:UnregisterEvent("ADDON_LOADED")
	self:SetScript("OnEvent", nil)
	self:SetPoint("Center", UIParent, "Center")
	self:Hide()
	self.db       = CastCursorDB
	Cast.db       = self.db.cast
	GCD.db        = self.db.gcd
	self.Start    = Start
	self.Update   = Update
	self.Setup    = Setup
	self.Defaults = Defaults
	self.rings    = { cast = Cast, gcd = GCD }
	Setup(Cast)
	Setup(GCD)
	self:InitOptions()
end )

--==========================================================================
