--[[
# Element: Range Fader

Changes the opacity of a unit frame based on whether the frame's unit is in the player's range.

## Widget

Range - A table containing opacity values.

## Notes

Offline units are handled as if they are in range.

## Options

.outsideAlpha - Opacity when the unit is out of range. Defaults to 0.55 (number)[0-1].
.insideAlpha  - Opacity when the unit is within range. Defaults to 1 (number)[0-1].

## Examples

    -- Register with oUF
    self.Range = {
        insideAlpha = 1,
        outsideAlpha = 1/2,
    }
--]]

local _, ns = ...
local oUF = ns.oUF

local _FRAMES = {}
local OnRangeFrame

local next, tinsert, tremove = next, tinsert, tremove

local CreateFrame = CreateFrame
local UnitInRange = UnitInRange
local UnitInParty = UnitInParty
local UnitInRaid = UnitInRaid
local UnitIsConnected = UnitIsConnected

local function Update(self, event)
	local element = self.Range
	local unit = self.unit

	--[[ Callback: Range:PreUpdate()
	Called before the element has been updated.

	* self - the Range element
	--]]
	if(element.PreUpdate) then
		element:PreUpdate()
	end

	local inRange, wasChecked
	local connected = UnitIsConnected(unit)
	local isEligible = connected and (UnitInParty(unit) or UnitInRaid(unit))
	if(isEligible) then
		inRange, wasChecked = UnitInRange(unit)

		if oUF.isRetail then
			self:SetAlphaFromBoolean(inRange, element.insideAlpha, element.outsideAlpha)
		elseif(wasChecked and not inRange) then
			self:SetAlpha(element.outsideAlpha)
		else
			self:SetAlpha(element.insideAlpha)
		end
	else
		self:SetAlpha(element.insideAlpha)
	end

	--[[ Callback: Range:PostUpdate(object, inRange, isEligible)
	Called after the element has been updated.

	* self       - the Range element
	* object     - the parent object
	* inRange    - indicates if the unit is within 40 yards of the player (boolean)
	* isEligible - indicates if the unit is eligible for the range check (boolean)
	--]]
	if(element.PostUpdate) then
		return element:PostUpdate(self, inRange, isEligible, connected)
	end
end

local function Path(self, ...)
	--[[ Override: Range.Override(self, event)
	Used to completely override the internal update function.

	* self  - the parent object
	* event - the event triggering the update (string)
	--]]
	return (self.Range.Override or Update) (self, ...)
end

-- Internal updating method
local timer = 0
local function OnRangeUpdate(_, elapsed)
	timer = timer + elapsed

	if(timer >= .20) then
		for _, object in next, _FRAMES do
			if object:IsVisible() then
				Path(object, 'OnUpdate')
			end
		end

		timer = 0
	end
end

local function Enable(self)
	local element = self.Range
	if(element) then
		element.__owner = self
		element.insideAlpha = element.insideAlpha or 1
		element.outsideAlpha = element.outsideAlpha or 0.55

		if oUF.isRetail or oUF.isTBC then
			self:RegisterEvent('UNIT_IN_RANGE_UPDATE', Path)
		else
			if not OnRangeFrame then
				OnRangeFrame = CreateFrame('Frame')
				OnRangeFrame:SetScript('OnUpdate', OnRangeUpdate)
			end

			tinsert(_FRAMES, self)
			OnRangeFrame:Show()
		end

		return true
	end
end

local function Disable(self)
	local element = self.Range
	if(element) then
		self:SetAlpha(element.insideAlpha)

		if oUF.isRetail or oUF.isTBC then
			self:UnregisterEvent('UNIT_IN_RANGE_UPDATE', Path)
		else
			for index, frame in next, _FRAMES do
				if frame == self then
					tremove(_FRAMES, index)
					break
				end
			end

			if #_FRAMES == 0 then
				OnRangeFrame:Hide()
			end
		end
	end
end

oUF:AddElement('Range', Path, Enable, Disable)
