Quantcast
local _, ns = ...
local oUF = ns.oUF or oUF
assert(oUF, 'oUF not loaded')

local cos, sin, sqrt2, max, atan2, floor = math.cos, math.sin, math.sqrt(2), math.max, math.atan2, math.floor;
local tinsert, tremove, tsort, twipe = table.insert, table.remove, table.sort, table.wipe;
local SuperVillain = SVUI[1]
local playerGUID = UnitGUID("player")
local _FRAMES, _PROXIMITY, OnUpdateFrame = {}, {}
local GPSMonitorFrame;

local function _calc(radius)
	return 0.5 + cos(radius) / sqrt2, 0.5 + sin(radius) / sqrt2;
end

local function spin(texture, angle)
	local LRx, LRy = _calc(angle + 0.785398163);
	local LLx, LLy = _calc(angle + 2.35619449);
	local ULx, ULy = _calc(angle + 3.92699082);
	local URx, URy = _calc(angle - 0.785398163);

	texture:SetTexCoord(ULx, ULy, LLx, LLy, URx, URy, LRx, LRy);
end

local sortFunc = function(a,b) return a[1] < b[1] end

local minThrottle = 0.02
local numArrows, inRange, unit, angle, GPS, distance
local Update = function(self, elapsed)
	if self.elapsed and self.elapsed > (self.throttle or minThrottle) then
		numArrows = 0
		twipe(_PROXIMITY)
		for _, object in next, _FRAMES do
			if(object:IsShown()) then
				GPS = object.GPS
				unit = object.unit
				if(unit) then
					if(GPS.PreUpdate) then GPS:PreUpdate(frame) end

					if unit and GPS.outOfRange then
						inRange = UnitInRange(unit)
					end

					local available = (GPS.OnlyProximity == false and GPS.onMouseOver == false)

					if(not unit or not (UnitInParty(unit) or UnitInRaid(unit)) or UnitIsUnit(unit, "player") or not UnitIsConnected(unit) or (not GPS.OnlyProximity and ((GPS.onMouseOver and (GetMouseFocus() ~= object)) or (GPS.outOfRange and inRange)))) then
						GPS:Hide()
					else
						distance, angle = SuperVillain:PositionFromPlayer(unit, available)
						if not angle then
							GPS:Hide()
						else
							if(GPS.OnlyProximity == false) then
								GPS:Show()
							else
								GPS:Hide()
							end

							if GPS.Arrow then
								if(distance > 40) then
									GPS.Arrow:SetVertexColor(1,0.1,0.1)
								elseif(distance > 30) then
									GPS.Arrow:SetVertexColor(0.4,0.8,0.1)
								else
									GPS.Arrow:SetVertexColor(0.1,1,0.1)
								end
								spin(GPS.Arrow, angle)
							end

							if GPS.Text then
								GPS.Text:SetText(floor(distance))
							end

							if(GPS.OnlyProximity and object.Health.percent and object.Health.percent < 80) then
								local value = object.Health.percent + distance
								_PROXIMITY[#_PROXIMITY + 1] = {value, GPS}
							end

							if(GPS.PostUpdate) then GPS:PostUpdate(frame, distance, angle) end
							numArrows = numArrows + 1
						end
					end
				else
					GPS:Hide()
				end
			end
		end

		tsort(_PROXIMITY, sortFunc)
        if(_PROXIMITY[1] and _PROXIMITY[1][2]) then
        	_PROXIMITY[1][2]:Show()
        end

		self.elapsed = 0
		self.throttle = max(minThrottle, 0.005 * numArrows)
	else
		self.elapsed = (self.elapsed or 0) + elapsed
	end
end

local Enable = function(self)
	local GPS = self.GPS
	if GPS then
		tinsert(_FRAMES, self)

		if not OnUpdateFrame then
			OnUpdateFrame = CreateFrame("Frame")
			OnUpdateFrame:SetScript("OnUpdate", Update)
		end

		OnUpdateFrame:Show()
		return true
	end
end

local Disable = function(self)
	local GPS = self.GPS
	if GPS then
		for k, frame in next, _FRAMES do
			if(frame == self) then
				tremove(_FRAMES, k)
				GPS:Hide()
				break
			end
		end

		if #_FRAMES == 0 and OnUpdateFrame then
			OnUpdateFrame:Hide()
		end
	end
end

oUF:AddElement('GPS', nil, Enable, Disable)