--[[
# Element: Health Prediction Bars

Handles the visibility and updating of incoming heals and heal/damage absorbs.

## Widget

HealthPrediction - A `table` containing references to sub-widgets and options.

## Sub-Widgets

healingAll                - A `StatusBar` used to represent incoming heals from all sources.
healingPlayer             - A `StatusBar` used to represent incoming heals from the player.
healingOther              - A `StatusBar` used to represent incoming heals from others.
overHealIndicator         - A `Texture` used to indicate that the incoming healing is greater than the configured limits.
damageAbsorb              - A `StatusBar` used to represent damage absorbs.
overDamageAbsorbIndicator - A `Texture` used to signify that the amount of damage absorb is greater than the configured limits.
healAbsorb                - A `StatusBar` used to represent heal absorbs.
overHealAbsorbIndicator   - A `Texture` used to signify that the amount of heal absorb is greater than the configured limits.

## Notes

A default texture will be applied to the StatusBar widgets if they don't have a texture set.
A default texture will be applied to the Texture widgets if they don't have a texture or a color set.

## Options

.damageAbsorbClampMode - Defines how damage absorbs should clamp. See [Enum.UnitDamageAbsorbClampMode](https://warcraft.wiki.gg/wiki/Enum.UnitDamageAbsorbClampMode).
.healAbsorbClampMode   - Defines how healing absorbs should clamp. See [Enum.UnitHealAbsorbClampMode](https://warcraft.wiki.gg/wiki/Enum.UnitHealAbsorbClampMode).
.healAbsorbMode        - Defines how healing absorbs should be treated. See [Enum.UnitHealAbsorbMode](https://warcraft.wiki.gg/wiki/Enum.UnitHealAbsorbMode).
.incomingHealClampMode - Defines how incoming healing should clamp. See [Enum.UnitIncomingHealClampMode](https://warcraft.wiki.gg/wiki/Enum.UnitIncomingHealClampMode).
.incomingHealOverflow  - The maximum amount of overflow past the end of the health bar. Set this to 1 to disable the overflow.
                         Defaults to 1.05 (number)

## Attributes

.values - A [unit heal prediction calculator](https://warcraft.wiki.gg/wiki/API_CreateUnitHealPredictionCalculator) used to calculate the values used in this element.

## Examples

This example does not contain all widgets or options, just a selection.

    -- Position and size
    local healingAll = CreateFrame('StatusBar', nil, self.Health)
    healingAll:SetPoint('TOP')
    healingAll:SetPoint('BOTTOM')
    healingAll:SetPoint('LEFT', self.Health:GetStatusBarTexture(), 'RIGHT')
    healingAll:SetWidth(200)
    healingAll:SetStatusBarTexture('Interface\\TargetingFrame\\UI-StatusBar')

    local damageAbsorb = CreateFrame('StatusBar', nil, self.Health)
    damageAbsorb:SetPoint('TOP')
    damageAbsorb:SetPoint('BOTTOM')
    damageAbsorb:SetPoint('LEFT', healingAll:GetStatusBarTexture(), 'RIGHT')
    damageAbsorb:SetWidth(200)

    local healAbsorb = CreateFrame('StatusBar', nil, self.Health)
    healAbsorb:SetPoint('TOP')
    healAbsorb:SetPoint('BOTTOM')
    healAbsorb:SetPoint('RIGHT', self.Health:GetStatusBarTexture())
    healAbsorb:SetWidth(200)
    healAbsorb:SetReverseFill(true)

    local overDamageAbsorbIndicator = self.Health:CreateTexture(nil, "OVERLAY")
    overDamageAbsorbIndicator:SetPoint('TOP')
    overDamageAbsorbIndicator:SetPoint('BOTTOM')
    overDamageAbsorbIndicator:SetPoint('LEFT', self.Health, 'RIGHT')
    overDamageAbsorbIndicator:SetWidth(10)

    local overHealAbsorbIndicator = self.Health:CreateTexture(nil, "OVERLAY")
    overHealAbsorbIndicator:SetPoint('TOP')
    overHealAbsorbIndicator:SetPoint('BOTTOM')
    overHealAbsorbIndicator:SetPoint('RIGHT', self.Health, 'LEFT')
    overHealAbsorbIndicator:SetWidth(10)

    -- Register with oUF
    self.HealthPrediction = {
        healingAll = healingAll,
        damageAbsorb = damageAbsorb,
        healAbsorb = healAbsorb,
        overDamageAbsorbIndicator = overDamageAbsorbIndicator,
        overHealAbsorbIndicator = overHealAbsorbIndicator,

        -- extra options
        incomingHealOverflow = 1.2,
    }
--]]

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

local function UpdateSize(self, event, unit)
	local element = self.HealthPrediction

	if(element.healingAll) then
		element.healingAll[element.isHoriz and 'SetWidth' or 'SetHeight'](element.healingAll, element.size)
	end

	if(element.healingPlayer) then
		element.healingPlayer[element.isHoriz and 'SetWidth' or 'SetHeight'](element.healingPlayer, element.size)
	end

	if(element.healingOther) then
		element.healingOther[element.isHoriz and 'SetWidth' or 'SetHeight'](element.healingOther, element.size)
	end

	if(element.damageAbsorb) then
		element.damageAbsorb[element.isHoriz and 'SetWidth' or 'SetHeight'](element.damageAbsorb, element.size)
	end

	if(element.healAbsorb) then
		element.healAbsorb[element.isHoriz and 'SetWidth' or 'SetHeight'](element.healAbsorb, element.size)
	end
end

local function Update(self, event, unit)
	if(self.unit ~= unit) then return end

	local element = self.HealthPrediction

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

	* self - the HealthPrediction element
	* unit - the unit for which the update has been triggered (string)
	--]]
	if(element.PreUpdate) then
		element:PreUpdate(unit)
	end

	local maxHealth = UnitHealthMax(unit)
	UnitGetDetailedHealPrediction(unit, 'player', element.values)

	local allHeal, playerHeal, otherHeal, healClamped = element.values:GetIncomingHeals()
	if(element.healingAll) then
		element.healingAll:SetMinMaxValues(0, maxHealth)
		element.healingAll:SetValue(allHeal)
	end
	if(element.healingPlayer) then
		element.healingPlayer:SetMinMaxValues(0, maxHealth)
		element.healingPlayer:SetValue(playerHeal)
	end
	if(element.healingOther) then
		element.healingOther:SetMinMaxValues(0, maxHealth)
		element.healingOther:SetValue(otherHeal)
	end
	if(element.overHealIndicator) then
		element.overHealIndicator:SetAlphaFromBoolean(healClamped, 1, 0)
	end

	local damageAbsorbAmount, damageAbsorbClamped = element.values:GetDamageAbsorbs()
	if(element.damageAbsorb) then
		element.damageAbsorb:SetMinMaxValues(0, maxHealth)
		element.damageAbsorb:SetValue(damageAbsorbAmount)
	end
	if(element.overDamageAbsorbIndicator) then
		element.overDamageAbsorbIndicator:SetAlphaFromBoolean(damageAbsorbClamped, 1, 0)
	end

	local healAbsorbAmount, healAbsorbClamped = element.values:GetHealAbsorbs()
	if(element.healAbsorb) then
		element.healAbsorb:SetMinMaxValues(0, maxHealth)
		element.healAbsorb:SetValue(healAbsorbAmount)
	end
	if(element.overHealAbsorbIndicator) then
		element.overHealAbsorbIndicator:SetAlphaFromBoolean(healAbsorbClamped, 1, 0)
	end

	--[[ Callback: HealthPrediction:PostUpdate(unit)
	Called after the element has been updated.

	* self - the HealthPrediction element
	* unit - the unit for which the update has been triggered (string)
	--]]
	if(element.PostUpdate) then
		return element:PostUpdate(unit)
	end
end

local function shouldUpdateSize(self)
	if(not self.Health) then return end

	local isHoriz = self.Health:GetOrientation() == 'HORIZONTAL'
	local newSize = self.Health[isHoriz and 'GetWidth' or 'GetHeight'](self.Health)
	if(isHoriz ~= self.HealthPrediction.isHoriz or newSize ~= self.HealthPrediction.size) then
		self.HealthPrediction.isHoriz = isHoriz
		self.HealthPrediction.size = newSize

		return true
	end
end

local function Path(self, ...)
	--[[ Override: HealthPrediction.UpdateSize(self, event, unit, ...)
	Used to completely override the internal function for updating the widgets' size.

	* self  - the parent object
	* event - the event triggering the update (string)
	* unit  - the unit accompanying the event (string)
	* ...   - the arguments accompanying the event
	--]]
	if(shouldUpdateSize(self)) then
		(self.HealthPrediction.UpdateSize or UpdateSize) (self, ...)
	end

	--[[ Override: HealthPrediction.Override(self, event, unit)
	Used to completely override the internal update function.

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

local function ForceUpdate(element)
	element.isHoriz = nil
	element.size = nil

	return Path(element.__owner, 'ForceUpdate', element.__owner.unit)
end

local function Enable(self)
	local element = self.HealthPrediction
	if(element) then
		element.__owner = self
		element.ForceUpdate = ForceUpdate

		if(element.values) then
			element.values:Reset()
		else
			element.values = CreateUnitHealPredictionCalculator()
		end

		if(element.damageAbsorbClampMode) then
			element.values:SetDamageAbsorbClampMode(element.damageAbsorbClampMode)
		end

		if(element.healAbsorbClampMode) then
			element.values:SetHealAbsorbClampMode(element.healAbsorbClampMode)
		end

		if(element.healAbsorbMode) then
			element.values:SetHealAbsorbMode(element.healAbsorbMode)
		end

		if(element.incomingHealClampMode) then
			element.values:SetIncomingHealClampMode(element.incomingHealClampMode)
		end

		if(element.incomingHealOverflow) then
			element.values:SetIncomingHealOverflowPercent(element.incomingHealOverflow)
		end

		self:RegisterEvent('UNIT_HEALTH', Path)
		self:RegisterEvent('UNIT_MAXHEALTH', Path)
		self:RegisterEvent('UNIT_HEAL_PREDICTION', Path)
		self:RegisterEvent('UNIT_ABSORB_AMOUNT_CHANGED', Path)
		self:RegisterEvent('UNIT_HEAL_ABSORB_AMOUNT_CHANGED', Path)
		self:RegisterEvent('UNIT_MAX_HEALTH_MODIFIERS_CHANGED', Path)

		if(element.healingAll) then
			if(element.healingAll:IsObjectType('StatusBar') and not element.healingAll:GetStatusBarTexture()) then
				element.healingAll:SetStatusBarTexture([[Interface\TargetingFrame\UI-StatusBar]])
			end
		end

		if(element.healingPlayer) then
			if(element.healingPlayer:IsObjectType('StatusBar') and not element.healingPlayer:GetStatusBarTexture()) then
				element.healingPlayer:SetStatusBarTexture([[Interface\TargetingFrame\UI-StatusBar]])
			end
		end

		if(element.healingOther) then
			if(element.healingOther:IsObjectType('StatusBar') and not element.healingOther:GetStatusBarTexture()) then
				element.healingOther:SetStatusBarTexture([[Interface\TargetingFrame\UI-StatusBar]])
			end
		end

		if(element.overHealIndicator) then
			if(element.overHealIndicator:IsObjectType('Texture') and not element.overHealIndicator:GetTexture()) then
				element.overHealIndicator:SetTexture([[Interface\RaidFrame\Shield-Overshield]])
				element.overHealIndicator:SetBlendMode('ADD')
			end
		end

		if(element.damageAbsorb) then
			if(element.damageAbsorb:IsObjectType('StatusBar') and not element.damageAbsorb:GetStatusBarTexture()) then
				element.damageAbsorb:SetStatusBarTexture([[Interface\TargetingFrame\UI-StatusBar]])
			end
		end

		if(element.healAbsorb) then
			if(element.healAbsorb:IsObjectType('StatusBar') and not element.healAbsorb:GetStatusBarTexture()) then
				element.healAbsorb:SetStatusBarTexture([[Interface\TargetingFrame\UI-StatusBar]])
			end
		end

		if(element.overDamageAbsorbIndicator) then
			if(element.overDamageAbsorbIndicator:IsObjectType('Texture') and not element.overDamageAbsorbIndicator:GetTexture()) then
				element.overDamageAbsorbIndicator:SetTexture([[Interface\RaidFrame\Shield-Overshield]])
				element.overDamageAbsorbIndicator:SetBlendMode('ADD')
			end
		end

		if(element.overHealAbsorbIndicator) then
			if(element.overHealAbsorbIndicator:IsObjectType('Texture') and not element.overHealAbsorbIndicator:GetTexture()) then
				element.overHealAbsorbIndicator:SetTexture([[Interface\RaidFrame\Absorb-Overabsorb]])
				element.overHealAbsorbIndicator:SetBlendMode('ADD')
			end
		end

		return true
	end
end

local function Disable(self)
	local element = self.HealthPrediction
	if(element) then
		if(element.healingAll) then
			element.healingAll:Hide()
		end

		if(element.healingPlayer) then
			element.healingPlayer:Hide()
		end

		if(element.healingOther) then
			element.healingOther:Hide()
		end

		if(element.overHealIndicator) then
			element.overHealIndicator:Hide()
		end

		if(element.damageAbsorb) then
			element.damageAbsorb:Hide()
		end

		if(element.healAbsorb) then
			element.healAbsorb:Hide()
		end

		if(element.overDamageAbsorbIndicator) then
			element.overDamageAbsorbIndicator:Hide()
		end

		if(element.overHealAbsorbIndicator) then
			element.overHealAbsorbIndicator:Hide()
		end

		self:UnregisterEvent('UNIT_HEALTH', Path)
		self:UnregisterEvent('UNIT_MAXHEALTH', Path)
		self:UnregisterEvent('UNIT_HEAL_PREDICTION', Path)
		self:UnregisterEvent('UNIT_ABSORB_AMOUNT_CHANGED', Path)
		self:UnregisterEvent('UNIT_HEAL_ABSORB_AMOUNT_CHANGED', Path)
		self:UnregisterEvent('UNIT_MAX_HEALTH_MODIFIERS_CHANGED', Path)
	end
end

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