if WOW_PROJECT_ID == WOW_PROJECT_MAINLINE then return end

local major = "LibHealComm-4.0"
local minor = 113
assert(LibStub, format("%s requires LibStub.", major))

local HealComm = LibStub:NewLibrary(major, minor)
if( not HealComm ) then return end

local COMM_PREFIX = "LHC40"
C_ChatInfo.RegisterAddonMessagePrefix(COMM_PREFIX)

local bit = bit
local ceil = ceil
local error = error
local floor = floor
local format = format
local gsub = gsub
local max = max
local min = min
local pairs = pairs
local ipairs = ipairs
local rawset = rawset
local select = select
local setmetatable = setmetatable
local strlen = strlen
local strmatch = strmatch
local strsplit = strsplit
local strsub = strsub
local tinsert = tinsert
local tonumber = tonumber
local tremove = tremove
local type = type
local unpack = unpack
local wipe = wipe
local GetBuildInfo = GetBuildInfo
local HasActiveSeason = C_Seasons.HasActiveSeason
local GetActiveSeason = C_Seasons.GetActiveSeason

local Ambiguate = Ambiguate
local CastingInfo = CastingInfo
local ChannelInfo = ChannelInfo
local CreateFrame = CreateFrame
local GetInventoryItemLink = GetInventoryItemLink
local GetInventorySlotInfo = GetInventorySlotInfo
local GetNumGroupMembers = GetNumGroupMembers
local GetNumTalents = GetNumTalents
local GetNumTalentTabs = GetNumTalentTabs
local GetRaidRosterInfo = GetRaidRosterInfo
local GetSpellBonusHealing = GetSpellBonusHealing
local GetSpellCritChance = GetSpellCritChance
local GetSpellInfo = GetSpellInfo
local GetTalentInfo = GetTalentInfo
local GetTime = GetTime
local GetZonePVPInfo = GetZonePVPInfo
local hooksecurefunc = hooksecurefunc
local InCombatLockdown = InCombatLockdown
local IsEquippedItem = IsEquippedItem
local IsInGroup = IsInGroup
local IsInInstance = IsInInstance
local IsInRaid = IsInRaid
local IsLoggedIn = IsLoggedIn
local IsSpellInRange = IsSpellInRange
local SpellIsTargeting = SpellIsTargeting
local UnitAura = UnitAura
local UnitCanAssist = UnitCanAssist
local UnitExists = UnitExists
local UnitBuff = UnitBuff
local UnitGUID = UnitGUID
local UnitIsCharmed = UnitIsCharmed
local UnitIsVisible = UnitIsVisible
local UnitInRaid = UnitInRaid
local UnitLevel = UnitLevel
local UnitName = UnitName
local UnitPlayerControlled = UnitPlayerControlled
local CheckInteractDistance = CheckInteractDistance
local CombatLogGetCurrentEventInfo = CombatLogGetCurrentEventInfo
local UnitHasVehicleUI = UnitHasVehicleUI or function() end
local GetGlyphSocketInfo = GetGlyphSocketInfo or function() end
local GetNumGlyphSockets = GetNumGlyphSockets or function() return 0 end
local C_Engraving = C_Engraving

local MAX_RAID_MEMBERS = MAX_RAID_MEMBERS
local MAX_PARTY_MEMBERS = MAX_PARTY_MEMBERS
local COMBATLOG_OBJECT_AFFILIATION_MINE = COMBATLOG_OBJECT_AFFILIATION_MINE

local build = floor(select(4, GetBuildInfo()) / 10000)
local isTBC = build == 2
local isWrath = build == 3
local isClassicEra = build == 1
-- https://warcraft.wiki.gg/wiki/Enum.SeasonID
local isSoD = HasActiveSeason() and GetActiveSeason() == (Enum.SeasonID.SeasonOfDiscovery or Enum.SeasonID.Placeholder) and isClassicEra

local spellRankTableData = {
	[1] = { 774, 8936, 5185, 740, 635, 19750, 139, 2060, 596, 2061, 2054, 2050, 1064, 331, 8004, 136, 755, 689, 746, 33763, 32546, 37563, 48438, 61295, 51945, 50464, 47757, 408120, 408124, 402277, 415236, 415240, 412510, 401417, 417057, 425268, 436937 },
	[2] = { 1058, 8938, 5186, 8918, 639, 19939, 6074, 10963, 996, 9472, 2055, 2052, 10622, 332, 8008, 3111, 3698, 699, 1159, 53248, 61299, 51990, 48450, 52986, 48119, 417058, 425269, 436938 },
	[3] = { 1430, 8939, 5187, 9862, 647, 19940, 6075, 10964, 10960, 9473, 6063, 2053, 10623, 547, 8010, 3661, 3699, 709, 3267, 53249, 61300, 51997, 48451, 52987, 48120, 417059, 425270, 436939 },
	[4] = { 2090, 8940, 5188, 9863, 1026, 19941, 6076, 10965, 10961, 9474, 6064, 913, 10466, 3662, 3700, 7651, 3268, 25422, 53251, 61301, 51998, 52988, 417060, 425271, 436940 },
	[5] = { 2091, 8941, 5189, 1042, 19942, 6077, 22009, 25314, 25316, 10915, 939, 10467, 13542, 11693, 11699, 7926, 25423, 26983, 51999, 417061, 425272, 436942 },
	[6] = { 3627, 9750, 6778, 3472, 19943, 6078, 10916, 959, 10468, 13543, 11694, 11700, 7927, 23569, 24412, 25210, 25308, 52000, 55458, 48446, 417062, 425273, 436943 },
	[7] = { 8910, 9856, 8903, 10328, 10927, 10917, 8005, 13544, 11695, 10838, 27137, 25213, 25420, 27219, 55459, 48447, 48072, 417063, 425274, 436944 },
	[8] = { 9839, 9857, 9758, 10329, 10928, 10395, 10839, 23568, 24413, 25233, 27259, 27220, 27046, 48784, 49275, 48062, 417064, 425275, 436945 },
	[9] = { 9840, 9858, 9888, 25292, 10929, 10396, 18608, 25235, 48785, 49276, 48063, 48989, 47856, 47857, 417065, 425276, 436946  },
	[10] = { 9841, 9889, 25315, 25357, 18610, 23567, 24414, 26980, 27135, 48070, 48990, 417066, 425277 },
	[11] = { 25299, 25297, 30020, 27136, 25221, 25391, 27030, 48442, 48071, 417068 },
	[12] = { 26981, 26978, 25222, 25396, 27031, 48781, 48443 },
	[13] = { 26982, 26979, 48782, 49272, 48067, 45543 },
	[14] = { 49273, 48377, 48440, 48068, 51827 },
	[15] = { 48378, 48441, 45544 },
	[16] = { 51803 },
}

local SpellIDToRank = {}
for rankIndex, spellIDTable in pairs(spellRankTableData) do
	for _, spellID in pairs(spellIDTable) do
		SpellIDToRank[spellID] = rankIndex
	end
end

-- API CONSTANTS
local ALL_DATA = 0x0f
local DIRECT_HEALS = 0x01
local CHANNEL_HEALS = 0x02
local HOT_HEALS = 0x04
local ABSORB_SHIELDS = 0x08
local BOMB_HEALS = 0x10
local ALL_HEALS = bit.bor(DIRECT_HEALS, CHANNEL_HEALS, HOT_HEALS, BOMB_HEALS)
local CASTED_HEALS = bit.bor(DIRECT_HEALS, CHANNEL_HEALS)
local OVERTIME_HEALS = bit.bor(HOT_HEALS, CHANNEL_HEALS)
local OVERTIME_AND_BOMB_HEALS = bit.bor(HOT_HEALS, CHANNEL_HEALS, BOMB_HEALS)

HealComm.ALL_HEALS, HealComm.OVERTIME_HEALS, HealComm.OVERTIME_AND_BOMB_HEALS, HealComm.CHANNEL_HEALS, HealComm.DIRECT_HEALS, HealComm.HOT_HEALS, HealComm.CASTED_HEALS, HealComm.ABSORB_SHIELDS, HealComm.ALL_DATA, HealComm.BOMB_HEALS = ALL_HEALS, OVERTIME_HEALS, OVERTIME_AND_BOMB_HEALS, CHANNEL_HEALS, DIRECT_HEALS, HOT_HEALS, CASTED_HEALS, ABSORB_SHIELDS, ALL_DATA, BOMB_HEALS

local playerGUID, playerName, playerLevel
local playerHealModifier = 1

HealComm.callbacks = HealComm.callbacks or LibStub:GetLibrary("CallbackHandler-1.0"):New(HealComm)
HealComm.activeHots = HealComm.activeHots or {}
HealComm.activePets = HealComm.activePets or {}
HealComm.equippedSetCache = HealComm.equippedSetCache or {}
HealComm.guidToGroup = HealComm.guidToGroup or {}
HealComm.guidToUnit = HealComm.guidToUnit or {}
HealComm.hotData = HealComm.hotData or {}
HealComm.itemSetsData = HealComm.itemSetsData or {}
HealComm.pendingHeals = HealComm.pendingHeals or {}
HealComm.pendingHots = HealComm.pendingHots or {}
HealComm.spellData = HealComm.spellData or {}
HealComm.talentData = HealComm.talentData or {}
HealComm.tempPlayerList = HealComm.tempPlayerList or {}
HealComm.glyphCache = HealComm.glyphCache or {}

if( not HealComm.unitToPet ) then
	HealComm.unitToPet = {["player"] = "pet"}
	for i = 1, MAX_PARTY_MEMBERS do HealComm.unitToPet["party" .. i] = "partypet" .. i end
	for i = 1, MAX_RAID_MEMBERS do HealComm.unitToPet["raid" .. i] = "raidpet" .. i end
end

local spellData, hotData, tempPlayerList, pendingHeals, pendingHots = HealComm.spellData, HealComm.hotData, HealComm.tempPlayerList, HealComm.pendingHeals, HealComm.pendingHots
local equippedSetCache, itemSetsData, talentData = HealComm.equippedSetCache, HealComm.itemSetsData, HealComm.talentData
local activeHots, activePets = HealComm.activeHots, HealComm.activePets

-- Figure out what they are now since a few things change based off of this
local playerClass = select(2, UnitClass("player"))

if( not HealComm.compressGUID  ) then
	HealComm.compressGUID = setmetatable({}, {
		__index = function(tbl, guid)
			local str
			if strsub(guid, 1, 6) ~= "Player" then
				for unit, pguid in pairs(activePets) do
					if pguid == guid and UnitExists(unit) then
						str = "p-" .. strmatch(UnitGUID(unit), "^%w*-([-%w]*)$")
					end
				end
				if not str then
					-- assert(str, "Could not encode: " .. guid)
					return nil
				end
			else
				str = strmatch(guid, "^%w*-([-%w]*)$")
			end
			rawset(tbl, guid, str)
			return str
		end})

	HealComm.decompressGUID = setmetatable({}, {
		__index = function(tbl, str)
			if( not str ) then return nil end
			local guid
			if strsub(str, 1, 2) == "p-" then
				local unit = HealComm.guidToUnit["Player-" .. strsub(str, 3)]
				if not unit then
					return nil
				end
				guid = activePets[unit]
			else
				guid = "Player-" .. str
			end

			rawset(tbl, str, guid)
			return guid
		end})
end

local compressGUID, decompressGUID = HealComm.compressGUID, HealComm.decompressGUID

-- Handles caching of tables for variable tick spells, like Wild Growth
if( not HealComm.tableCache ) then
	HealComm.tableCache = setmetatable({}, {__mode = "k"})
	function HealComm:RetrieveTable()
		return tremove(HealComm.tableCache, 1) or {}
	end

	function HealComm:DeleteTable(tbl)
		wipe(tbl)
		tinsert(HealComm.tableCache, tbl)
	end
end

-- Validation for passed arguments
if( not HealComm.tooltip ) then
	local tooltip = CreateFrame("GameTooltip")
	tooltip:SetOwner(UIParent, "ANCHOR_NONE")
	tooltip.TextLeft1 = tooltip:CreateFontString()
	tooltip.TextRight1 = tooltip:CreateFontString()
	tooltip:AddFontStrings(tooltip.TextLeft1, tooltip.TextRight1)

	HealComm.tooltip = tooltip
end

-- Record management, because this is getting more complicted to deal with
local function updateRecord(pending, guid, amount, stack, endTime, ticksLeft)
	if( pending[guid] ) then
		local id = pending[guid]

		pending[id] = guid
		pending[id + 1] = amount
		pending[id + 2] = stack
		pending[id + 3] = endTime or 0
		pending[id + 4] = ticksLeft or 0
	else
		pending[guid] = #(pending) + 1
		tinsert(pending, guid)
		tinsert(pending, amount)
		tinsert(pending, stack)
		tinsert(pending, endTime or 0)
		tinsert(pending, ticksLeft or 0)

		if( pending.bitType == HOT_HEALS ) then
			activeHots[guid] = (activeHots[guid] or 0) + 1
			HealComm.hotMonitor:Show()
		end
	end
end

local function getRecord(pending, guid)
	local id = pending[guid]
	if( not id ) then return nil end

	-- amount, stack, endTime, ticksLeft
	return pending[id + 1], pending[id + 2], pending[id + 3], pending[id + 4]
end

local function removeRecord(pending, guid)
	local id = pending[guid]
	if( not id ) then return nil end

	-- ticksLeft, endTime, stack, amount, guid
	tremove(pending, id + 4)
	tremove(pending, id + 3)
	tremove(pending, id + 2)
	local amount = tremove(pending, id + 1)
	tremove(pending, id)
	pending[guid] = nil

	-- Release the table
	if( type(amount) == "table" ) then HealComm:DeleteTable(amount) end

	if( pending.bitType == HOT_HEALS and activeHots[guid] ) then
		activeHots[guid] = activeHots[guid] - 1
		activeHots[guid] = activeHots[guid] > 0 and activeHots[guid] or nil
	end

	-- Shift any records after this ones index down 5 to account for the removal
	for i = 1, #(pending), 5 do
		local guid = pending[i]
		if( pending[guid] > id ) then
			pending[guid] = pending[guid] - 5
		end
	end
end

local function removeRecordList(pending, inc, comp, ...)
	for i = 1, select("#", ...), inc do
		local guid = select(i, ...)
		guid = comp and decompressGUID[guid] or guid

		if guid then
			local id = pending[guid]
			-- ticksLeft, endTime, stack, amount, guid
			tremove(pending, id + 4)
			tremove(pending, id + 3)
			tremove(pending, id + 2)
			local amount = tremove(pending, id + 1)
			tremove(pending, id)
			pending[guid] = nil

			-- Release the table
			if( type(amount) == "table" ) then HealComm:DeleteTable(amount) end
		end
	end

	-- Redo all the id maps
	for i = 1, #(pending), 5 do
		pending[pending[i]] = i
	end
end

-- Removes every mention to the given GUID
local function removeAllRecords(guid)
	local changed

	for _, tbl in pairs({pendingHeals, pendingHots}) do
		for _, spells in pairs(tbl) do
			for _, pending in pairs(spells) do
				if( pending.bitType and pending[guid] ) then
					local id = pending[guid]

					-- ticksLeft, endTime, stack, amount, guid
					tremove(pending, id + 4)
					tremove(pending, id + 3)
					tremove(pending, id + 2)
					local amount = tremove(pending, id + 1)
					tremove(pending, id)
					pending[guid] = nil

					-- Release the table
					if( type(amount) == "table" ) then HealComm:DeleteTable(amount) end

					-- Shift everything back
					if( #(pending) > 0 ) then
						for i = 1, #(pending), 5 do
							local guid = pending[i]
							if( pending[guid] > id ) then
								pending[guid] = pending[guid] - 5
							end
						end
					else
						wipe(pending)
					end

					changed = true
				end
			end
		end
	end

	activeHots[guid] = nil

	if( changed ) then
		HealComm.callbacks:Fire("HealComm_GUIDDisappeared", guid)
	end
end

-- These are not public APIs and are purely for the wrapper to use
HealComm.removeRecordList = removeRecordList
HealComm.removeRecord = removeRecord
HealComm.getRecord = getRecord
HealComm.updateRecord = updateRecord

-- Removes all pending heals, if it's a group that is causing the clear then we won't remove the players heals on themselves
local function clearPendingHeals()
	for _, tbl in pairs({pendingHeals, pendingHots}) do
		for casterGUID, spells in pairs(tbl) do
			for _, pending in pairs(spells) do
				if( pending.bitType ) then
					wipe(tempPlayerList)
					for i=#(pending), 1, -5 do tinsert(tempPlayerList, pending[i - 4]) end

					if( #(tempPlayerList) > 0 ) then
						local spellID, bitType = pending.spellID, pending.bitType
						wipe(pending)

						HealComm.callbacks:Fire("HealComm_HealStopped", casterGUID, spellID, bitType, true, unpack(tempPlayerList))
					end
				end
			end
		end
	end
end

-- APIs
-- Returns the players current heaing modifier
function HealComm:GetPlayerHealingMod()
	return playerHealModifier or 1
end

-- Returns the current healing modifier for the GUID
function HealComm:GetHealModifier(guid)
	return HealComm.currentModifiers[guid] or 1
end

-- Returns whether or not the GUID has casted a heal
function HealComm:GUIDHasHealed(guid)
	return (pendingHeals[guid] or pendingHots[guid]) and true or nil
end

-- Returns the guid to unit table
function HealComm:GetGUIDUnitMapTable()
	if( not HealComm.protectedMap ) then
		HealComm.protectedMap = setmetatable({}, {
			__index = function(tbl, key) return HealComm.guidToUnit[key] end,
			__newindex = function() error("This is a read only table and cannot be modified.", 2) end,
			__metatable = false
		})
	end

	return HealComm.protectedMap
end


-- Custom function for HealBarsClassic that returns heals within a timeframe
function HealComm:GetTimeframeHealAmount(guid, bitFlag, startTime, time, ignoreGUID, srcGUID)

	local healFrom, healTime
	local healAmount = 0
	local currentTime = startTime or GetTime()

	if startTime and time and (startTime > time) then return end

	for _, tbl in pairs({pendingHeals, pendingHots}) do
		for casterGUID, spells in pairs(tbl) do
			if( not ignoreGUID or ignoreGUID ~= casterGUID ) and (not srcGUID or srcGUID == casterGUID) then
				for _, pending in pairs(spells) do
					if( pending.bitType and bit.band(pending.bitType, bitFlag) > 0 ) then
						for i = 1, #(pending), 5 do
							local targetGUID = pending[i]
							if( not guid or targetGUID == guid ) then
								local amount = pending[i + 1]
								local stack = pending[i + 2]
								local endTime = pending[i + 3]
								endTime = endTime > 0 and endTime or pending.endTime
								-- Direct heals are easy, if they match the filter then return them
								if( ( pending.bitType == DIRECT_HEALS or pending.bitType == BOMB_HEALS ) and ( not time or not startTime
										or (startTime <= endTime and endTime <= time) ) ) then
									if not healTime or (endTime < healTime) then
										healTime = endTime
										healFrom = casterGUID
										healAmount = healAmount + (amount * stack)
									end
									-- Channeled heals and hots, have to figure out how many times it'll tick within the given time band
								elseif( ( pending.bitType == CHANNEL_HEALS or pending.bitType == HOT_HEALS ) ) then
									local ticksLeft = pending[i + 4]
									local secondsLeft = endTime - currentTime
									local bandSeconds = time - currentTime
									local ticks = floor(min(bandSeconds, secondsLeft) / pending.tickInterval)
									local nextTickIn = secondsLeft % pending.tickInterval
									local fractionalBand = bandSeconds % pending.tickInterval
									if( nextTickIn > 0 and nextTickIn < fractionalBand ) then
										ticks = ticks + 1
									end

									healAmount = healAmount + (amount * stack) * min(ticks, ticksLeft)
								end


							end
						end
					end
				end
			end
		end
	end
	return healAmount, healFrom, healTime
end

-- Gets the next heal landing on someone using the passed filters
function HealComm:GetNextHealAmount(guid, bitFlag, time, ignoreGUID, srcGUID)
	local healTime, healAmount, healFrom
	local currentTime = GetTime()

	for _, tbl in pairs({pendingHeals, pendingHots}) do
		for casterGUID, spells in pairs(tbl) do
			if( not ignoreGUID or ignoreGUID ~= casterGUID ) and (not srcGUID or srcGUID == casterGUID) then
				for _, pending in pairs(spells) do
					if( pending.bitType and bit.band(pending.bitType, bitFlag) > 0 ) then
						for i = 1, #(pending), 5 do
							local targetGUID = pending[i]
							if( not guid or targetGUID == guid ) then
								local amount = pending[i + 1]
								local stack = pending[i + 2]
								local endTime = pending[i + 3]
								endTime = endTime > 0 and endTime or pending.endTime

								-- Direct heals are easy, if they match the filter then return them
								if( ( pending.bitType == DIRECT_HEALS or pending.bitType == BOMB_HEALS ) and ( not time or endTime <= time ) ) then
									if( not healTime or endTime < healTime ) then
										healTime = endTime
										healAmount = amount * stack
										healFrom = casterGUID
									end

									-- Channeled heals and hots, have to figure out how many times it'll tick within the given time band
								elseif( ( pending.bitType == CHANNEL_HEALS or pending.bitType == HOT_HEALS ) ) then
									local secondsLeft = time and time - currentTime or endTime - currentTime
									local nextTick = currentTime + (secondsLeft % pending.tickInterval)
									if( not healTime or nextTick < healTime ) then
										healTime = nextTick
										if pending.hasVariableTicks then
											healAmount = amount[pending.totalTicks - pending[i + 4] + 1]
										else
											healAmount = amount * stack
										end
										healFrom = casterGUID
									end
								end
							end
						end
					end
				end
			end
		end
	end

	return healTime, healFrom, healAmount
end

-- Get the healing amount that matches the passed filters
local function filterData(spells, filterGUID, bitFlag, time, ignoreGUID)
	local healAmount = 0
	local currentTime = GetTime()

	if spells then
		for _, pending in pairs(spells) do
			if( pending.bitType and bit.band(pending.bitType, bitFlag) > 0 ) then
				for i = 1, #(pending), 5 do
					local guid = pending[i]
					if( guid == filterGUID or ignoreGUID ) then
						local amount = pending[i + 1]
						local stack = pending[i + 2]
						local endTime = pending[i + 3]
						endTime = endTime > 0 and endTime or pending.endTime

						if( ( pending.bitType == DIRECT_HEALS or pending.bitType == BOMB_HEALS ) and ( not time or endTime <= time ) ) then
							healAmount = healAmount + amount * stack
						elseif( ( pending.bitType == CHANNEL_HEALS or pending.bitType == HOT_HEALS ) and endTime > currentTime ) then
							local ticksLeft = pending[i + 4]
							if( not time or time >= endTime ) then
								if( not pending.hasVariableTicks ) then
									healAmount = healAmount + (amount * stack) * ticksLeft
								else
									local ticksPassed = pending.totalTicks - ticksLeft
									for numTick, heal in pairs(amount) do
										if numTick > ticksPassed then
											healAmount = healAmount + (heal * stack)
										end
									end
								end
							else
								local secondsLeft = endTime - currentTime
								local bandSeconds = time - currentTime
								local ticks = floor(min(bandSeconds, secondsLeft) / pending.tickInterval)
								local nextTickIn = secondsLeft % pending.tickInterval
								local fractionalBand = bandSeconds % pending.tickInterval
								if( nextTickIn > 0 and nextTickIn < fractionalBand ) then
									ticks = ticks + 1
								end
								if( not pending.hasVariableTicks ) then
									healAmount = healAmount + (amount * stack) * min(ticks, ticksLeft)
								else
									local ticksPassed = pending.totalTicks - ticksLeft
									for numTick, heal in ipairs(amount) do
										if numTick > ticksPassed then
											healAmount = healAmount + (heal * stack)
										end
									end
								end
							end
						end
					end
				end
			end
		end
	end

	return healAmount
end

-- Gets healing amount using the passed filters
function HealComm:GetHealAmount(guid, bitFlag, time, casterGUID)
	local amount = 0
	if( casterGUID and (pendingHeals[casterGUID] or pendingHots[casterGUID]) ) then
		amount = filterData(pendingHeals[casterGUID], guid, bitFlag, time) + filterData(pendingHots[casterGUID], guid, bitFlag, time)
	elseif( not casterGUID ) then
		for _, tbl in pairs({pendingHeals, pendingHots}) do
			for _, spells in pairs(tbl) do
				amount = amount + filterData(spells, guid, bitFlag, time)
			end
		end
	end

	return amount > 0 and amount or nil
end

-- Gets healing amounts for everyone except the player using the passed filters
function HealComm:GetOthersHealAmount(guid, bitFlag, time)
	local amount = 0
	for _, tbl in pairs({pendingHeals, pendingHots}) do
		for casterGUID, spells in pairs(tbl) do
			if( casterGUID ~= playerGUID ) then
				amount = amount + filterData(spells, guid, bitFlag, time)
			end
		end
	end

	return amount > 0 and amount or nil
end

function HealComm:GetCasterHealAmount(guid, bitFlag, time)
	local amount = pendingHeals[guid] and filterData(pendingHeals[guid], nil, bitFlag, time, true) or 0
	amount = amount + (pendingHots[guid] and filterData(pendingHots[guid], nil, bitFlag, time, true) or 0)
	return amount > 0 and amount or nil
end

function HealComm:GetHealAmountEx(dstGUID, dstBitFlag, dstTime, srcGUID, srcBitFlag, srcTime)
	local dstAmount1 = 0
	local dstAmount2 = 0
	local srcAmount1 = 0
	local srcAmount2 = 0

	local currTime = GetTime()

	dstBitFlag = dstBitFlag or ALL_HEALS
	srcBitFlag = srcBitFlag or ALL_HEALS

	for _, tbl in ipairs({pendingHeals, pendingHots}) do
		for casterGUID, spells in pairs(tbl) do
			local time

			if casterGUID ~= srcGUID then
				time = dstTime
			else
				time = srcTime
			end

			if spells then
				for _, pending in pairs(spells) do
					local bitType = pending.bitType or 0

					if casterGUID ~= srcGUID then
						bitType = bit.band(bitType, dstBitFlag)
					else
						bitType = bit.band(bitType, srcBitFlag)
					end

					if bitType > 0 then
						for i = 1, #pending, 5 do
							local targetGUID = pending[i]

							if targetGUID == dstGUID then
								local amount = 0
								if not pending.hasVariableTicks then
									amount = pending[i + 1]
								end
								local stack = pending[i + 2]
								local endTime = pending[i + 3]

								endTime = endTime > 0 and endTime or pending.endTime

								if endTime > currTime then
									amount = amount * stack

									local amount1 = 0
									local amount2 = 0

									if bitType == DIRECT_HEALS or bitType == BOMB_HEALS then
										if not time or endTime <= time then
											amount1 = amount
										end

										amount2 = amount
									elseif bitType == HOT_HEALS or bitType == CHANNEL_HEALS then
										local ticksLeft = pending[i + 4]
										local ticks

										if not time then
											ticks = 1
										elseif time >= endTime then
											ticks = ticksLeft
										else
											local tickInterval = pending.tickInterval
											local secondsLeft = endTime - currTime
											local bandSeconds = max(time - currTime, 0)

											ticks = floor(min(bandSeconds, secondsLeft) / tickInterval)

											local nextTickIn = secondsLeft % tickInterval
											local fractionalBand = bandSeconds % tickInterval

											if nextTickIn > 0 and nextTickIn < fractionalBand then
												ticks = ticks + 1
											end
										end

										if ticks > ticksLeft then
											ticks = ticksLeft
										end
										if not pending.hasVariableTicks then
											amount1 = amount * ticks
											amount2 = amount * ticksLeft
										else
											amount = pending[i + 1]
											amount1 = amount[pending.totalTicks - pending[i + 4] + 1] or 0
											local ticksPassed = pending.totalTicks - ticksLeft
											for numTick, heal in ipairs(amount) do
												if numTick > ticksPassed then
													amount2 = amount2 + (heal * stack)
												end
											end
										end
									end

									if casterGUID ~= srcGUID then
										dstAmount1 = dstAmount1 + amount1
										dstAmount2 = dstAmount2 + amount2
									else
										srcAmount1 = srcAmount1 + amount1
										srcAmount2 = srcAmount2 + amount2
									end
								end
							end
						end
					end
				end
			end
		end
	end

	dstAmount2 = dstAmount2 - dstAmount1
	srcAmount2 = srcAmount2 - srcAmount1

	dstAmount1 = dstAmount1 > 0 and dstAmount1 or nil
	dstAmount2 = dstAmount2 > 0 and dstAmount2 or nil
	srcAmount1 = srcAmount1 > 0 and srcAmount1 or nil
	srcAmount2 = srcAmount2 > 0 and srcAmount2 or nil

	return dstAmount1, dstAmount2, srcAmount1, srcAmount2
end

-- Get the number of direct heals on a target
function HealComm:GetNumHeals(filterGUID, time)
	local numHeals = 0

	for _, spells in pairs(pendingHeals) do
		if spells then
			for _, pending in pairs(spells) do
				for i = 1, #(pending), 5 do
					local guid = pending[i]
					if( guid == filterGUID ) then
						local endTime = pending[i + 3]
						endTime = endTime > 0 and endTime or pending.endTime

						if( pending.bitType == DIRECT_HEALS and ( not time or endTime <= time ) ) then
							numHeals = numHeals + 1
						end
					end
				end
			end
		end
	end

	return numHeals
end

-- Healing class data
-- Thanks to Gagorian (DrDamage) for letting me steal his formulas and such
local playerCurrentRelic
local guidToUnit, guidToGroup, glyphCache = HealComm.guidToUnit, HealComm.guidToGroup, HealComm.glyphCache

local unitHasAura

do
	local findAura = AuraUtil.FindAura
	local findAuraByName = AuraUtil.FindAuraByName

	local function spellIdPredicate(spellIdToFind, _, _, _, _, _, _, _, _, _, _, _, spellId)
		return spellIdToFind == spellId
	end

	local function findAuraBySpellId(spellId, unit, filter)
		return findAura(spellIdPredicate, unit, filter, spellId)
	end

	function unitHasAura(unit, name)
		if type(name) == "number" then
			return findAuraBySpellId(name, unit)
		else
			return findAuraByName(name, unit)
		end
	end
end

-- Note because I always forget on the order:
-- Talents that effective the coeffiency of spell power to healing are first and are tacked directly onto the coeffiency (Empowered Rejuvenation)
-- Penalty modifiers (downranking/spell level too low) are applied directly to the spell power
-- Spell power modifiers are then applied to the spell power
-- Heal modifiers are applied after all of that
-- Crit modifiers are applied after
-- Any other modifiers such as Mortal Strike or Avenging Wrath are applied after everything else

local function calculateGeneralAmount(level, amount, spellPower, spModifier, healModifier)
	-- SOD runes have no spell level and are treated as playerLevel
	level = level or isSoD and playerLevel
	local penalty = level > 20 and 1 or (1 - ((20 - level) * 0.0375))
	if isWrath then
		-- https://wowwiki-archive.fandom.com/wiki/Downranking
		penalty = min(1, max(0, (22 + (level + 5) - playerLevel) / 20))
	elseif isTBC then
		-- TBC added another downrank penalty
		penalty = penalty * min(1, (level + 11) / playerLevel)
	end
	spellPower = spellPower * penalty

	return healModifier * (amount + (spellPower * spModifier))
end

local function DirectCoefficient(castTime)
	return castTime / 3.5
end

local function HotCoefficient(duration)
	return duration / 15
end

local function avg(a, b)
	return (a + b) / 2
end

--[[
	What the different callbacks do:

	AuraHandler: Specific aura tracking needed for this class, who has Beacon up on them and such

	ResetChargeData: Due to spell "queuing" you can't always rely on aura data for buffs that last one or two casts, for example Divine Favor (+100% crit, one spell)
	if you cast Holy Light and queue Flash of Light the library would still see they have Divine Favor and give them crits on both spells. The reset means that the flag that indicates
	they have the aura can be killed and if they interrupt the cast then it will call this and let you reset the flags.

	What happens in terms of what the client thinks and what actually is, is something like this:

	UNIT_SPELLCAST_START, Holy Light -> Divine Favor up
	UNIT_SPELLCAST_SUCCEEDED, Holy Light -> Divine Favor up (But it was really used)
	UNIT_SPELLCAST_START, Flash of Light -> Divine Favor up (It's not actually up but auras didn't update)
	UNIT_AURA -> Divine Favor up (Split second where it still thinks it's up)
	UNIT_AURA -> Divine Favor faded (Client catches up and realizes it's down)

	CalculateHealing: Calculates the healing value, does all the formula calculations talent modifiers and such

	CalculateHotHealing: Used specifically for calculating the heals of hots

	GetHealTargets: Who the heal is going to hit, used for setting extra targets for Beacon of Light + Paladin heal or Prayer of Healing.
	The returns should either be:

	"compressedGUID1,compressedGUID2,compressedGUID3,compressedGUID4", healthAmount
	Or if you need to set specific healing values for one GUID it should be
	"compressedGUID1,healthAmount1,compressedGUID2,healAmount2,compressedGUID3,healAmount3", -1

	The latter is for cases like Glyph of Healing Wave where you need a heal for 1,000 on A and a heal for 200 on the player for B without sending 2 events.
	The -1 tells the library to look in the GUId list for the heal amounts

	**NOTE** Any GUID returned from GetHealTargets must be compressed through a call to compressGUID[guid]
]]

local CalculateHealing, GetHealTargets, AuraHandler, CalculateHotHealing, ResetChargeData, LoadClassData

local function getBaseHealAmount(spellData, spellName, spellID, spellRank)
	if spellID == 37563 then
		spellData = spellData["37563"]
	else
		spellData = spellData[spellName]
	end
	local average = spellData.averages[spellRank]
	if type(average) == "number" then
		return average
	end
	-- SOD runes have no spell level and are treated as playerLevel
	local requiresLevel = spellData.levels[spellRank] or isSoD and 1
	return average[min(playerLevel - requiresLevel + 1, #average)]
end

-- Values for SOD have been taken from https://github.com/jezzi23/stat_weights_classic
local function generateSODAverages(baseAmount, levelCoeff, levelScaling, levelScalingSquared)
	local averages = {}
	for lvl = 1, 60 do
		averages[lvl] = levelCoeff * (baseAmount + levelScaling * lvl + levelScalingSquared * lvl * lvl)
	end
	return {averages}
end

if( playerClass == "DRUID" ) then
	LoadClassData = function()
		local GiftofNature = GetSpellInfo(17104)
		local HealingTouch = GetSpellInfo(5185)
		local ImprovedRejuv = GetSpellInfo(17111)
		local MarkoftheWild = GetSpellInfo(1126)
		local Regrowth = GetSpellInfo(8936)
		local Rejuvenation = GetSpellInfo(774)
		local Tranquility = GetSpellInfo(740)
		local Lifebloom = GetSpellInfo(33763) or GetSpellInfo(408124) or "Lifebloom"
		local EmpoweredRejuv = GetSpellInfo(33886) or "Empowered Rejuvenation"
		local EmpoweredTouch = GetSpellInfo(33879) or "Empowered Touch"
		local WildGrowth = GetSpellInfo(48438) or GetSpellInfo(408120) or "Wild Growth"
		local Nourish = GetSpellInfo(50464) or "Nourish"
		local MasterShapeshifter = GetSpellInfo(48411) or "Master Shapeshifter"
		local Genesis = GetSpellInfo(57810) or "Genesis"
		local NaturesSplendor = GetSpellInfo(57865) or "Natures Splendor"
		local TreeofLife = GetSpellInfo(33891) or "Tree of Life"

		hotData[Regrowth] = { interval = 3, ticks = 7, coeff = (isTBC or isWrath) and 0.7 or 0.5, levels = { 12, 18, 24, 30, 36, 42, 48, 54, 60, 65, 71, 77 }, averages = { 98, 175, 259, 343, 427, 546, 686, 861, 1064, 1274, 1792, 2345 }}
		hotData[Rejuvenation] = { interval = 3, levels = { 4, 10, 16, 22, 28, 34, 40, 46, 52, 58, 60, 63, 69 }, averages = { 32, 56, 116, 180, 244, 304, 388, 488, 608, 756, 888, 932, 1060 }}
		if isWrath then
			hotData[Rejuvenation] = { interval = 3, levels = { 4, 10, 16, 22, 28, 34, 40, 46, 52, 58, 60, 63, 69, 75, 80 }, averages = { 40, 70, 145, 225, 305, 380, 485, 610, 760, 945, 1110, 1165, 1325, 1490, 1690 }}
			hotData[Lifebloom] = {interval = 1, ticks = 7, coeff = 0.66626, dhCoeff = 0.517928287, levels = {64, 72, 80}, averages = {224, 287, 371}, bomb = {480, 616, 776}}
			hotData[WildGrowth] = {interval = 1, ticks = 7, coeff = 0.8056, levels = {60, 70, 75, 80}, averages = {686, 861, 1239, 1442}}
		elseif isTBC then
			hotData[Lifebloom] = {interval = 1, ticks = 7, coeff = 0.52, dhCoeff = 0.34335, levels = {64}, averages = {273}, bomb = {600}}
		elseif isSoD then
			hotData[Lifebloom] = {interval = 1, ticks = 7, coeff = 7 * 0.051, dhCoeff = 0.274, levels = {nil}, averages = generateSODAverages(38.949830, 0.04 * 7, 0.606705, 0.167780), bomb = generateSODAverages(38.949830, 0.57, 0.606705, 0.167780)}
			hotData[WildGrowth] = {interval = 1, ticks = 7, coeff = 7 * 0.061, levels = {nil}, averages = generateSODAverages(38.949830, 0.34 * 7 , 0.606705, 0.167780)}
		end
		if isWrath then
			spellData[HealingTouch] = { levels = {1, 8, 14, 20, 26, 32, 38, 44, 50, 56, 60, 62, 69, 74, 79}, averages = {
				{avg(37, 51), avg(37, 52), avg(38, 53), avg(39, 54), avg(40, 55)},
				{avg(88, 112), avg(89, 114), avg(90, 115), avg(91, 116), avg(93, 118), avg(94, 119)},
				{avg(195, 243), avg(196, 245), avg(198, 247), avg(200, 249), avg(202, 251), avg(204, 253)},
				{avg(363, 445), avg(365, 448), avg(368, 451), avg(371, 454), avg(373, 456), avg(376, 459)},
				{avg(490, 594), avg(493, 597), avg(496, 600), avg(499, 603), avg(502, 606), avg(505, 609)},
				{avg(636, 766), avg(639, 770), avg(642, 773), avg(646, 777), avg(649, 780), avg(653, 783)},
				{avg(802, 960), avg(805, 964), avg(809, 968), avg(813, 972), avg(817, 976), avg(821, 980)},
				{avg(1199, 1427), avg(1203, 1432), avg(1208, 1436), avg(1212, 1441), avg(1217, 1445), avg(1221, 1450)},
				{avg(1299, 1539), avg(1304, 1545), avg(1309, 1550), avg(1314, 1555), avg(1319, 1560), avg(1324, 1565)},
				{avg(1620, 1912), avg(1625, 1918), avg(1631, 1924), avg(1637, 1930), avg(1642, 1935), avg(1648, 1941)},
				{avg(1944, 2294), avg(1950, 2301), avg(1956, 2307), avg(1962, 2313), avg(1968, 2319), avg(1975, 2325)},
				{avg(2026, 2392), avg(2032, 2399), avg(2038, 2405), avg(2044, 2411), avg(2051, 2418), avg(2057, 2424)},
				{avg(2321, 2739), avg(2328, 2746), avg(2335, 2753), avg(2342, 2760), avg(2349, 2767)},
				{avg(3223, 3805), avg(3232, 3815), avg(3242, 3825), avg(3252, 3835), avg(3262, 3845)},
				{avg(3750, 4428), avg(3761, 4440)} }}
		else
			spellData[HealingTouch] = { levels = {1, 8, 14, 20, 26, 32, 38, 44, 50, 56, 60, 62, 69}, averages = {
				{avg(37, 51), avg(37, 52), avg(38, 53), avg(39, 54), avg(40, 55)},
				{avg(88, 112), avg(89, 114), avg(90, 115), avg(91, 116), avg(93, 118), avg(94, 119)},
				{avg(195, 243), avg(196, 245), avg(198, 247), avg(200, 249), avg(202, 251), avg(204, 253)},
				{avg(363, 445), avg(365, 448), avg(368, 451), avg(371, 454), avg(373, 456), avg(376, 459)},
				{avg(572, 694), avg(575, 698), avg(579, 701), avg(582, 705), avg(586, 708), avg(589, 712)},
				{avg(742, 894), avg(746, 898), avg(750, 902), avg(754, 906), avg(758, 910), avg(762, 914)},
				{avg(936, 1120), avg(940, 1125), avg(945, 1129), avg(949, 1134), avg(954, 1138), avg(958, 1143)},
				{avg(1199, 1427), avg(1204, 1433), avg(1209, 1438), avg(1214, 1443), avg(1219, 1448), avg(1225, 1453)},
				{avg(1516, 1796), avg(1521, 1802), avg(1527, 1808), avg(1533, 1814), avg(1539, 1820), avg(1545, 1826)},
				{avg(1890, 2230), avg(1896, 2237), avg(1903, 2244), avg(1909, 2250), avg(1916, 2257), avg(1923, 2263)},
				{avg(2267, 2677), avg(2274, 2685), avg(2281, 2692), avg(2288, 2699), avg(2296, 2707), avg(2303, 2714)},
				{avg(2364, 2790), avg(2371, 2798), avg(2378, 2805), avg(2386, 2813), avg(2393, 2820), avg(2401, 2827)},
				{avg(2707, 3197), avg(2715, 3206)} }}
		end
		spellData[Regrowth] = {coeff = (isWrath and 0.6 or 0.5) * (2 / 3.5) , levels = hotData[Regrowth].levels, averages = {
			{avg(84, 98), avg(85, 100), avg(87, 102), avg(89, 104), avg(91, 106), avg(93, 107)},
			{avg(164, 188), avg(166, 191), avg(169, 193), avg(171, 196), avg(174, 198), avg(176, 201)},
			{avg(240, 274), avg(243, 278), avg(246, 281), avg(249, 284), avg(252, 287), avg(255, 290)},
			{avg(318, 360), avg(321, 364), avg(325, 368), avg(328, 371), avg(332, 375), avg(336, 378)},
			{avg(405, 457), avg(409, 462), avg(413, 466), avg(417, 470), avg(421, 474), avg(425, 478)},
			{avg(511, 575), avg(515, 580), avg(520, 585), avg(525, 590), avg(529, 594), avg(534, 599)},
			{avg(646, 724), avg(651, 730), avg(656, 735), avg(661, 740), avg(667, 746), avg(672, 751)},
			{avg(809, 905), avg(815, 911), avg(821, 917), avg(827, 923), avg(833, 929), avg(839, 935)},
			{avg(1003, 1119), avg(1009, 1126), avg(1016, 1133), avg(1023, 1140), avg(1030, 1147), avg(1037, 1153)},
			{avg(1215, 1355), avg(1222, 1363), avg(1230, 1371), avg(1238, 1379), avg(1245, 1386), avg(1253, 1394)},
			{avg(1710, 1908), avg(1720, 1919), avg(1731, 1930), avg(1742, 1941), avg(1753, 1952), avg(1764, 1962)},
			{avg(2234, 2494), avg(2241, 2502), avg(2249, 2510), avg(2257, 2518)} }}
		if isTBC or isWrath then
			spellData[Tranquility] = {_isChanneled = true, coeff = 1.145, ticks = 4, interval = 2, levels = {30, 40, 50, 60, 70, 75, 80}, averages = {
				{351 * 4, 354 * 4, 356 * 4, 358 * 4, 360 * 4, 362 * 4, 365 * 4},
				{515 * 4, 518 * 4, 521 * 4, 523 * 4, 526 * 4, 528 * 4, 531 * 4},
				{765 * 4, 769 * 4, 772 * 4, 776 * 4, 779 * 4, 782 * 4, 786 * 4},
				{1097 * 4, 1101 * 4, 1105 * 4, 1109 * 4, 1112 * 4, 1116 * 4, 1120 * 4},
				{1518 * 4, 1523 * 4, 1527 * 4, 1532 * 4, 1536 * 4},
				{2598 * 4, 2606 * 4, 2614 * 4, 2622 * 4, 2629 * 4},
				{3035 * 4} }}
		else
			spellData[Tranquility] = {_isChanneled = true, coeff = 1/3, ticks = 5, interval = 2, levels = {30, 40, 50, 60}, averages = {
				{94 * 5, 95 * 5, 96 * 5, 96 * 5, 97 * 5, 97 * 5, 98 * 5},
				{138 * 5, 139 * 5, 140 * 5, 141 * 5, 141 * 5, 142 * 5, 143 * 5},
				{205 * 5, 206 * 5, 207 * 5, 208 * 5, 209 * 5, 210 * 5, 211 * 5},
				{294 * 5} }}
		end
		if isSoD then
			spellData[Nourish] = {coeff = 0.357, levels = {nil}, averages = generateSODAverages(38.949830, avg(1.61, 1.89), 0.606705, 0.167780)}
		elseif isWrath then
			spellData[Nourish] = {coeff = 0.358005, levels = {80}, averages = {avg(1883, 2187)}}
		end

		talentData[GiftofNature] = {mod = 0.02, current = 0, spent = 0}
		talentData[ImprovedRejuv] = {mod = 0.05, current = 0, spent = 0}
		talentData[EmpoweredRejuv] = {mod = 0.04, current = 0, spent = 0}
		talentData[EmpoweredTouch] = {mod = 0.1, current = 0, spent = 0}
		talentData[Genesis] = {mod = 0.01, current = 0, spent = 0}
		talentData[NaturesSplendor] = {mod = 1, current = 0, spent = 0}
		talentData[MasterShapeshifter] = {mod = 0.02, current = 0, spent = 0}

		itemSetsData["Stormrage"] = {16903, 16898, 16904, 16897, 16900, 16899, 16901, 16902}
		itemSetsData["Nordrassil"] = {30216, 30217, 30219, 30220, 30221}
		itemSetsData["Thunderheart"] = {31041, 31032, 31037, 31045, 31047, 34571, 34445, 34554}
		itemSetsData["Dreamwalker"] = {40460, 40461, 40462, 40463, 40465, 39531, 39538, 39539, 39542, 39543}
		itemSetsData["Lasherweave"] = {50106, 50107, 50108, 50109, 50113, 51139, 51138, 51137, 51136, 51135, 51300, 51301, 51302, 51303, 51304}

		local bloomBombIdols = {[28355] = 87, [33076] = 105, [33841] = 116, [35021] = 131, [42576] = 188, [42577] = 217, [42578] = 246, [42579] = 294, [42580] = 376, [51423] = 448}
		local rejuIdols = {[186054] = 15, [22398] = 50, [25643] = 86, [38366] = 33}
		local bloomIdols = {[40711] = 125, [27886] = 47}

		local hotTotals, hasRegrowth = {}, {}
		AuraHandler = function(unit, guid)
			hotTotals[guid] = 0
			if( unitHasAura(unit, Rejuvenation) ) then hotTotals[guid] = hotTotals[guid] + 1 end
			if( unitHasAura(unit, Lifebloom) ) then hotTotals[guid] = hotTotals[guid] + 1 end
			if( unitHasAura(unit, WildGrowth) ) then hotTotals[guid] = hotTotals[guid] + 1 end
			if( unitHasAura(unit, Regrowth) ) then
				hasRegrowth[guid] = true
				hotTotals[guid] = hotTotals[guid] + 1
			else
				hasRegrowth[guid] = nil
			end
		end

		GetHealTargets = function(bitType, guid, spellID)
			-- Tranquility pulses on everyone within 30 yards, if they are in range of Mark of the Wild they'll get Tranquility
			local spellName = GetSpellInfo(spellID)
			if( spellName == Tranquility ) then
				local targets = compressGUID[playerGUID]
				local playerGroup = guidToGroup[playerGUID]

				for groupGUID, id in pairs(guidToGroup) do
					if( id == playerGroup and playerGUID ~= groupGUID and not UnitHasVehicleUI(guidToUnit[groupID]) and IsSpellInRange(MarkoftheWild, guidToUnit[groupGUID]) == 1 ) then
						targets = targets .. "," .. compressGUID[groupGUID]
					end
				end
				return targets
			end

			return compressGUID[guid]
		end

		-- Calculate hot heals
		local wgTicks = {}
		CalculateHotHealing = function(guid, spellID)
			-- Treat Druid T10 Rejuvenation like a rank 15 Rejuvenation
			if spellID == 70691 then
				spellID = 48441
			end

			local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
			local healAmount = getBaseHealAmount(hotData, spellName, spellID, spellRank)
			local spellPower = GetSpellBonusHealing()
			local healModifier, spModifier = playerHealModifier, 1
			local bombAmount, totalTicks

			-- Gift of Nature
			if isTBC or isWrath then
				healModifier = healModifier * (1 + talentData[GiftofNature].current)
			else
				-- Gift of Nature does only apply to base values in classic
				healAmount = healAmount + talentData[GiftofNature].current
			end

			if( unitHasAura("player", TreeofLife) ) then
				-- 32387 - Idol of the Raven Godess, +44 SP while in TOL
				if( playerCurrentRelic == 32387 ) then
					spellPower = spellPower + 44
				end
			end

			-- Rejuvenation
			if( spellName == Rejuvenation ) then
				if isTBC or isWrath then
					healModifier = healModifier + talentData[ImprovedRejuv].current
				else
					-- Improved Rejuvenation only applies to base values in classic
					healAmount = healAmount * (1 + talentData[ImprovedRejuv].current)
				end

				if( playerCurrentRelic and rejuIdols[playerCurrentRelic] ) then
					spellPower = spellPower + rejuIdols[playerCurrentRelic]
				end

				local duration = isWrath and 15 or 12
				local ticks = duration / hotData[spellName].interval

				if( equippedSetCache["Stormrage"] >= 8 ) then
					healAmount = healAmount + (healAmount / ticks) -- Add Tick Amount Gained by Set.
					duration = duration + 3
					ticks = ticks + 1
				end

				totalTicks = ticks

				spellPower = spellPower * (((duration / 15) * (isWrath and 1.88 or 1)) * (1 + talentData[EmpoweredRejuv].current))
				spellPower = spellPower / ticks
				healAmount = healAmount / ticks
			elseif( spellName == Regrowth ) then
				spellPower = spellPower * hotData[spellName].coeff * (isWrath and 1.88 or 1 ) * (1 + talentData[EmpoweredRejuv].current)
				spellPower = spellPower / hotData[spellName].ticks
				healAmount = healAmount / hotData[spellName].ticks

				totalTicks = 7

				if( talentData[NaturesSplendor].current >= 1 ) then totalTicks = totalTicks + 2 end
				if( equippedSetCache["Nordrassil"] >= 2 ) then totalTicks = totalTicks + 2 end

			elseif( spellName == Lifebloom ) then
				-- Figure out the bomb heal, apparently Gift of Nature double dips and will heal 10% for the HOT + 10% again for the direct heal
				local bombSpellPower = spellPower
				if( playerCurrentRelic and bloomBombIdols[playerCurrentRelic] ) then
					bombSpellPower = bombSpellPower + bloomBombIdols[playerCurrentRelic]
				end

				local bombSpell = bombSpellPower * hotData[spellName].dhCoeff * (1 + talentData[EmpoweredRejuv].current)
				if isSoD then
					local bombBase = hotData[spellName].bomb[spellRank]
					if type(bombBase) == "table" then
						bombBase = bombBase[min(playerLevel, #bombBase)]
					end
					bombAmount = ceil(calculateGeneralAmount(hotData[spellName].levels[spellRank], bombBase, bombSpell, spModifier, healModifier))
				else
					bombAmount = ceil(calculateGeneralAmount(hotData[spellName].levels[spellRank], hotData[spellName].bomb[spellRank], bombSpell, spModifier, healModifier))
				end

				-- Figure out the hot tick healing
				spellPower = spellPower * (hotData[spellName].coeff * (1 + talentData[EmpoweredRejuv].current))
				spellPower = spellPower / hotData[spellName].ticks
				healAmount = healAmount / hotData[spellName].ticks
				-- Figure out total ticks
				totalTicks = 7

				if( playerCurrentRelic and bloomIdols[playerCurrentRelic] ) then
					spellPower = spellPower + bloomIdols[playerCurrentRelic]
				end

				-- Glyph of Lifebloom, +1 second
				if( glyphCache[54826] ) then totalTicks = totalTicks + 1 end
				-- Nature's Splendor, +2 seconds
				if( talentData[NaturesSplendor].current >= 1 ) then totalTicks = totalTicks + 2 end

				-- Wild Growth
			elseif( spellName == WildGrowth ) then
				spellPower = spellPower * (hotData[spellName].coeff * (1 + talentData[EmpoweredRejuv].current))
				spellPower = spellPower / hotData[spellName].ticks
				healAmount = healAmount / hotData[spellName].ticks

				healModifier = healModifier + talentData[Genesis].current

				table.wipe(wgTicks)
				local tickModifier = equippedSetCache["Lasherweave"] >= 2 and 0.70 or 1
				local tickAmount = healAmount / hotData[spellName].ticks
				for i = 1, hotData[spellName].ticks do
					table.insert(wgTicks, math.ceil(healModifier * ((healAmount + tickAmount * (3 - (i - 1) * tickModifier)) + (spellPower * spModifier))))
				end

				if( isWrath and unitHasAura("player", MasterShapeshifter) ) then
					healModifier = healModifier * (1 + talentData[MasterShapeshifter].current)
				end

				return HOT_HEALS, wgTicks, hotData[spellName].ticks, hotData[spellName].interval, nil, true
			end

			healModifier = healModifier + talentData[Genesis].current

			if( isWrath and unitHasAura("player", MasterShapeshifter) ) then
				healModifier = healModifier * (1 + talentData[MasterShapeshifter].current)
				if bombAmount then
					bombAmount = bombAmount * (1 + talentData[MasterShapeshifter].current)
				end
			end

			healAmount = calculateGeneralAmount(hotData[spellName].levels[spellRank], healAmount, spellPower, spModifier, healModifier)
			return HOT_HEALS, ceil(max(0, healAmount)), totalTicks, hotData[spellName].interval, bombAmount
		end

		-- Calcualte direct and channeled heals
		CalculateHealing = function(guid, spellID)
			local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
			local healAmount = getBaseHealAmount(spellData, spellName, spellID, spellRank)
			local spellPower = GetSpellBonusHealing()
			local healModifier, spModifier = playerHealModifier, 1

			-- Gift of Nature
			if isTBC or isWrath then
				healModifier = healModifier * (1 + talentData[GiftofNature].current)
			else
				-- Gift of Nature does only apply to base values in classic
				healAmount = healAmount * (1 + talentData[GiftofNature].current)
			end

			if( isWrath and unitHasAura("player", MasterShapeshifter) ) then
				healModifier = healModifier * ( 1 + talentData[MasterShapeshifter].current)
				-- 32387 - Idol of the Raven Godess, +44 SP while in TOL
				if( playerCurrentRelic == 32387 ) then
					spellPower = spellPower + 44
				end
			end

			-- Regrowth
			if( spellName == Regrowth ) then
				-- Glyph of Regrowth - +20% if target has Regrowth
				if( glyphCache[54743] and hasRegrowth[guid] ) then
					healModifier = healModifier * 1.20
				end
				spellPower = spellPower * spellData[spellName].coeff * (isWrath and 1.88 or 1)
				-- Nourish
			elseif( spellName == Nourish ) then
				-- 46138 - Idol of Flourishing Life, +187 Nourish SP
				if( playerCurrentRelic == 46138 ) then
					spellPower = spellPower + 187
				end

				-- Apply any hot specific bonuses
				local hots = hotTotals[guid]
				if( hots and hots > 0 ) then
					local bonus = 1.20

					-- T7 Resto, +5% healing per each of the players hot on their target
					if( equippedSetCache["Dreamwalker"] >= 2 ) then
						bonus = bonus + 0.05 * hots
					end

					-- Glyph of Nourish - 6% per HoT
					if( glyphCache[62971] ) then
						bonus = bonus + 0.06 * hots
					end

					healModifier = healModifier * bonus
				end

				spellPower = spellPower * ((spellData[spellName].coeff * (isWrath and 1.88 or 1)) + talentData[EmpoweredTouch].current)
				-- Healing Touch
			elseif( spellName == HealingTouch ) then

				local castTime
				if isWrath then
					castTime = spellRank > 3 and 3 or spellRank == 3 and 2.5 or spellRank == 2 and 2 or 1.5
				else
					castTime = spellRank >= 5 and 3.5 or (spellRank == 4 and 3 or (spellRank == 3 and 2.5 or (spellRank == 2 and 2 or 1.5)))
				end

				-- Glyph of Healing Touch
				if( glyphCache[54825] ) then
					healAmount = healAmount / 2
					castTime = max(castTime - 1.5, 1.5)
				end

				spellPower = spellPower * (((castTime / 3.5) * (isWrath and 1.88 or 1)) + (talentData[EmpoweredTouch].current * (isWrath and 2 or 1)))

				if( playerCurrentRelic == 22399 ) then
					healAmount = healAmount + 100
				elseif( playerCurrentRelic == 28568 ) then
					healAmount = healAmount + 136
				end

				if equippedSetCache["Thunderheart"] >= 4 then
					healModifier = healModifier * 1.05
				end

				-- Tranquility
			elseif( spellName == Tranquility ) then
				healModifier = healModifier + talentData[Genesis].current

				spellPower = spellPower * spellData[spellName].coeff * (isWrath and 1.88 or 1) * (1 + talentData[EmpoweredRejuv].current)
				spellPower = spellPower / spellData[spellName].ticks
				healAmount = healAmount / spellData[spellName].ticks
			end

			healAmount = calculateGeneralAmount(spellData[spellName].levels[spellRank], healAmount, spellPower, spModifier, healModifier)

			-- 100% chance to crit with Nature, this mostly just covers fights like Loatheb where you will basically have 100% crit
			if( GetSpellCritChance(4) >= 100 ) then
				healAmount = healAmount * 1.50
			end

			if( spellData[spellName].ticks ) then
				return CHANNEL_HEALS, ceil(healAmount), spellData[spellName].ticks, spellData[spellName].interval
			end

			return DIRECT_HEALS, ceil(healAmount)
		end
	end
end

local hasDivineFavor, activeBeaconGUID

if( playerClass == "PALADIN" ) then
	LoadClassData = function()
		local DivineFavor = GetSpellInfo(20216)
		local FlashofLight = GetSpellInfo(19750)
		local HealingLight = GetSpellInfo(20237)
		local HolyLight = GetSpellInfo(635)
		local Divinity = GetSpellInfo(63646) or "Divinity"
		local TouchedbytheLight = GetSpellInfo(53590) or "Touched by the Light"
		local BeaconofLight = GetSpellInfo(53563) or GetSpellInfo(407613) or "Beacon of Light"
		local SealofLight = GetSpellInfo(20165) or "Seal of Light"
		local DivineIllumination = GetSpellInfo(31842) or "Divine Illumination"
		local DivinePlea = GetSpellInfo(54428)
		local AvengingWrath = GetSpellInfo(31884)
		local SacrificeRedeemed = GetSpellInfo(407805) or "Sacrifice Redeemed"

		if isWrath then
			spellData[HolyLight] = { coeff = 2.5 / 3.5, levels = {1, 6, 14, 22, 30, 38, 46, 54, 60, 62, 70, 75, 80}, averages = {
				{avg(50, 60), avg(50, 61), avg(51, 62), avg(52, 63), avg(53, 64)},
				{avg(96, 116), avg(97, 118), avg(98, 119), avg(99, 120), avg(100, 121), avg(101, 122)},
				{avg(203, 239), avg(204, 241), avg(206, 243), avg(208, 245), avg(209, 246), avg(211, 248)},
				{avg(397, 455), avg(399, 458), avg(401, 460), avg(404, 463), avg(406, 465), avg(409, 467)},
				{avg(628, 708), avg(631, 712), avg(634, 715), avg(637, 718), avg(640, 721), avg(643, 724)},
				{avg(894, 998), avg(897, 1002), avg(901, 1006), avg(905, 1010), avg(909, 1014), avg(913, 1017)},
				{avg(1209, 1349), avg(1213, 1354), avg(1218, 1359), avg(1222, 1363), avg(1227, 1368), avg(1232, 1372)},
				{avg(1595, 1777), avg(1600, 1783), avg(1605, 1788), avg(1610, 1793), avg(1615, 1798), avg(1621, 1803)},
				{avg(2034, 2266), avg(2039, 2272), avg(2045, 2278), avg(2051, 2284), avg(2057, 2290), avg(2063, 2295)},
				{avg(2232, 2486), avg(2238, 2493), avg(2244, 2499), avg(2251, 2506), avg(2257, 2512), avg(2264, 2518)},
				{avg(2818, 3138), avg(2825, 3145), avg(2832, 3152), avg(2839, 3159), avg(2846, 3166)},
				{avg(4199, 4677), avg(4209, 4688), avg(4219, 4698), avg(4230, 4709), avg(4240, 4719)},
				{avg(4888, 5444)} }}
			spellData[FlashofLight] = { coeff = 1.5 / 3.5, levels = {20, 26, 34, 42, 50, 58, 66, 74, 79}, averages = {
				{avg(81, 93), avg(82, 94), avg(83, 95), avg(84, 96), avg(85, 97), avg(86, 98)},
				{avg(124, 144), avg(125, 146), avg(126, 147), avg(127, 148), avg(129, 150), avg(130, 151)},
				{avg(189, 211), avg(190, 213), avg(192, 215), avg(193, 216), avg(195, 218), avg(197, 219)},
				{avg(256, 288), avg(257, 290), avg(259, 292), avg(261, 294), avg(263, 296), avg(265, 298)},
				{avg(346, 390), avg(348, 393), avg(350, 395), avg(352, 397), avg(354, 399), avg(357, 401)},
				{avg(445, 499), avg(447, 502), avg(450, 505), avg(452, 507), avg(455, 510), avg(458, 512)},
				{avg(588, 658), avg(591, 661), avg(594, 664), avg(597, 667), avg(600, 670)},
				{avg(682, 764), avg(685, 768), avg(688, 771), avg(692, 775), avg(695, 778)},
				{avg(785, 879), avg(788, 883)} }}
		else
			spellData[HolyLight] = { coeff = 2.5 / 3.5, levels = {1, 6, 14, 22, 30, 38, 46, 54, 60, 62, 70}, averages = {
				{avg(39, 47), avg(39, 48), avg(40, 49), avg(41, 50), avg(42, 51)},
				{avg(76, 90), avg(77, 92), avg(78, 93), avg(79, 94), avg(80, 95), avg(81, 96)},
				{avg(159, 187), avg(160, 189), avg(162, 191), avg(164, 193), avg(165, 194), avg(167, 196)},
				{avg(310, 356), avg(312, 359), avg(314, 361), avg(317, 364), avg(319, 366), avg(322, 368)},
				{avg(491, 553), avg(494, 557), avg(497, 560), avg(500, 563), avg(503, 566), avg(506, 569)},
				{avg(698, 780), avg(701, 784), avg(705, 788), avg(709, 792), avg(713, 796), avg(717, 799)},
				{avg(945, 1053), avg(949, 1058), avg(954, 1063), avg(958, 1067), avg(963, 1072), avg(968, 1076)},
				{avg(1246, 1388), avg(1251, 1394), avg(1256, 1399), avg(1261, 1404), avg(1266, 1409), avg(1272, 1414)},
				{avg(1590, 1770), avg(1595, 1775), avg(1601, 1781), avg(1607, 1787), avg(1613, 1793), avg(1619, 1799)},
				{avg(1741, 1939), avg(1747, 1946), avg(1753, 1952), avg(1760, 1959), avg(1766, 1965), avg(1773, 1971)},
				{avg(2196, 2446)} }}
			spellData[FlashofLight] = { coeff = 1.5 / 3.5, levels = {20, 26, 34, 42, 50, 58, 66}, averages = {
				{avg(62, 72), avg(63, 73), avg(64, 74), avg(65, 75), avg(66, 76), avg(67, 77)},
				{avg(96, 110), avg(97, 112), avg(98, 113), avg(99, 114), avg(101, 116), avg(102, 117)},
				{avg(145, 163), avg(146, 165), avg(148, 167), avg(149, 168), avg(151, 170), avg(153, 171)},
				{avg(197, 221), avg(198, 223), avg(200, 225), avg(202, 227), avg(204, 229), avg(206, 231)},
				{avg(267, 299), avg(269, 302), avg(271, 304), avg(273, 306), avg(275, 308), avg(278, 310)},
				{avg(343, 383), avg(345, 386), avg(348, 389), avg(350, 391), avg(353, 394), avg(356, 396)},
				{avg(448, 502), avg(450, 505), avg(453, 508), avg(455, 510), avg(458, 513)} }}
		end

		talentData[HealingLight] = {mod = 0.04, current = 0, spent = 0}
		talentData[Divinity] = {mod = 0.01, current = 0, spent = 0}
		talentData[TouchedbytheLight] = {mod = 0.10, current = 0, spent = 0}

		itemSetsData["Lightbringer"] = {30992, 30983, 30988, 30994, 30996, 34432, 34487, 34559}
		itemSetsData["Lightsworn"] = {50865, 50866, 50867, 50868, 50869, 51270, 51271, 51272, 51273, 51274, 51165, 51166, 51167, 51168, 51169}

		local flashLibrams
		if isWrath then
			flashLibrams = {[51472] = 510, [42616] = 436, [42615] = 375, [42614] = 331, [42613] = 293, [42612] = 204, [28592] = 89, [25644] = 79, [23006] = 43, [23201] = 28}
		else
			flashLibrams = {[23006] = 83, [23201] = 53, [186065] = 10, [25644] = 79}
		end
		local holyLibrams = {[45436] = 160, [40268] = 141, [28296] = 47}

		local blessings = {
			[19977] = {
				[HolyLight] = 210,
				[FlashofLight] = 60,
			},
			[19978] = {
				[HolyLight] = 300,
				[FlashofLight] = 85,
			},
			[19979] = {
				[HolyLight] = 400,
				[FlashofLight] = 115,
			},
			[25890] = {
				[HolyLight] = 400,
				[FlashofLight] = 115,
			},
			[27144] = {
				[HolyLight] = 580,
				[FlashofLight] = 185,
			},
			[27145] = {
				[HolyLight] = 580,
				[FlashofLight] = 185,
			},
		}

		AuraHandler = function(unit, guid)
			if( unitHasAura(unit, BeaconofLight) ) then
				activeBeaconGUID = guid
			elseif( activeBeaconGUID == guid ) then
				activeBeaconGUID = nil
			end

			if( unit == "player" ) then
				hasDivineFavor = unitHasAura("player", DivineFavor)
			end
		end

		GetHealTargets = function(bitType, guid, spellID)
			if( activeBeaconGUID and activeBeaconGUID ~= guid and guidToUnit[activeBeaconGUID] and UnitIsVisible(guidToUnit[activeBeaconGUID]) ) then
				return string.format("%s,%s", compressGUID[guid], compressGUID[activeBeaconGUID])
			end

			return compressGUID[guid]
		end

		CalculateHealing = function(guid, spellID, unit)
			local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
			local healAmount = getBaseHealAmount(spellData, spellName, spellID, spellRank)
			local spellPower = GetSpellBonusHealing()
			local healModifier, spModifier = playerHealModifier, 1

			if( glyphCache[54943] and unitHasAura("player", SealofLight) ) then
				healModifier = healModifier * 1.05
			end

			if isSoD and unitHasAura("player", SacrificeRedeemed) then
				healModifier = healModifier * 1.10
			end

			if isTBC or isWrath then
				healModifier = healModifier * (1 + talentData[HealingLight].current) * (1 + (talentData[Divinity].current * (UnitIsUnit(unit,"player") and 2 or 1)))
			else
				healAmount = healAmount * (1 + talentData[HealingLight].current)
			end

			if playerCurrentRelic then
				if( spellName == HolyLight and holyLibrams[playerCurrentRelic] ) then
					spellPower = spellPower + holyLibrams[playerCurrentRelic]
				elseif( spellName == FlashofLight and flashLibrams[playerCurrentRelic] ) then
					spellPower = spellPower + flashLibrams[playerCurrentRelic]
				end
			end

			if( equippedSetCache["Lightsworn"] >= 2 and unitHasAura("player", DivineIllumination) ) then
				healModifier = healModifier * 1.35
			end

			if( equippedSetCache["Lightbringer"] >= 4 and spellName == FlashofLight ) then healModifier = healModifier + 0.05 end

			if isWrath then
				if( unitHasAura("player", AvengingWrath) ) then healModifier = healModifier * 1.2 end
				if( unitHasAura("player", DivinePlea) ) then healModifier = healModifier * 0.5 end
			end

			spellPower = spellPower * spellData[spellName].coeff * (isWrath and 2.35 or 1)
			healAmount = calculateGeneralAmount(spellData[spellName].levels[spellRank], healAmount, spellPower, spModifier, healModifier)

			if not isWrath then -- Blessing of Light was baked in the base healing values of paladin spells in WotLK
				for auraID, values in pairs(blessings) do
					if unitHasAura(unit, auraID) then
						if playerCurrentRelic == 28592 then
							if spellName == FlashofLight then
								healAmount = calculateGeneralAmount(spellData[spellName].levels[spellRank], healAmount, values[spellName] + 60, healModifier, 1)
							else
								healAmount = calculateGeneralAmount(spellData[spellName].levels[spellRank], healAmount, values[spellName] + 120, healModifier, 1)
							end
						else
							healAmount = calculateGeneralAmount(spellData[spellName].levels[spellRank], healAmount, values[spellName], healModifier, 1)
						end
						break
					end
				end
			end

			if( hasDivineFavor or GetSpellCritChance(2) >= 100 ) then
				healAmount = healAmount * (1.50 * (1 + talentData[TouchedbytheLight].current))
			end

			return DIRECT_HEALS, ceil(healAmount)
		end
	end
end

if( playerClass == "PRIEST" ) then
	LoadClassData = function()
		local Renew = GetSpellInfo(139)
		local GreaterHeal = GetSpellInfo(2060)
		local PrayerofHealing = GetSpellInfo(596)
		local FlashHeal = GetSpellInfo(2061)
		local Heal = GetSpellInfo(2054)
		local LesserHeal = GetSpellInfo(2050)
		local SpiritualHealing = GetSpellInfo(14898)
		local ImprovedRenew = GetSpellInfo(14908)
		local GreaterHealHot = GetSpellInfo(22009)
		local DispelMagic = GetSpellInfo(527)
		local BindingHeal = GetSpellInfo(32546) or "Binding Heal"
		local EmpoweredHealing = GetSpellInfo(33158) or "Empowered Healing"
		local Renewal = GetSpellInfo(37563) and "37563" -- T4 bonus
		local Penance = GetSpellInfo(47540) or GetSpellInfo(402174) or "Penance"
		local Grace = GetSpellInfo(47516) or "Grace"
		local BlessedResilience = GetSpellInfo(33142) or "Blessed Resilience"
		local FocusedPower = GetSpellInfo(33186) or "Focused Power"
		local DivineProvidence = GetSpellInfo(47562) or "Divine Providence"
		local EmpoweredRenew = GetSpellInfo(63534) or "Empowered Renew"
		local TwinDisciplines = GetSpellInfo(47586) or "Twin Disciplines"

		hotData[Renew] = {coeff = 1, interval = 3, ticks = 5, levels = {8, 14, 20, 26, 32, 38, 44, 50, 56, 60, 65, 70, 75, 80}, averages = {
			45, 100, 175, 245, 315, 400, 510, 650, 810, 970, 1010, 1110, 1235, 1400 }}
		hotData[GreaterHealHot] = hotData[Renew]

		if Renewal then
			hotData[Renewal] = {coeff = 0, interval = 3, ticks = 3, levels = {70}, averages = {150}}
		end

		spellData[FlashHeal] = {coeff = 1.5 / 3.5, levels = {20, 26, 32, 38, 44, 50, 56, 61, 67, 73, 79}, averages = {
			{avg(193, 237), avg(194, 239), avg(196, 241), avg(198, 243),  avg(200, 245), avg(202, 247)},
			{avg(258, 314), avg(260, 317), avg(262, 319), avg(264, 321), avg(266, 323), avg(269, 325)},
			{avg(327, 393), avg(329, 396), avg(332, 398), avg(334, 401), avg(337, 403), avg(339, 406)},
			{avg(400, 478), avg(402, 481), avg(405, 484), avg(408, 487), avg(411, 490), avg(414, 492)},
			{avg(518, 616), avg(521, 620), avg(524, 623), avg(527, 626), avg(531, 630), avg(534, 633)},
			{avg(644, 764), avg(647, 768), avg(651, 772), avg(655, 776), avg(658, 779), avg(662, 783)},
			{avg(812, 958), avg(816, 963), avg(820, 967), avg(824, 971), avg(828, 975), avg(833, 979)},
			{avg(913, 1059), avg(917, 1064), avg(922, 1069), avg(927, 1074), avg(931, 1078)},
			{avg(1101, 1279), avg(1106, 1285), avg(1111, 1290), avg(1116, 1295), avg(1121, 1300)},
			{avg(1578, 1832), avg(1586, 1840), avg(1594, 1848), avg(1602, 1856), avg(1610, 1864), avg(1618, 1872)},
			{avg(1887, 2193), avg(1896, 2203)} }}
		spellData[GreaterHeal] = {coeff = 3 / 3.5, levels = {40, 46, 52, 58, 60, 63, 68, 73, 78}, averages = {
			{avg(899, 1013), avg(904, 1019), avg(909, 1024), avg(914, 1029), avg(919, 1034), avg(924, 1039)},
			{avg(1149, 1289), avg(1154, 1295), avg(1160, 1301), avg(1166, 1307), avg(1172, 1313), avg(1178, 1318)},
			{avg(1437, 1609), avg(1443, 1616), avg(1450, 1623), avg(1456, 1629), avg(1463, 1636), avg(1470, 1642)},
			{avg(1798, 2006), avg(1805, 2014), avg(1813, 2021), avg(1820, 2029), avg(1828, 2036), avg(1835, 2044)},
			{avg(1966, 2194), avg(1974, 2203), avg(1982, 2211), avg(1990, 2219), avg(1998, 2227), avg(2006, 2235)},
			{avg(2074, 2410), avg(2082, 2419), avg(2090, 2427), avg(2099, 2436), avg(2107, 2444)},
			{avg(2396, 2784), avg(2405, 2794), avg(2414, 2803), avg(2423, 2812), avg(2433, 2822)},
			{avg(3395, 3945), avg(3408, 3959), avg(3421, 3972), avg(3434, 3985), avg(3447, 3998)},
			{avg(3950, 4590), avg(3965, 4606), avg(3980, 4621)}	}}
		spellData[Heal] = {coeff = 3 / 3.5, levels = {16, 22, 28, 34}, averages = {
			{avg(295, 341), avg(297, 344), avg(299, 346), avg(302, 349), avg(304, 351), avg(307, 353)},
			{avg(429, 491), avg(432, 495), avg(435, 498), avg(438, 501), avg(441, 504), avg(445, 507)},
			{avg(566, 642), avg(570, 646), avg(574, 650), avg(578, 654), avg(582, 658), avg(586, 662)},
			{avg(712, 804), avg(716, 809), avg(721, 813), avg(725, 818), avg(730, 822), avg(734, 827)} }}
		spellData[LesserHeal] = {levels = {1, 4, 10}, averages = {
			{avg(46, 56), avg(46, 57), avg(47, 58)},
			{avg(71, 85), avg(72, 87), avg(73, 88), avg(74, 89), avg(75, 90), avg(76, 91)},
			{avg(135, 157), avg(136, 159), avg(138, 161), avg(139, 162), avg(141, 164), avg(143, 165)} }}
		spellData[PrayerofHealing] = {coeff = (isWrath and 0.2798 or isTBC and 0.431596 or (3/3.5/3)), levels = {30, 40, 50, 60, 60, 68, 76}, averages = {
			{avg(301, 321), avg(302, 323), avg(303, 324), avg(304, 325), avg(306, 327), avg(307, 328), avg(308, 329), avg(310, 331), avg(311, 332), avg(312, 333)},
			{avg(444, 472), avg(445, 474), avg(447, 476), avg(448, 477), avg(450, 479), avg(452, 480), avg(453, 482), avg(455, 484), avg(456, 485), avg(458, 487)},
			{avg(657, 695), avg(659, 697), avg(661, 699), avg(663, 701), avg(665, 703), avg(667, 705), avg(669, 707), avg(671, 709), avg(673, 711), avg(675, 713)},
			{avg(939, 991), avg(941, 994), avg(943, 996), avg(946, 999), avg(948, 1001), avg(951, 1003), avg(953, 1006), avg(955, 1008), avg(958, 1011), avg(960, 1013)},
			{avg(997, 1053), avg(999, 1056), avg(1002, 1058), avg(1004, 1061), avg(1007, 1063), avg(1009, 1066), avg(1012, 1068), avg(1014, 1071), avg(1017, 1073), avg(1019, 1076)},
			{avg(1246, 1316), avg(1248, 1319), avg(1251, 1322), avg(1254, 1325), avg(1257, 1328), avg(1260, 1330), avg(1262, 1333), avg(1265, 1336)},
			{avg(2091, 2209), avg(2095, 2214), avg(2100, 2219), avg(2105, 2224), avg(2109, 2228)} }}
		spellData[BindingHeal] = {coeff = 1.5 / 3.5, levels = {64, 72, 78}, averages = {
			{avg(1042, 1338), avg(1043, 1340), avg(1045, 1342), avg(1047, 1344), avg(1049, 1346), avg(1051, 1348), avg(1053, 1350), avg(1055, 1352)},
			{avg(1619, 2081), avg(1622, 2084), avg(1625, 2087), avg(1628, 2090), avg(1631, 2093)},
			{avg(1952, 2508), avg(1955, 2512), avg(1959, 2516)} }}
		if isSoD then
			spellData[Penance] = {_isChanneled = true, coeff = 0.857, ticks = 3, levels = {nil}, averages = generateSODAverages(38.258376, 1.06 * 0.8, 0.904195, 0.161311)}
		else
			spellData[Penance] = {_isChanneled = true, coeff = 0.857, ticks = 3, levels = {60, 70, 75, 80}, averages = {avg(670, 756), avg(805, 909), avg(1278, 1442), avg(1484, 1676)}}
		end

		talentData[ImprovedRenew] = {mod = 0.05, current = 0, spent = 0}
		talentData[SpiritualHealing] = {mod = 0.02, current = 0, spent = 0}
		talentData[EmpoweredHealing] = {mod = 0.02, current = 0, spent = 0}
		if isWrath then
			talentData[EmpoweredHealing].mod = 0.04
		end
		talentData[BlessedResilience] = {mod = 0.01, current = 0, spent = 0}
		talentData[FocusedPower] = {mod = 0.02, current = 0, spent = 0}
		talentData[DivineProvidence] = {mod = 0.02, current = 0, spent = 0}
		talentData[EmpoweredRenew] = {mod = 0.05, current = 0, spent = 0}
		talentData[TwinDisciplines] = {mod = 0.01, current = 0, spent = 0}

		itemSetsData["Oracle"] = {21351, 21349, 21350, 21348, 21352}
		itemSetsData["Absolution"] = {31068, 31063, 31060, 31069, 31066, 34562, 34527, 34435}
		itemSetsData["Avatar"] = {30153, 30152, 30151, 30154, 30150}

		local activeGraceGUID, activeGraceModifier = nil, 0
		AuraHandler = function(unit, guid)
			local stack, _, _, _, caster = select(3, unitHasAura(unit, Grace))
			if( caster == "player" ) then
				activeGraceModifier = stack * 0.03
				activeGraceGUID = guid
			elseif( activeGraceGUID == guid ) then
				activeGraceGUID = nil
			end
		end

		GetHealTargets = function(bitType, guid, spellID)
			local spellName = GetSpellInfo(spellID)
			if( spellName == BindingHeal ) then
				if guid == playerGUID then
					return format("%s", compressGUID[playerGUID])
				else
					return format("%s,%s", compressGUID[guid], compressGUID[playerGUID])
				end
			elseif( spellName == PrayerofHealing ) then
				-- In Wrath PoH can be castet on other groups than your own
				if not isWrath then
					guid = UnitGUID("player")
				end
				local targets = compressGUID[guid]
				local group = guidToGroup[guid]

				for groupGUID, id in pairs(guidToGroup) do
					-- We skip the rangecheck in Wrath since range would have to be measured from our target to its party and we can't do that
					local unit = isWrath and "player" or guidToUnit[groupGUID]
					local testUnit = guidToUnit[groupGUID]
					-- Dispel Magic is chosen because it's learned before PoH and maintains 30y range through the expansions

					-- Patch 1.15.1 CheckInteractDistance is not able to be used on friendly targets in combat
					local InCombatLockdownRestriction
					if isClassicEra then
						InCombatLockdownRestriction = InCombatLockdown() and not UnitCanAttack("player", unit)
					else
						InCombatLockdownRestriction = false
					end
					local inRange = IsSpellInRange(DispelMagic, unit) == 1 or (not InCombatLockdownRestriction and CheckInteractDistance(unit, 4))

					if( id == group and guid ~= groupGUID and inRange and UnitIsVisible(testUnit) and not UnitHasVehicleUI(testUnit) ) then
						targets = targets .. "," .. compressGUID[groupGUID]
					end
				end

				return targets
			end

			return compressGUID[guid]
		end

		CalculateHotHealing = function(guid, spellID)
			local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
			if not spellRank then return end

			local healAmount = getBaseHealAmount(hotData, spellName, spellID, spellRank)
			local spellPower = GetSpellBonusHealing()
			local healModifier, spModifier = playerHealModifier, 1
			local totalTicks

			if isWrath then
				healModifier = healModifier + talentData[SpiritualHealing].current
			elseif isTBC then
				healModifier = healModifier * (1 + talentData[SpiritualHealing].current)
			else
				-- Spiritual Healing only applies to base value in classic
				healAmount = healAmount * (1 + talentData[SpiritualHealing].current)
			end

			if( spellName == Renew or spellName == GreaterHealHot ) then
				if isWrath then
					healModifier = healModifier + talentData[ImprovedRenew].current
				elseif isTBC then
					healModifier = healModifier * (1 + talentData[ImprovedRenew].current)
				else
					-- Improved Renew only applies to the base value in classic
					healAmount = healAmount * (1 + talentData[ImprovedRenew].current)
				end
				healModifier = healModifier + talentData[TwinDisciplines].current

				totalTicks = hotData[spellName].ticks

				if( glyphCache[55674] ) then
					healModifier = healModifier + 0.25
					-- completly lose a tick
					healAmount = healAmount - (healAmount / totalTicks)
					spellPower = spellPower - (spellPower / totalTicks)
					totalTicks = totalTicks - 1
				end

				if( equippedSetCache["Oracle"] >= 5 or equippedSetCache["Avatar"] >= 4 ) then
					healAmount = healAmount + (healAmount / totalTicks) -- Add Tick Amount gained by Set.
					totalTicks = totalTicks + 1
				end

				-- SOD Empowered Renew Rune on Belt
				if isSoD then
					local belt_rune = C_Engraving.GetRuneForEquipmentSlot(6)
					if belt_rune and belt_rune.itemEnchantmentID == 7026 then
						spellPower = spellPower * 1.15
					end
				end

				-- SOD Empowered Renew Rune on Belt
				if isSoD then
					local belt_rune = C_Engraving.GetRuneForEquipmentSlot(6)
					if belt_rune and belt_rune.itemEnchantmentID == 7026 then
						spellPower = spellPower * 1.15
					end
				end

				spellPower = spellPower * ((isWrath and 1.88 or 1) * (1 + (talentData[EmpoweredRenew].current)))
				spellPower = spellPower / totalTicks
				healAmount = healAmount / totalTicks
			end

			if( activeGraceGUID == guid ) then
				healModifier = healModifier * (1 + activeGraceModifier)
			end

			healModifier = healModifier * (1 + talentData[FocusedPower].current)
			healModifier = healModifier * (1 + talentData[BlessedResilience].current)

			healAmount = calculateGeneralAmount(hotData[spellName].levels[spellRank], healAmount, spellPower, spModifier, healModifier)
			return HOT_HEALS, ceil(healAmount), totalTicks, hotData[spellName].interval
		end

		CalculateHealing = function(guid, spellID)
			local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
			if not spellRank then return end

			local healAmount = getBaseHealAmount(spellData, spellName, spellID, spellRank)
			local spellPower = GetSpellBonusHealing()
			local healModifier, spModifier = playerHealModifier, 1

			if isWrath then
				healModifier = healModifier + talentData[SpiritualHealing].current
			elseif isTBC then
				healModifier = healModifier * (1 + talentData[SpiritualHealing].current)
			else
				healAmount = healAmount * (1 + talentData[SpiritualHealing].current)
			end

			-- Greater Heal
			if( spellName == GreaterHeal ) then
				if( equippedSetCache["Absolution"] >= 4 ) then healModifier = healModifier * 1.05 end
				spellPower = spellPower * ((spellData[spellName].coeff * (isWrath and 1.88 or 1)) * (1 + (talentData[EmpoweredHealing].current * 2)))
				-- Flash Heal
			elseif( spellName == FlashHeal ) then
				spellPower = spellPower * ((spellData[spellName].coeff * (isWrath and 1.88 or 1)) * (1 + talentData[EmpoweredHealing].current))
				-- Binding Heal
			elseif( spellName == BindingHeal ) then
				healModifier = healModifier + talentData[DivineProvidence].current
				spellPower = spellPower * ((spellData[spellName].coeff * (isWrath and 1.88 or 1)) * (1 + talentData[EmpoweredHealing].current))
				-- Penance
			elseif( spellName == Penance ) then
				spellPower = spellPower * spellData[spellName].coeff * (isWrath and 1.88 or 1)
				spellPower = spellPower / spellData[spellName].ticks
				healModifier = healModifier + talentData[TwinDisciplines].current
				-- Prayer of Healing
			elseif( spellName == PrayerofHealing ) then
				healModifier = healModifier + talentData[DivineProvidence].current
				spellPower = spellPower * spellData[spellName].coeff * (isWrath and 1.88 or 1)
				-- Heal
			elseif( spellName == Heal ) then
				spellPower = spellPower * spellData[spellName].coeff * (isWrath and 1.88 or 1)
				-- Lesser Heal
			elseif( spellName == LesserHeal ) then
				local castTime = spellRank >= 3 and 2.5 or spellRank == 2 and 2 or 1.5
				spellPower = spellPower * (castTime / 3.5) * (isWrath and 1.88 or 1)
			end

			if( activeGraceGUID == guid ) then
				healModifier = healModifier * (1 + activeGraceModifier)
			end

			healModifier = healModifier * (1 + talentData[FocusedPower].current)
			healModifier = healModifier * (1 + talentData[BlessedResilience].current)

			healAmount = calculateGeneralAmount(spellData[spellName].levels[spellRank], healAmount, spellPower, spModifier, healModifier)

			-- Player has over a 100% chance to crit with Holy spells
			if( GetSpellCritChance(2) >= 100 ) then
				healAmount = healAmount * 1.50
			end

			-- Penance ticks 3 times, the player will see all 3 ticks, everyone else should only see the last 2
			if( spellName == Penance ) then
				return CHANNEL_HEALS, math.ceil(healAmount), spellData[spellName].ticks, 1
			end

			return DIRECT_HEALS, ceil(healAmount)
		end
	end
end

-- Keep track of current cast target of Healing Rain
local healingRainTargetGUID

if( playerClass == "SHAMAN" ) then
	LoadClassData = function()
		local ChainHeal = GetSpellInfo(1064)
		local HealingWave = GetSpellInfo(331)
		local LesserHealingWave = GetSpellInfo(8004)
		local ImpChainHeal = GetSpellInfo(30872) or "Improved Chain Heal"
		local HealingWay = GetSpellInfo(29206) or "Healing Way"
		local Purification = GetSpellInfo(16178) or "Purification"
		local EarthShield = GetSpellInfo(974) or "Earth Shield"
		local TidalWaves = GetSpellInfo(51562) or "Tidal Waves"
		local Riptide = GetSpellInfo(61295) or "Riptide"
		local Earthliving = GetSpellInfo(51945) or "Earthliving"
		local ElementalWeapons = GetSpellInfo(16266) or "Elemental Weapons"
		local HealingRain = GetSpellInfo(415236) or "Healing Rain"

		hotData[Riptide] = {interval = 3, ticks = 5, coeff = 0.50, levels = {60, 70, 75, 80}, averages = {665, 885, 1435, 1670}}
		hotData[Earthliving] = {interval = 3, ticks = 4, coeff = 0.80, levels = {30, 40, 50, 60, 70, 80}, averages = {116, 160, 220, 348, 456, 652}}

		if isSoD then
			hotData[Earthliving] = {interval = 3, ticks = 4, coeff = 0.364, levels = {30, 40, 50, 60, 70, 80}, averages = {116, 160, 220, 348, 456, 652}}
			
			-- Multiplied generateSODAverages levelCoeff by 10 for number of ticks
			hotData[HealingRain] = {interval = 1, ticks = 10, coeff = 10 * 0.063, levels = {nil}, averages = generateSODAverages(29.888200, 10*0.15, 0.690312, 0.136267)}
		end

		spellData[ChainHeal] = {coeff = 2.5 / 3.5, levels = {40, 46, 54, 61, 68, 74, 80}, averages = {
			{avg(320, 368), avg(322, 371), avg(325, 373), avg(327, 376), avg(330, 378), avg(332, 381)},
			{avg(405, 465), avg(407, 468), avg(410, 471), avg(413, 474), avg(416, 477), avg(419, 479)},
			{avg(551, 629), avg(554, 633), avg(557, 636), avg(560, 639), avg(564, 643), avg(567, 646)},
			{avg(605, 691), avg(608, 695), avg(612, 699), avg(616, 703), avg(620, 707), avg(624, 710)},
			{avg(826, 942), avg(829, 946), avg(833, 950), avg(837, 954), avg(841, 958), avg(845, 961)},
			{avg(906, 1034), avg(909, 1038), avg(913, 1042), avg(917, 1046), avg(921, 1050), avg(925, 1053)},
			{avg(1055, 1205)} }}
		spellData[HealingWave] = {levels = {1, 6, 12, 18, 24, 32, 40, 48, 56, 60, 63, 70, 75, 80}, averages = {
			{avg(34, 44), avg(34, 45), avg(35, 46), avg(36, 47)},
			{avg(64, 78), avg(65, 79), avg(66, 80), avg(67, 81), avg(68, 82), avg(69, 83)},
			{avg(129, 155), avg(130, 157), avg(132, 158), avg(133, 160), avg(135, 161), avg(136, 163)},
			{avg(268, 316), avg(270, 319), avg(272, 321), avg(274, 323), avg(277, 326), avg(279, 328)},
			{avg(376, 440), avg(378, 443), avg(381, 446), avg(384, 449), avg(386, 451), avg(389, 454)},
			{avg(536, 622), avg(539, 626), avg(542, 629), avg(545, 632), avg(549, 636), avg(552, 639)},
			{avg(740, 854), avg(743, 858), avg(747, 862), avg(751, 866), avg(755, 870), avg(759, 874)},
			{avg(1017, 1167), avg(1021, 1172), avg(1026, 1177), avg(1031, 1182), avg(1035, 1186), avg(1040, 1191)},
			{avg(1367, 1561), avg(1372, 1567), avg(1378, 1572), avg(1383, 1578), avg(1389, 1583), avg(1394, 1589)},
			{avg(1620, 1850), avg(1625, 1856), avg(1631, 1861), avg(1636, 1867), avg(1642, 1872), avg(1647, 1878)},
			{avg(1725, 1969), avg(1731, 1976), avg(1737, 1982), avg(1743, 1988), avg(1750, 1995), avg(1756, 2001)},
			{avg(2134, 2436), avg(2141, 2444), avg(2148, 2451), avg(2155, 2458), avg(2162, 2465)},
			{avg(2624, 2996), avg(2632, 3004), avg(2640, 3012), avg(2648, 3020), avg(2656, 3028)},
			{avg(3034, 3466)} }}
		spellData[LesserHealingWave] = {coeff = 1.5 / 3.5, levels = {20, 28, 36, 44, 52, 60, 66, 72, 77}, averages = {
			{avg(162, 186), avg(163, 188), avg(165, 190), avg(167, 192), avg(168, 193), avg(170, 195)},
			{avg(247, 281), avg(249, 284), avg(251, 286), avg(253, 288), avg(255, 290), avg(257, 292)},
			{avg(337, 381), avg(339, 384), avg(342, 386), avg(344, 389), avg(347, 391), avg(349, 394)},
			{avg(458, 514), avg(461, 517), avg(464, 520), avg(467, 523), avg(470, 526), avg(473, 529)},
			{avg(631, 705), avg(634, 709), avg(638, 713), avg(641, 716), avg(645, 720), avg(649, 723)},
			{avg(832, 928), avg(836, 933), avg(840, 937), avg(844, 941), avg(848, 945), avg(853, 949)},
			{avg(1039, 1185), avg(1043, 1190), avg(1047, 1194), avg(1051, 1198), avg(1055, 1202)},
			{avg(1382, 1578), avg(1387, 1583), avg(1392, 1588), avg(1397, 1593), avg(1402, 1598)},
			{avg(1606, 1834), avg(1612, 1840), avg(1618, 1846), avg(1624, 1852)} }}

		talentData[HealingWay] = {mod = (0.25/3), current = 0, spent = 0}
		talentData[ImpChainHeal] = {mod = 0.10, current = 0, spent = 0}
		talentData[Purification] = {mod = 0.02, current = 0, spent = 0}
		talentData[TidalWaves] = {mod = 0.04, current = 0, spent = 0}
		talentData[ElementalWeapons] = {mod = 0.10, current = 0, spent = 0}

		itemSetsData["Skyshatter"] = {31016, 31007, 31012, 31019, 31022, 34543, 34438, 34565}
		itemSetsData["T7 Resto"] = {40508, 40509, 40510, 40512, 40513, 39583, 39588, 39589, 39590, 39591}
		itemSetsData["T9 Resto"] = {48280, 48281, 48282, 48283, 48284, 48295, 48296, 48297, 48298, 48299, 48301, 48302, 48303, 48304, 48300, 48306, 48307, 48308, 48309, 48305, 48286, 48287, 48288, 48289, 48285, 48293, 48292, 48291, 48290, 48294}

		local lhwTotems = {[42598] = 320, [42597] = 267, [42596] = 236, [42595] = 204, [25645] = 79, [22396] = 80, [23200] = 53, [186072] = 10}
		local chTotems = {[45114] = 243, [38368] = 102, [28523] = 87}

		-- Keep track of who has riptide on them
		local riptideData, earthshieldList = {}, {}
		AuraHandler = function(unit, guid)
			riptideData[guid] = unitHasAura(unit, Riptide) and true or nil

			-- Currently, Glyph of Lesser Healing Wave + Any Earth Shield increase the healing not just the players own
			if( unitHasAura(unit, EarthShield) ) then
				earthshieldList[guid] = true
			elseif( earthshieldList[guid] ) then
				earthshieldList[guid] = nil
			end
		end

		ResetChargeData = function(guid)
			riptideData[guid] = guidToUnit[guid] and unitHasAura(guidToUnit[guid], Riptide) and true or nil
		end

		-- Lets a specific override on how many people this will hit
		GetHealTargets = function(bitType, guid, spellID, amount)
			local spellName = GetSpellInfo(spellID)
			-- Glyph of Healing Wave, heals you for 20% of your heal when you heal someone else
			if( glyphCache[55440] and guid ~= playerGUID and spellName == HealingWave ) then
				if guidToUnit[guid] then
					return string.format("%s,%d,%s,%d", compressGUID[guid], amount, compressGUID[playerGUID], amount *  0.20), -1
				else
					return compressGUID[UnitGUID("player")], amount *  0.20
				end
			elseif( isSoD and spellName == HealingRain ) then
				-- Healing Rain can only be applied to party members of healing rain target
				if( guidToGroup[healingRainTargetGUID] ~= guidToGroup[guid] ) then return nil end
			end

			return compressGUID[guid]
		end

		CalculateHotHealing = function(guid, spellID)
			local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
			local healAmount = getBaseHealAmount(hotData, spellName, spellID, spellRank)
			local spellPower = GetSpellBonusHealing()
			local healModifier, spModifier = playerHealModifier, 1
			local totalTicks = hotData[spellName].ticks

			healModifier = healModifier * (1 + talentData[Purification].current)

			-- Riptide
			if( spellName == Riptide ) then
				if( equippedSetCache["T9 Resto"] >= 2 ) then
					spModifier = spModifier * 1.20
				end

				spellPower = spellPower * hotData[spellName].coeff * (isWrath and 1.88 or 1)
				spellPower = spellPower / hotData[spellName].ticks
				healAmount = healAmount / hotData[spellName].ticks

				-- Glyph of Riptide, +6 seconds
				if( glyphCache[63273] ) then totalTicks = totalTicks + 2 end
			end

			-- Earthliving Weapon
			if( spellName == Earthliving ) then
				spellPower = (spellPower * (hotData[spellName].coeff * 1.88) * 0.45)
				spellPower = spellPower / hotData[spellName].ticks
				healAmount = healAmount / hotData[spellName].ticks

				healModifier = healModifier * (1 + talentData[ElementalWeapons].current)

				totalTicks = hotData[spellName].ticks
			end

			healAmount = calculateGeneralAmount(hotData[spellName].levels[spellRank], healAmount, spellPower, spModifier, healModifier)

			if( isSoD and spellName == HealingRain ) then
				-- HoT duration is equal to remaining duration of aura
				local _, _, _, _, _, expiration = unitHasAura("player", HealingRain)
				local ticksLeft = ceil(expiration - GetTime())
				totalTicks = ticksLeft
			end

			return HOT_HEALS, healAmount, totalTicks, hotData[spellName].interval
		end

		CalculateHealing = function(guid, spellID, unit)
			local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
			local healAmount = getBaseHealAmount(spellData, spellName, spellID, spellRank)
			local spellPower = GetSpellBonusHealing()
			local healModifier, spModifier = playerHealModifier, 1

			if isTBC or isWrath then
				healModifier = healModifier * (1 + talentData[Purification].current)
			else
				-- Purification only applies to base values in classic
				healAmount = healAmount * (1 + talentData[Purification].current)
			end

			-- Chain Heal
			if( spellName == ChainHeal ) then
				spellPower = spellPower * spellData[spellName].coeff * (isWrath and 1.88 or 1)

				if( equippedSetCache["Skyshatter"] >= 4 ) then
					healModifier = healModifier * 1.05
				end

				if( equippedSetCache["T7 Resto"] >= 4 ) then
					healModifier = healModifier * 1.05
				end

				healModifier = healModifier * (1 + talentData[ImpChainHeal].current)

				-- Add +25% from Riptide being up and reset the flag
				if( riptideData[guid] ) then
					healModifier = healModifier * 1.25
					riptideData[guid] = nil
				end

				if playerCurrentRelic and chTotems[playerCurrentRelic] then
					healAmount = healAmount + chTotems[playerCurrentRelic]
				end
				-- Healing Wave
			elseif( spellName == HealingWave ) then
				local hwStacks = select(3, unitHasAura(unit, 29203))
				if( hwStacks ) then
					healAmount = healAmount * ((hwStacks * 0.06) + 1)
				end

				if isWrath then
					healModifier = healModifier * (1 + talentData[HealingWay].current)
				end

				if( equippedSetCache["T7 Resto"] >= 4 ) then
					healModifier = healModifier * 1.05
				end

				local castTime = spellRank > 3 and 3 or spellRank == 3 and 2.5 or spellRank == 2 and 2 or 1.5

				if playerCurrentRelic == 27544 then spellPower = spellPower + 88 end

				spellPower = spellPower * ((castTime / 3.5) * (isWrath and 1.88 or 1) + talentData[TidalWaves].current)

				-- Lesser Healing Wave
			elseif( spellName == LesserHealingWave ) then
				-- Glyph of Lesser Healing Wave, +20% healing on LHW if target has ES up
				if( glyphCache[55438] and earthshieldList[guid] ) then
					healModifier = healModifier * 1.20
				end

				spellPower = spellPower + (playerCurrentRelic and lhwTotems[playerCurrentRelic] or 0)
				spellPower = spellPower * (spellData[spellName].coeff * (isWrath and 1.88 or 1) + (talentData[TidalWaves].current / 2))
			end

			healAmount = calculateGeneralAmount(spellData[spellName].levels[spellRank], healAmount, spellPower, spModifier, healModifier)

			-- Player has over a 100% chance to crit with Nature spells
			if( GetSpellCritChance(4) >= 100 ) then
				healAmount = healAmount * 1.50
			end

			-- Apply the final modifier of any MS or self heal increasing effects
			return DIRECT_HEALS, ceil(healAmount)
		end
	end
end

if( playerClass == "HUNTER" ) then
	LoadClassData = function()
		local MendPet = GetSpellInfo(136)

		if isTBC or isWrath then
			hotData[MendPet] = { interval = 3, levels = { 12, 20, 28, 36, 44, 52, 60, 68, 74, 80 }, ticks = 5, averages = {125, 250, 450, 700, 1000, 1400, 1825, 2375, 4250, 5250 } }
		else
			spellData[MendPet] = { interval = 1, levels = { 12, 20, 28, 36, 44, 52, 60 }, ticks = 5, averages = {100, 190, 340, 515, 710, 945, 1225 } }
		end

		itemSetsData["Giantstalker"] = {16851, 16849, 16850, 16845, 16848, 16852, 16846, 16847}

		GetHealTargets = function(bitType, guid, spellID)
			local petGUID = UnitGUID("pet")
			return petGUID and compressGUID[petGUID]
		end

		CalculateHotHealing = function(guid, spellID)
			local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
			local amount = getBaseHealAmount(hotData, spellName, spellID, spellRank)

			if( equippedSetCache["Giantstalker"] >= 3 ) then amount = amount * 1.1 end

			return HOT_HEALS, ceil(amount / hotData[spellName].ticks), hotData[spellName].ticks, hotData[spellName].interval
		end

		CalculateHealing = function(guid, spellID)
			local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
			local healAmount = getBaseHealAmount(spellData, spellName, spellID, spellRank)

			if( equippedSetCache["Giantstalker"] >= 3 ) then healAmount = healAmount * 1.1 end

			return CHANNEL_HEALS, ceil(healAmount / spellData[spellName].ticks), spellData[spellName].ticks, spellData[spellName].interval
		end
	end
end

if( isSoD and playerClass == "MAGE" ) then
	LoadClassData = function()
		local MassRegeneration = GetSpellInfo(412510) or "Mass Regeneration"
		local Regeneration = GetSpellInfo(401417) or "Regeneration"

		spellData[MassRegeneration] = {_isChanneled = true, coeff = 0.081 * 3, interval = 1,  ticks = 3, levels = {nil}, averages = generateSODAverages(38.258376, 0.42, 0.904195, 0.161311) }
		spellData[Regeneration] = {_isChanneled = true, coeff = 0.243 * 3 , interval = 1, ticks = 3, levels = {nil}, averages = generateSODAverages(38.258376,  0.42, 0.904195, 0.161311)}

		GetHealTargets = function(bitType, guid, spellID)
			local spellName = GetSpellInfo(spellID)
			if( spellName == MassRegeneration ) then
				-- Mass Regeneration can be casted on other groups than your own
				local targets = compressGUID[guid]
				local group = guidToGroup[guid]

				for groupGUID, id in pairs(guidToGroup) do
					-- We skip the rangecheck since range would have to be measured from our target to its party and we can't do that
					local testUnit = guidToUnit[groupGUID]
					if( id == group and guid ~= groupGUID and UnitIsVisible(testUnit) and not UnitHasVehicleUI(testUnit) ) then
						targets = targets .. "," .. compressGUID[groupGUID]
					end
				end
				return targets
			end
			return compressGUID[guid]
		end

		CalculateHealing = function(guid, spellID)
			local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
			if not spellRank then return end

			local healAmount = getBaseHealAmount(spellData, spellName, spellID, spellRank)
			local spellPower = GetSpellBonusHealing()
			local healModifier, spModifier = playerHealModifier, 1

			spellPower = spellPower * spellData[spellName].coeff
			spellPower = spellPower / spellData[spellName].ticks

			healAmount = calculateGeneralAmount(spellData[spellName].levels[spellRank], healAmount, spellPower, spModifier, healModifier)

			if( spellName == MassRegeneration or spellName == Regeneration ) then
				return CHANNEL_HEALS, math.ceil(healAmount), spellData[spellName].ticks, spellData[spellName].interval
			end

			return DIRECT_HEALS, ceil(healAmount)
		end
	end
end

--[[
-- While the groundwork is done, "CalculateHealing" needs to be theory crafted for classic / TBC / WotLK. Deactivating until someone steps up.
if( playerClass == "WARLOCK" ) then
	LoadClassData = function()
		local HealthFunnel = GetSpellInfo(755)
		local DrainLife = GetSpellInfo(689)
		local ImpHealthFunnel = GetSpellInfo(18703)
		local ShadowMastery = GetSpellInfo(18271)

		spellData[HealthFunnel] = {_isChanneled = true, coeff = 0.548, interval = 1, levels = { 12, 20, 28, 36, 44, 52, 60, 68, 76 }, ticks = 10, averages = { 120, 240, 430, 640, 890, 1190, 1530, 1880, 5200 } }
		spellData[DrainLife] = {_isChanneled = true, coeff = 0.143, interval = 1, levels = { 14, 22, 30, 38, 46, 54, 62, 69, 78 }, ticks = 5, averages = { 10 * 5, 17 * 5, 29 * 5, 41 * 5, 55 * 5, 71 * 5, 87 * 5, 108 * 5, 133 * 5 } }

		talentData[ImpHealthFunnel] = {mod = 0.1, current = 0, spent = 0}
		talentData[ShadowMastery] = {mod = 0.1, current = 0, spent = 0}

		GetHealTargets = function(bitType, guid, spellID)
			local spellName = GetSpellInfo(spellID)
			local petGUID = UnitGUID("pet")
			if spellName == DrainLife then
				return compressGUID[playerGUID]
			else
				return petGUID and compressGUID[petGUID]
			end
		end

		CalculateHealing = function(guid, spellID)
			local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]
			local healAmount = getBaseHealAmount(spellData, spellName, spellID, spellRank)
			local spellPower = GetSpellBonusHealing()
			local healModifier, spModifier = playerHealModifier, 1

			if spellName == DrainLife then
				healModifier = healModifier * (1 + talentData[ShadowMastery].current)
			elseif spellName == HealthFunnel then
				healModifier = healModifier * (1 + talentData[ImpHealthFunnel].current)
			end

			spellPower = spellPower * spellData[spellName].coeff

			healAmount = calculateGeneralAmount(spellData[spellName].levels[spellRank], healAmount, spellPower, spModifier, healModifier)

			return CHANNEL_HEALS, ceil(healAmount / spellData[spellName].ticks), spellData[spellName].ticks, spellData[spellName].interval
		end
	end
end
]]

-- Healing modifiers
if( not HealComm.aurasUpdated ) then
	HealComm.aurasUpdated = true
	HealComm.healingModifiers = nil
end

HealComm.currentModifiers = HealComm.currentModifiers or {}

-- The only spell in the game with a name conflict is Ray of Pain from the Nagrand Void Walkers
HealComm.healingModifiers = HealComm.healingModifiers or {
	[28776] = 0.10, -- Necrotic Poison
	[36693] = 0.55, -- Necrotic Poison
	[46296] = 0.25, -- Necrotic Poison
	[19716] = 0.25, -- Gehennas' Curse
	[13737] = 0.50, -- Mortal Strike
	[15708] = 0.50, -- Mortal Strike
	[16856] = 0.50, -- Mortal Strike
	[17547] = 0.50, -- Mortal Strike
	[19643] = 0.50, -- Mortal Strike
	[24573] = 0.50, -- Mortal Strike
	[27580] = 0.50, -- Mortal Strike
	[29572] = 0.50, -- Mortal Strike
	[31911] = 0.50, -- Mortal Strike
	[32736] = 0.50, -- Mortal Strike
	[35054] = 0.50, -- Mortal Strike
	[37335] = 0.50, -- Mortal Strike
	[39171] = 0.50, -- Mortal Strike
	[40220] = 0.50, -- Mortal Strike
	[44268] = 0.50, -- Mortal Strike
	[12294] = 0.50, -- Mortal Strike (Rank 1)
	[21551] = 0.50, -- Mortal Strike (Rank 2)
	[21552] = 0.50, -- Mortal Strike (Rank 3)
	[21553] = 0.50, -- Mortal Strike (Rank 4)
	[25248] = 0.50, -- Mortal Strike (Rank 5)
	[30330] = 0.50, -- Mortal Strike (Rank 6)
	[43441] = 0.50, -- Mortal Strike
	[30843] = 0.00, -- Enfeeble
	[19434] = 0.50, -- Aimed Shot (Rank 1)
	[20900] = 0.50, -- Aimed Shot (Rank 2)
	[20901] = 0.50, -- Aimed Shot (Rank 3)
	[20902] = 0.50, -- Aimed Shot (Rank 4)
	[20903] = 0.50, -- Aimed Shot (Rank 5)
	[20904] = 0.50, -- Aimed Shot (Rank 6)
	[27065] = 0.50, -- Aimed Shot (Rank 7)
	[34625] = 0.25, -- Demolish
	[35189] = 0.50, -- Solar Strike
	[32315] = 0.50, -- Soul Strike
	[32378] = 0.50, -- Filet
	[36917] = 0.50, -- Magma-Thrower's Curse
	[44534] = 0.50, -- Wretched Strike
	[34366] = 0.75, -- Ebon Poison
	[36023] = 0.50, -- Deathblow
	[36054] = 0.50, -- Deathblow
	[45885] = 0.50, -- Shadow Spike
	[41292] = 0.00, -- Aura of Suffering
	[40599] = 0.50, -- Arcing Smash
	[9035] = 0.80, -- Hex of Weakness (Rank 1)
	[19281] = 0.80, -- Hex of Weakness (Rank 2)
	[19282] = 0.80, -- Hex of Weakness (Rank 3)
	[19283] = 0.80, -- Hex of Weakness (Rank 4)
	[19284] = 0.80, -- Hex of Weakness (Rank 5)
	[25470] = 0.80, -- Hex of Weakness (Rank 6)
	[34073] = 0.85, -- Curse of the Bleeding Hollow
	[31306] = 0.25, -- Carrion Swarm
	[44475] = 0.25, -- Magic Dampening Field
	[23169] = 0.50, -- Brood Affliction: Green
	[22859] = 0.50, -- Mortal Cleave
	[38572] = 0.50, -- Mortal Cleave
	[39595] = 0.50, -- Mortal Cleave
	[45996] = 0.00, -- Darkness
	[41350] = 2.00, -- Aura of Desire
	[28176] = 1.20, -- Fel Armor
	[7068] = 0.25, -- Veil of Shadow
	[17820] = 0.25, -- Veil of Shadow
	[22687] = 0.25, -- Veil of Shadow
	[23224] = 0.25, -- Veil of Shadow
	[24674] = 0.25, -- Veil of Shadow
	[28440] = 0.25, -- Veil of Shadow
	[13583] = 0.50, -- Curse of the Deadwood
	[23230] = 0.50, -- Blood Fury
	[31977] = 1.50, -- Curse of Infinity
}
if isWrath then
	HealComm.healingModifiers[34123] = 1.06 -- Tree of Life
	HealComm.healingModifiers[45237] = 1.03 -- Focused Will Rank 1
	HealComm.healingModifiers[45241] = 1.04 -- Focused Will Rank 2
	HealComm.healingModifiers[45242] = 1.05 -- Focused Will Rank 3
end


HealComm.healingStackMods = HealComm.healingStackMods or {
	-- Mortal Wound
	[25646] = function(stacks) return 1 - stacks * 0.10 end,
	[28467] = function(stacks) return 1 - stacks * 0.10 end,
	[30641] = function(stacks) return 1 - stacks * 0.05 end,
	[31464] = function(stacks) return 1 - stacks * 0.10 end,
	[36814] = function(stacks) return 1 - stacks * 0.10 end,
	[38770] = function(stacks) return 1 - stacks * 0.05 end,
	-- Dark Touched
	[45347] = function(stacks) return 1 - stacks * 0.05 end,
	-- Nether Portal - Dominance
	[30423] = function(stacks) return 1 - stacks * 0.01 end,
	-- Focused Will
	[45242] = function(stacks) return 1 + stacks * 0.10 end,
}

if isTBC or isWrath then
	HealComm.healingStackMods[13218] = function(stacks) return 1 - stacks * 0.10 end -- Wound Poison (Rank 1)
	HealComm.healingStackMods[13222] = function(stacks) return 1 - stacks * 0.10 end -- Wound Poison (Rank 2)
	HealComm.healingStackMods[13223] = function(stacks) return 1 - stacks * 0.10 end -- Wound Poison (Rank 3)
	HealComm.healingStackMods[13224] = function(stacks) return 1 - stacks * 0.10 end -- Wound Poison (Rank 4)
	HealComm.healingStackMods[27189] = function(stacks) return 1 - stacks * 0.10 end -- Wound Poison (Rank 5)
end

local healingStackMods = HealComm.healingStackMods
local healingModifiers, currentModifiers = HealComm.healingModifiers, HealComm.currentModifiers

local distribution
local CTL = _G.ChatThrottleLib
local function sendMessage(msg)
	if( distribution and strlen(msg) <= 240 ) then
		if CTL then
			CTL:SendAddonMessage("BULK", COMM_PREFIX, msg, distribution or 'GUILD')
		end
	end
end

-- Keep track of where all the data should be going
local instanceType
local function updateDistributionChannel()
	if( instanceType == "pvp" or instanceType == "arena" or IsInGroup(LE_PARTY_CATEGORY_INSTANCE) ) then
		distribution = "INSTANCE_CHAT"
	elseif( IsInRaid() ) then
		distribution = "RAID"
	elseif( IsInGroup() ) then
		distribution = "PARTY"
	else
		distribution = nil
	end
end

-- Figure out where we should be sending messages and wipe some caches
function HealComm:PLAYER_ENTERING_WORLD()
	HealComm.eventFrame:UnregisterEvent("PLAYER_ENTERING_WORLD")
	HealComm:ZONE_CHANGED_NEW_AREA()
end

function HealComm:ZONE_CHANGED_NEW_AREA()
	local pvpType = GetZonePVPInfo()
	local instance = select(2, IsInInstance())

	HealComm.zoneHealModifier = 1
	if( pvpType == "combat" or instance == "arena" or instance == "pvp" ) then
		HealComm.zoneHealModifier = 0.90
	end

	if( instance ~= instanceType ) then
		instanceType = instance

		updateDistributionChannel()
		clearPendingHeals()
		wipe(activeHots)
	end

	instanceType = instance
end

local alreadyAdded = {}
function HealComm:UNIT_AURA(unit)
	local guid = UnitGUID(unit)
	if( not guidToUnit[guid] ) then return end
	local increase, decrease, playerIncrease, playerDecrease = 1, 1, 1, 1

	-- Scan buffs
	local id = 1
	while( true ) do
		local name, _, stack, _, _, _, _, _, _, spellID = UnitAura(unit, id, "HELPFUL")
		if( not name ) then break end
		-- Prevent buffs like Tree of Life that have the same name for the shapeshift/healing increase from being calculated twice
		if( not alreadyAdded[name] ) then
			alreadyAdded[name] = true

			if( healingModifiers[spellID] ) then
				increase = increase * healingModifiers[spellID]

			elseif( healingStackMods[spellID] ) then
				increase = increase * healingStackMods[spellID](stack)
			end
		end

		id = id + 1
	end
	-- Scan debuffs
	id = 1
	while( true ) do
		local name, _, stack, _, _, _, _, _, _, spellID = UnitAura(unit, id, "HARMFUL")
		if( not name ) then break end

		if( healingModifiers[spellID] ) then
			decrease = min(decrease, healingModifiers[spellID])
		elseif( healingStackMods[spellID] ) then
			decrease = min(decrease, healingStackMods[spellID](stack))
		end

		id = id + 1
	end

	-- Check if modifier changed
	local modifier = increase * decrease
	if( modifier ~= currentModifiers[guid] ) then
		if( currentModifiers[guid] or modifier ~= 1 ) then
			currentModifiers[guid] = modifier
			self.callbacks:Fire("HealComm_ModifierChanged", guid, modifier)
		else
			currentModifiers[guid] = modifier
		end
	end

	wipe(alreadyAdded)

	if( unit == "player" ) then
		if unitHasAura("player", 10060) and not (isWrath or isTBC) then -- Power Infusion
			playerIncrease = playerIncrease * 1.20
		end

		local npsStacks = select(3, unitHasAura("player", 30422)) -- Nether Portal - Serenity
		if npsStacks then
			playerIncrease = playerIncrease * (1 + npsStacks * 0.05)
		end

		if unitHasAura("player", 41406) then -- Dementia: +5%
			playerIncrease = playerIncrease * 1.05
		end

		if unitHasAura("player", 41409) then -- Dementia: -5%
			playerDecrease = playerDecrease * 0.95
		end

		if unitHasAura("player", 32346) then -- Stolen Soul
			playerDecrease = playerDecrease * 0.50
		end

		if unitHasAura("player", 40099) then -- Vile Slime
			playerDecrease = playerDecrease * 0.50
		end

		if unitHasAura("player", 38246) then -- Vile Sludge
			playerDecrease = playerDecrease * 0.50
		end

		if unitHasAura("player", 45573) then -- Vile Sludge
			playerDecrease = playerDecrease * 0.50
		end

		playerHealModifier = playerIncrease * playerDecrease
	end

	-- Class has a specific monitor it needs for auras
	if( AuraHandler ) then
		AuraHandler(unit, guid)
	end
end

-- Monitor glyph changes
function HealComm:GlyphsUpdated(id)
	local spellID = glyphCache[id]

	-- Invalidate the old cache value
	if( spellID ) then
		glyphCache[spellID] = nil
		glyphCache[id] = nil
	end

	-- Cache the new one if any
	local enabled, _, glyphID = GetGlyphSocketInfo(id)
	if( enabled and glyphID ) then
		glyphCache[glyphID] = true
		glyphCache[id] = glyphID
	end
end

HealComm.GLYPH_ADDED = HealComm.GlyphsUpdated
HealComm.GLYPH_REMOVED = HealComm.GlyphsUpdated
HealComm.GLYPH_UPDATED = HealComm.GlyphsUpdated

function HealComm:PLAYER_LEVEL_UP(level)
	playerLevel = tonumber(level) or UnitLevel("player")
end

-- Cache player talent data for spells we need
function HealComm:CHARACTER_POINTS_CHANGED()
	for tabIndex = 1, GetNumTalentTabs() do
		for i = 1, GetNumTalents(tabIndex) do
			local name, _, _, _, spent = GetTalentInfo(tabIndex, i)
			if( name and talentData[name] ) then
				talentData[name].current = talentData[name].mod * spent
				talentData[name].spent = spent
			end
		end
	end
end

-- Save the currently equipped range weapon
local RANGED_SLOT = GetInventorySlotInfo("RangedSlot")
function HealComm:PLAYER_EQUIPMENT_CHANGED()
	-- Caches set bonus info, as you can't reequip set bonus gear in combat no sense in checking it
	if( not InCombatLockdown() ) then
		for name, items in pairs(itemSetsData) do
			equippedSetCache[name] = 0
			for _, itemID in pairs(items) do
				if( IsEquippedItem(itemID) ) then
					equippedSetCache[name] = equippedSetCache[name] + 1
				end
			end
		end
	end

	-- Check relic
	local relic = GetInventoryItemLink("player", RANGED_SLOT)
	playerCurrentRelic = relic and tonumber(strmatch(relic, "item:(%d+):")) or nil
end

-- COMM CODE
local function loadHealAmount(...)
	local tbl = HealComm:RetrieveTable()
	for i = 1, select("#", ...) do
		tbl[i] = tonumber((select(i, ...)))
	end

	return tbl
end

-- Direct heal started
local function loadHealList(pending, amount, stack, endTime, ticksLeft, ...)
	wipe(tempPlayerList)

	-- For the sake of consistency, even a heal doesn't have multiple end times like a hot, it'll be treated as such in the DB
	if( amount ~= -1 and amount ~= "-1" ) then
		amount = not pending.hasVariableTicks and amount or loadHealAmount(strsplit("@", amount))

		for i = 1, select("#", ...) do
			local guid = select(i, ...)
			local decompGUID = guid and decompressGUID[guid]
			if( decompGUID ) then
				updateRecord(pending, decompGUID, amount, stack, endTime, ticksLeft)
				tinsert(tempPlayerList, decompGUID)
			end
		end
	else
		for i = 1, select("#", ...), 2 do
			local guid = select(i, ...)
			local decompGUID = guid and decompressGUID[guid]
			amount = not pending.hasVariableTicks and tonumber((select(i + 1, ...))) or loadHealAmount(string.split("@", amount))
			if( decompGUID and amount ) then
				updateRecord(pending, decompGUID, amount, stack, endTime, ticksLeft)
				tinsert(tempPlayerList, decompGUID)
			end
		end
	end
end

local function parseDirectHeal(casterGUID, spellID, amount, castTime, ...)
	local spellName = GetSpellInfo(spellID)
	local unit = guidToUnit[casterGUID]

	if( not unit or not spellName or not amount or select("#", ...) == 0 ) then return end

	local endTime
	if unit == "player" then
		endTime = select(5, CastingInfo())
		if not endTime then return end
		endTime = endTime / 1000
	else
		endTime = GetTime() + (castTime or 1.5)
	end

	pendingHeals[casterGUID] = pendingHeals[casterGUID] or {}
	pendingHeals[casterGUID][spellName] = pendingHeals[casterGUID][spellName] or {}

	local pending = pendingHeals[casterGUID][spellName]
	wipe(pending)
	pending.endTime = endTime
	pending.spellID = spellID
	pending.bitType = DIRECT_HEALS

	loadHealList(pending, amount, 1, pending.endTime, nil, ...)

	HealComm.callbacks:Fire("HealComm_HealStarted", casterGUID, spellID, pending.bitType, pending.endTime, unpack(tempPlayerList))
end

HealComm.parseDirectHeal = parseDirectHeal

-- Channeled heal started
local function parseChannelHeal(casterGUID, spellID, amount, totalTicks, ...)
	local spellName = GetSpellInfo(spellID)
	local unit = guidToUnit[casterGUID]
	if( not unit or not spellName or not totalTicks or not amount or select("#", ...) == 0 ) then return end

	local tickInterval = spellName == GetSpellInfo(740) and 2 or 1

	local startTime, endTime
	if unit == "player" then
		startTime, endTime = select(4, ChannelInfo())
		if not startTime then return end
		startTime = startTime / 1000
		endTime = endTime / 1000
	else
		startTime = GetTime()
		endTime = startTime + totalTicks * tickInterval
	end

	pendingHots[casterGUID] = pendingHots[casterGUID] or {}
	pendingHots[casterGUID][spellName] = pendingHots[casterGUID][spellName] or {}

	local inc = amount == -1 and 2 or 1
	local pending = pendingHots[casterGUID][spellName]
	wipe(pending)
	pending.startTime = startTime
	pending.endTime = endTime
	pending.duration = endTime - startTime
	pending.totalTicks = totalTicks
	pending.tickInterval = pending.duration / totalTicks
	pending.spellID = spellID
	pending.isMultiTarget = (select("#", ...) / inc) > 1
	pending.bitType = CHANNEL_HEALS

	local ticksLeft = ceil((endTime - GetTime()) / pending.tickInterval)

	loadHealList(pending, amount, 1, endTime, ticksLeft, ...)
	HealComm.callbacks:Fire("HealComm_HealStarted", casterGUID, spellID, pending.bitType, pending.endTime, unpack(tempPlayerList))
end

-- Hot heal started
-- When the person is within visible range of us, the aura is available by the time the message reaches the target
-- as such, we can rely that at least one person is going to have the aura data on them (and that it won't be different, at least for this cast)
local function findAura(casterGUID, spellID, ...)
	for i = 1, select("#", ...) do
		local guid = decompressGUID[select(i, ...)]
		local unit = guid and guidToUnit[guid]
		if( unit and UnitIsVisible(unit) ) then
			local id = 1
			while true do
				local name, _, stack, _, duration, endTime, caster, _, _, spell = UnitAura(unit, id, 'HELPFUL')
				if( not spell ) then break end

				if( spell == spellID and caster and UnitGUID(caster) == casterGUID ) then
					return (stack and stack > 0 and stack or 1), duration or 0, endTime or 0
				end

				id = id + 1
			end
		end
	end
end

local function parseHotHeal(casterGUID, wasUpdated, spellID, tickAmount, totalTicks, tickInterval, ...)
	local spellName = GetSpellInfo(spellID)
	-- If the user is on 3.3, then anything without a total ticks attached to it is rejected
	if( not tickInterval or not tickAmount or not spellName or select("#", ...) == 0 ) then return end

	if type(tickAmount) == "table" then
		tickAmount = table.concat(tickAmount, "@")
	end
	-- Retrieve the hot information
	local inc = 2
	local stack, duration, endTime = findAura(casterGUID, spellID, ...)

	if not ( tickAmount == -1 or tickAmount == "-1" ) then
		inc = 1
		duration = totalTicks * (tickInterval or 1)
		endTime = GetTime() + duration
	end

	if( not stack or stack == 0 or duration == 0 or endTime == 0 ) then return end

	pendingHots[casterGUID] = pendingHots[casterGUID] or {}
	pendingHots[casterGUID][spellName] = pendingHots[casterGUID][spellName] or {}

	local pending = pendingHots[casterGUID][spellName]
	pending.duration = duration
	pending.endTime = endTime
	pending.stack = stack
	pending.totalTicks = totalTicks or duration / tickInterval
	pending.tickInterval = totalTicks and duration / totalTicks or tickInterval
	pending.spellID = spellID
	pending.hasVariableTicks = type(tickAmount) == "string"
	pending.isMutliTarget = (select("#", ...) / inc) > 1
	pending.bitType = HOT_HEALS

	-- As you can't rely on a hot being the absolutely only one up, have to apply the total amount now :<
	local ticksLeft = ceil((endTime - GetTime()) / pending.tickInterval)
	loadHealList(pending, tickAmount, stack, endTime, ticksLeft, ...)

	if( not wasUpdated ) then
		HealComm.callbacks:Fire("HealComm_HealStarted", casterGUID, spellID, pending.bitType, endTime, unpack(tempPlayerList))
	else
		HealComm.callbacks:Fire("HealComm_HealUpdated", casterGUID, spellID, pending.bitType, endTime, unpack(tempPlayerList))
	end
end

local function parseHotBomb(casterGUID, wasUpdated, spellID, amount, ...)
	local spellName, spellRank = GetSpellInfo(spellID)
	if( not amount or not spellName or select("#", ...) == 0 ) then return end

	-- If we don't have a pending hot then there is no bomb as far as were concerned
	local hotPending = pendingHots[casterGUID] and pendingHots[casterGUID][spellName]
	if( not hotPending or not hotPending.bitType ) then return end
	hotPending.hasBomb = true

	pendingHeals[casterGUID] = pendingHeals[casterGUID] or {}
	pendingHeals[casterGUID][spellName] = pendingHeals[casterGUID][spellName] or {}

	local pending = pendingHeals[casterGUID][spellName]
	pending.endTime = hotPending.endTime
	pending.spellID = spellID
	pending.bitType = BOMB_HEALS
	pending.stack = (isWrath or isSoD) and hotPending.stack or 1 -- TBC Lifebloom bomb heal does not stack

	loadHealList(pending, amount, pending.stack, pending.endTime, nil, ...)

	if( not wasUpdated ) then
		HealComm.callbacks:Fire("HealComm_HealStarted", casterGUID, spellID, pending.bitType, pending.endTime, unpack(tempPlayerList))
	else
		HealComm.callbacks:Fire("HealComm_HealUpdated", casterGUID, spellID, pending.bitType, pending.endTime, unpack(tempPlayerList))
	end
end

-- Heal finished
local function parseHealEnd(casterGUID, pending, checkField, spellID, interrupted, ...)
	local spellName = GetSpellInfo(spellID)
	if( not spellName or not casterGUID ) then return end

	if( not pending ) then
		if pendingHeals[casterGUID] then
			pending = pendingHeals[casterGUID][spellName]
		end
		if (not pending) and pendingHots[casterGUID] then
			pending = pendingHots[casterGUID][spellName]
		end
	end
	if( not pending or not pending.bitType ) then return end

	wipe(tempPlayerList)

	if( select("#", ...) == 0 ) then
		for i=#(pending), 1, -5 do
			tinsert(tempPlayerList, pending[i - 4])
			removeRecord(pending, pending[i - 4])
		end
	else
		for i = 1, select("#", ...) do
			local guid = decompressGUID[select(i, ...)]

			if guid then
				tinsert(tempPlayerList, guid)
				removeRecord(pending, guid)
			end
		end
	end

	-- Double check and make sure we actually removed at least one person
	if( #(tempPlayerList) == 0 ) then return end

	-- Heals that also have a bomb associated to them have to end at this point, they will fire there own callback too
	local bombPending = pending.hasBomb and pendingHeals[casterGUID][spellName]
	if( bombPending and bombPending.bitType ) then
		parseHealEnd(casterGUID, bombPending, "name", spellID, interrupted, ...)
	end

	local bitType = pending.bitType
	-- Clear data if we're done
	if( #(pending) == 0 ) then wipe(pending) end

	HealComm.callbacks:Fire("HealComm_HealStopped", casterGUID, spellID, bitType, interrupted, unpack(tempPlayerList))
end

HealComm.parseHealEnd = parseHealEnd

-- Heal delayed
local function parseHealDelayed(casterGUID, startTimeRelative, endTimeRelative, spellID)
	local spellName = GetSpellInfo(spellID)
	local startTime = startTimeRelative + GetTime()
	local endTime = endTimeRelative + GetTime()
	if not casterGUID then return end
	local pending = (pendingHeals[casterGUID][spellName] or pendingHots[casterGUID][spellName])
	-- It's possible to get duplicate interrupted due to raid1 = party1, player = raid# etc etc, just block it here
	if( pending.endTime == endTime and pending.startTime == startTime ) then return end

	-- Casted heal
	if( pending.bitType == DIRECT_HEALS ) then
		pending.startTime = startTime
		pending.endTime = endTime
		-- Channel heal
	elseif( pending.bitType == CHANNEL_HEALS ) then
		pending.startTime = startTime
		pending.endTime = endTime
		pending.duration = endTime - startTime
		pending.tickInterval = pending.duration / pending.totalTicks
	else
		return
	end

	wipe(tempPlayerList)
	for i = 1, #(pending), 5 do
		pending[i + 3] = endTime
		tinsert(tempPlayerList, pending[i])
	end

	HealComm.callbacks:Fire("HealComm_HealDelayed", casterGUID, pending.spellID, pending.bitType, pending.endTime, unpack(tempPlayerList))
end

-- After checking around 150-200 messages in battlegrounds, server seems to always be passed (if they are from another server)
-- Channels use tick total because the tick interval varies by haste
-- Hots use tick interval because the total duration varies but the tick interval stays the same
function HealComm:CHAT_MSG_ADDON(prefix, message, channel, sender)
	if( prefix ~= COMM_PREFIX or channel ~= distribution ) then return end

	local commType, extraArg, spellID, arg1, arg2, arg3, arg4, arg5, arg6 = strsplit(":", message)
	local casterGUID = UnitGUID(Ambiguate(sender, "none"))
	spellID = tonumber(spellID)

	if( not commType or not spellID or not casterGUID or casterGUID == playerGUID) then return end

	-- New direct heal - D:<extra>:<spellID>:<amount>:target1,target2...
	if( commType == "D" and arg1 and arg2 ) then
		parseDirectHeal(casterGUID, spellID, tonumber(arg1), tonumber(extraArg), strsplit(",", arg2))
		-- New channel heal - C:<extra>:<spellID>:<amount>:<totalTicks>:target1,target2...
	elseif( commType == "C" and arg1 and arg3 ) then
		-- Penance heals its first tick instantly and by now has already happened
		parseChannelHeal(casterGUID, spellID, tonumber(arg1), tonumber(arg2), strsplit(",", arg3))
		-- New hot with a "bomb" component - B:<totalTicks>:<spellID>:<bombAmount>:target1,target2:<amount>:<isMulti>:<tickInterval>:target1,target2...
	elseif( commType == "B" and arg1 and arg6 ) then
		parseHotHeal(casterGUID, false, spellID, tonumber(arg3), tonumber(extraArg), tonumber(arg5), strsplit(",", arg6))
		parseHotBomb(casterGUID, false, spellID, tonumber(arg1), strsplit(",", arg2))
		-- New hot - H:<totalTicks>:<spellID>:<amount>:<isMulti>:<tickInterval>:target1,target2...
	elseif( commType == "H" and arg1 and arg4 ) then
		parseHotHeal(casterGUID, false, spellID, tonumber(arg1), tonumber(extraArg), tonumber(arg3), strsplit(",", arg4))
		-- New updated heal somehow before ending - U:<totalTicks>:<spellID>:<amount>:<tickInterval>:target1,target2...
	elseif( commType == "U" and arg1 and arg3 ) then
		parseHotHeal(casterGUID, true, spellID, tonumber(arg1), tonumber(extraArg), tonumber(arg2), strsplit(",", arg3))
		-- New variable tick hot - VH::<spellID>:<amount>:<isMulti>:<tickInterval>:target1,target2...
	elseif( commType == "VH" and arg1 and arg4 ) then
		parseHotHeal(casterGUID, false, spellID, arg1, tonumber(arg3), tonumber(extraArg), string.split(",", arg4))
		-- New updated variable tick hot - U::<spellID>:amount1@amount2@amount3:<tickTotal>:target1,target2...
	elseif( commType == "VU" and arg1 and arg3 ) then
		parseHotHeal(casterGUID, true, spellID, arg1, tonumber(arg2), tonumber(extraArg), string.split(",", arg3))
		-- New updated bomb hot - UB:<totalTicks>:<spellID>:<bombAmount>:target1,target2:<amount>:<tickInterval>:target1,target2...
	elseif( commType == "UB" and arg1 and arg5 ) then
		parseHotHeal(casterGUID, true, spellID, tonumber(arg3), tonumber(extraArg), tonumber(arg4), strsplit(",", arg5))
		parseHotBomb(casterGUID, true, spellID, tonumber(arg1), strsplit(",", arg2))
		-- Heal stopped - S:<extra>:<spellID>:<ended early: 0/1>:target1,target2...
	elseif( commType == "S" or commType == "HS" ) then
		local interrupted = arg1 == "1" and true or false
		local checkType = commType == "HS" and "id" or "name"
		local pending = commType == "HS" and pendingHots[casterGUID] and pendingHots[casterGUID][GetSpellInfo(spellID)]

		if( arg2 and arg2 ~= "" ) then
			parseHealEnd(casterGUID, pending, checkType, spellID, interrupted, strsplit(",", arg2))
		else
			parseHealEnd(casterGUID, pending, checkType, spellID, interrupted)
		end
	elseif commType == "F" then
		parseHealDelayed(casterGUID, tonumber(arg1), tonumber(arg2), spellID)
	end
end

-- Bucketing reduces the number of events triggered for heals such as Tranquility that hit multiple targets
-- instead of firing 5 events * ticks it will fire 1 (maybe 2 depending on lag) events
HealComm.bucketHeals = HealComm.bucketHeals or {}
local bucketHeals = HealComm.bucketHeals
local BUCKET_FILLED = 0.30

HealComm.bucketFrame = HealComm.bucketFrame or CreateFrame("Frame")
HealComm.bucketFrame:Hide()

HealComm.bucketFrame:SetScript("OnUpdate", function(self, elapsed)
	local totalLeft = 0
	for casterGUID, spells in pairs(bucketHeals) do
		for _, data in pairs(spells) do
			if( data.timeout ) then
				data.timeout = data.timeout - elapsed
				if( data.timeout <= 0 ) then
					-- This shouldn't happen, on the offhand chance it does then don't bother sending an event
					if( #(data) == 0 or not data.spellID or not data.spellName ) then
						wipe(data)
						-- We're doing a bucket for a tick heal like Tranquility or Wild Growth
					elseif( data.type == "tick" ) then
						local pending = pendingHots[casterGUID] and pendingHots[casterGUID][data.spellName]
						if( pending and pending.bitType ) then
							local endTime = select(3, getRecord(pending, data[1]))
							HealComm.callbacks:Fire("HealComm_HealUpdated", casterGUID, pending.spellID, pending.bitType, endTime, unpack(data))
						end

						wipe(data)
						-- We're doing a bucket for a cast thats a multi-target heal like Wild Growth or Prayer of Healing
					elseif( data.type == "heal" ) then
						local bitType, amount, totalTicks, tickInterval, _, hasVariableTicks = CalculateHotHealing(data[1], data.spellID)
						if( bitType ) then
							local targets = GetHealTargets(bitType, data[1], data.spellID, data)
							parseHotHeal(playerGUID, false, data.spellID, amount, totalTicks, tickInterval, strsplit(",", targets))

							if( not hasVariableTicks ) then
								sendMessage(format("H:%d:%d:%d::%d:%s", totalTicks, data.spellID, amount, tickInterval, targets))
							else
								sendMessage(format("VH:%d:%d:%s::%d:%s", tickInterval, data.spellID, table.concat(amount, "@"), totalTicks, targets))
							end
						end

						wipe(data)
					end
				else
					totalLeft = totalLeft + 1
				end
			end
		end
	end

	if( totalLeft <= 0 ) then
		self:Hide()
	end
end)

-- Monitor aura changes as well as new hots being cast
local eventRegistered = {
	SPELL_HEAL = true,
	SPELL_PERIODIC_HEAL = true,
	SPELL_AURA_REMOVED = true,
	SPELL_AURA_APPLIED = true,
	SPELL_AURA_REFRESH = true,
	SPELL_AURA_APPLIED_DOSE = true,
	SPELL_AURA_REMOVED_DOSE = true,
}

function HealComm:COMBAT_LOG_EVENT_UNFILTERED(...)
	local timestamp, eventType, hideCaster, sourceGUID, sourceName, sourceFlags, sourceRaidFlags, destGUID, destName, destFlags, destRaidFlags = ...
	if( not eventRegistered[eventType] ) then return end

	local _, spellName = select(12, ...)
	local destUnit = guidToUnit[destGUID]

	if not destUnit then return end -- We only handle own group units

	local spellID = destUnit and select(10, unitHasAura(destUnit, spellName)) or select(7, GetSpellInfo(spellName))

	-- Heal or hot ticked that the library is tracking
	-- It's more efficient/accurate to have the library keep track of this locally, spamming the comm channel would not be a very good thing especially when a single player can have 4 - 8 hots/channels going on them.
	if( eventType == "SPELL_HEAL" or eventType == "SPELL_PERIODIC_HEAL" ) then
		local pending = sourceGUID and pendingHots[sourceGUID] and pendingHots[sourceGUID][spellName]
		if( pending and pending[destGUID] and pending.bitType and bit.band(pending.bitType, OVERTIME_HEALS) > 0 ) then
			local amount, stack, _, ticksLeft = getRecord(pending, destGUID)
			ticksLeft = ticksLeft - 1
			local endTime = GetTime() + pending.tickInterval * ticksLeft

			updateRecord(pending, destGUID, amount, stack, endTime, ticksLeft)

			if( pending.isMultiTarget and sourceGUID ) then
				bucketHeals[sourceGUID] = bucketHeals[sourceGUID] or {}
				bucketHeals[sourceGUID][spellName] = bucketHeals[sourceGUID][spellName] or {}

				local spellBucket = bucketHeals[sourceGUID][spellName]
				if( not spellBucket[destGUID] ) then
					spellBucket.timeout = BUCKET_FILLED
					spellBucket.type = "tick"
					spellBucket.spellName = spellName
					spellBucket.spellID = spellID
					spellBucket[destGUID] = true
					tinsert(spellBucket, destGUID)

					self.bucketFrame:Show()
				end
			else
				HealComm.callbacks:Fire("HealComm_HealUpdated", sourceGUID, spellID, pending.bitType, endTime, destGUID)
			end
		end

		-- New hot was applied
	elseif( ( eventType == "SPELL_AURA_APPLIED" or eventType == "SPELL_AURA_REFRESH" or eventType == "SPELL_AURA_APPLIED_DOSE" ) and bit.band(sourceFlags, COMBATLOG_OBJECT_AFFILIATION_MINE) == COMBATLOG_OBJECT_AFFILIATION_MINE ) then
		if( hotData[spellName] ) then
			-- Single target so we can just send it off now thankfully
			local bitType, amount, totalTicks, tickInterval, bombAmount, hasVariableTicks = CalculateHotHealing(destGUID, spellID)
			if( bitType ) then
				local targets = GetHealTargets(type, destGUID, spellID)
				if targets then
					parseHotHeal(sourceGUID, false, spellID, amount, totalTicks, tickInterval, strsplit(",", targets))

					-- Hot with a bomb!
					if( bombAmount ) then
						local bombTargets = GetHealTargets(BOMB_HEALS, destGUID, spellName)
						parseHotBomb(sourceGUID, false, spellID, bombAmount, strsplit(",", bombTargets))
						sendMessage(format("B:%d:%d:%d:%s:%d::%d:%s", totalTicks, spellID, bombAmount, bombTargets, amount, tickInterval, targets))
					elseif( hasVariableTicks ) then
						sendMessage(format("VH:%d:%d:%s::%d:%s", tickInterval, spellID, table.concat(amount, "@"), totalTicks, targets))
					else
						sendMessage(format("H:%d:%d:%d::%d:%s", totalTicks, spellID, amount, tickInterval, targets))
					end
				end
			end
		end
		-- Single stack of a hot was removed, this only applies when going from 2 -> 1, when it goes from 1 -> 0 it fires SPELL_AURA_REMOVED
	elseif( eventType == "SPELL_AURA_REMOVED_DOSE" and bit.band(sourceFlags, COMBATLOG_OBJECT_AFFILIATION_MINE) == COMBATLOG_OBJECT_AFFILIATION_MINE ) then
		local pending = sourceGUID and pendingHeals[sourceGUID] and pendingHeals[sourceGUID][spellID]
		if( pending and pending.bitType ) then
			local amount = getRecord(pending, destGUID)
			if( amount ) then
				parseHotHeal(sourceGUID, true, spellID, amount, pending.totalTicks, pending.tickInterval, compressGUID[destGUID])

				-- Plant the bomb
				local bombPending = pending.hasBomb and pendingHeals[sourceGUID][spellName]
				if( bombPending and bombPending.bitType ) then
					local bombAmount = getRecord(bombPending, destGUID)
					if( bombAmount ) then
						parseHotBomb(sourceGUID, true, spellID, bombAmount, compressGUID[destGUID])

						sendMessage(format("UB:%s:%d:%d:%s:%d:%d:%s", pending.totalTicks, spellID, bombAmount, compressGUID[destGUID], amount, pending.tickInterval, compressGUID[destGUID]))
						return
					end
				end

				if( pending.hasVariableTicks ) then
					sendMessage(format("VU:%d:%d:%s:%d:%s", pending.tickInterval, spellID, amount, pending.totalTicks, compressGUID[destGUID]))
				else
					sendMessage(format("U:%s:%d:%d:%d:%s", spellID, amount, pending.totalTicks, pending.tickInterval, compressGUID[destGUID]))
				end
			end
		end
		-- Aura faded
	elseif( eventType == "SPELL_AURA_REMOVED" and bit.band(sourceFlags, COMBATLOG_OBJECT_AFFILIATION_MINE) == COMBATLOG_OBJECT_AFFILIATION_MINE ) then
		if compressGUID[destGUID] then
			-- Hot faded that we cast
			local pending = pendingHots[playerGUID] and pendingHots[playerGUID][spellName]
			if hotData[spellName] then
				-- Healing Rain aura affects all targets even if they do not receive the heal
				-- Do not send message unless they are in same group
				if( isSoD and (spellName == GetSpellInfo(415236) or spellName == "Healing Rain") and guidToGroup[healingRainTargetGUID] ~= guidToGroup[destGUID] ) then
					return
				end

				parseHealEnd(sourceGUID, pending, "id", spellID, false, compressGUID[destGUID])
				sendMessage(format("HS::%d::%s", spellID, compressGUID[destGUID]))
			elseif spellData[spellName] and spellData[spellName]._isChanneled then
				parseHealEnd(sourceGUID, pending, "id", spellID, false, compressGUID[destGUID])
				sendMessage(format("S::%d:0:%s", spellID, compressGUID[destGUID]))
			end
		end
	end
end

-- Spell cast magic
-- When auto self cast is on, the UNIT_SPELLCAST_SENT event will always come first followed by the funciton calls
-- Otherwise either SENT comes first then function calls, or some function calls then SENT then more function calls
local castTarget, mouseoverGUID, mouseoverName, hadTargetingCursor, lastSentID, lastTargetGUID, lastTargetName
local lastFriendlyGUID, lastFriendlyName, lastGUID, lastName, lastIsFriend
local castGUIDs, guidPriorities = {}, {}

-- Deals with the fact that functions are called differently
-- Why a table when you can only cast one spell at a time you ask? When you factor in lag and mash clicking it's possible to:
-- cast A, interrupt it, cast B and have A fire SUCEEDED before B does, the tables keeps it from bugging out
local function setCastData(priority, name, guid)
	if( not guid or not lastSentID ) then return end
	if( guidPriorities[lastSentID] and guidPriorities[lastSentID] >= priority ) then return end

	-- This is meant as a way of locking a cast in because which function has accurate data can be called into question at times, one of them always does though
	-- this means that as soon as it finds a name match it locks the GUID in until another SENT is fired. Technically it's possible to get a bad GUID but it first requires
	-- the functions to return different data and it requires the messed up call to be for another name conflict.
	if( castTarget and castTarget == name ) then priority = 99 end

	castGUIDs[lastSentID] = guid
	guidPriorities[lastSentID] = priority
end

-- Penance sends different spellIDs on UNIT_SPELLCAST_SENT and UNIT_SPELLCAST_START. Reported on beta / remove this once fixed.
local penanceIDs = {
	[47540] = 47757,
	[53005] = 52986,
	[53006] = 52987,
	[53007] = 52988,
	[402174] = 402277,
}
-- When the game tries to figure out the UnitID from the name it will prioritize players over non-players
-- if there are conflicts in names it will pull the one with the least amount of current health
function HealComm:UNIT_SPELLCAST_SENT(unit, targetName, castGUID, spellID)
	local spellName = GetSpellInfo(spellID)
	if( unit ~= "player" ) then return end

	if hotData[spellName] or spellData[spellName] then
		targetName = targetName or UnitName("player")

		castTarget = gsub(targetName, "(.-)%-(.*)$", "%1")
		lastSentID = penanceIDs[spellID] or spellID

		-- Self cast is off which means it's possible to have a spell waiting for a target.
		-- It's possible that it's the mouseover unit, but if a Target, TargetLast or AssistUnit call comes right after it means it's casting on that instead instead.
		if( hadTargetingCursor ) then
			hadTargetingCursor = nil
			self.resetFrame:Show()

			guidPriorities[lastSentID] = nil
			setCastData(5, mouseoverName, mouseoverGUID)
		else
			-- If the player is ungrouped and healing, you can't take advantage of the name -> "unit" map, look in the UnitIDs that would most likely contain the information that's needed.
			local guid = UnitGUID(targetName)
			if( not guid ) then
				guid = UnitName("target") == castTarget and UnitGUID("target") or UnitName("focus") == castTarget and UnitGUID("focus") or UnitName("mouseover") == castTarget and UnitGUID("mouseover") or UnitName("targettarget") == castTarget and UnitGUID("target") or UnitName("focustarget") == castTarget and UnitGUID("focustarget")
			end

			guidPriorities[lastSentID] = nil
			setCastData(0, nil, guid)
		end
	end
end

local PlayerTargetSpells = {
	[GetSpellInfo(689)] = true, -- Drain Life
	[GetSpellInfo(740)] = true, -- Tranquility
	[GetSpellInfo(331)] = true, -- Healing Wave
}
if isTBC or isWrath then
	PlayerTargetSpells[GetSpellInfo(32546)] = true -- Binding Heal
end
function HealComm:UNIT_SPELLCAST_START(unit, cast, spellID)
	local spellName = GetSpellInfo(spellID)

	-- Save target of healing rain cast
	if( isSoD and (spellName == GetSpellInfo(415236) or spellName == "Healing Rain") ) then
		healingRainTargetGUID = castGUIDs[spellID]
	end

	if (not spellData[spellName] or UnitIsCharmed("player") or not UnitPlayerControlled("player") ) then return end

	local castGUID = castGUIDs[spellID]
	local castUnit = guidToUnit[castGUID]
	if (isTBC or isWrath) and not castUnit and PlayerTargetSpells[spellName] then
		castGUID = UnitGUID("player")
		castUnit = "player"
	elseif glyphCache[55440] and not castUnit and PlayerTargetSpells[spellName] then -- Glyph of Healing Wave
		castUnit = unit
	end
	if( not castGUID or not castUnit ) then
		return
	end

	-- Figure out who we are healing and for how much
	local bitType, amount, ticks, tickInterval = CalculateHealing(castGUID, spellID, castUnit)
	if not amount then return end

	local targets, fakeAmount = GetHealTargets(bitType, castGUID, spellID, amount)
	amount = fakeAmount or amount
	if not targets then return end -- only here until I compress/decompress npcs

	if( bitType == DIRECT_HEALS ) then
		local startTime, endTime = select(4, CastingInfo())
		parseDirectHeal(playerGUID, spellID, amount, (endTime - startTime) / 1000, strsplit(",", targets))
		sendMessage(format("D:%.3f:%d:%d:%s", (endTime - startTime) / 1000, spellID or 0, amount or "", targets))
	elseif( bitType == CHANNEL_HEALS ) then
		parseChannelHeal(playerGUID, spellID, amount, ticks, string.split(",", targets))
		if( spellName == GetSpellInfo(53007) or (isSoD and spellName == GetSpellInfo(402174)) or spellName == "Penance" ) then
			-- Penance has its first tick already done by the time this arrives
			sendMessage(string.format("C::%d:%d:%s:%s", spellID, amount, ticks - 1, targets))
		else
			sendMessage(string.format("C::%d:%d:%s:%s", spellID, amount, ticks, targets))
		end
	end
end

HealComm.UNIT_SPELLCAST_CHANNEL_START = HealComm.UNIT_SPELLCAST_START

local spellCastSucceeded = {}
local function hasNS()
	local i = 1
	repeat
		local spellID = select(10, UnitBuff("player", i))
		if spellID == 17116 or spellID == 16188 then
			return true
		end
		i = i + 1
	until not spellID
end

function HealComm:UNIT_SPELLCAST_SUCCEEDED(unit, cast, spellID)
	local spellName = GetSpellInfo(spellID)

	if spellID == 20216 then
		hasDivineFavor = true
	end

	if spellData[spellName] and not spellData[spellName]._isChanneled and not hasNS() then
		hasDivineFavor = nil
		parseHealEnd(playerGUID, nil, "name", spellID, false)
		sendMessage(format("S::%d:0", spellID or 0))
		spellCastSucceeded[spellID] = true
	elseif spellName == GetSpellInfo(20473) then -- Holy Shock
		hasDivineFavor = nil
	end
end

function HealComm:UNIT_SPELLCAST_STOP(unit, castGUID, spellID)
	local spellName = GetSpellInfo(spellID)
	if( not spellData[spellName] or spellData[spellName]._isChanneled ) then return end

	if not spellCastSucceeded[spellID] then
		parseHealEnd(playerGUID, nil, "name", spellID, true)
		sendMessage(format("S::%d:1", spellID or 0))
	end

	spellCastSucceeded[spellID] = nil
end

function HealComm:UNIT_SPELLCAST_CHANNEL_STOP(unit, _, spellID)
	local spellName = GetSpellInfo(spellID)
	if( not spellData[spellName] ) then return end

	-- End heal if a Penance cast is stopped prematurely (e.g. by movement)
	if( spellName == GetSpellInfo(53007) or (isSoD and spellName == GetSpellInfo(402174)) or spellName == "Penance" ) then
		parseHealEnd(playerGUID, nil, "name", spellID, true)
		sendMessage(format("S::%d:1", spellID or 0))
	end
end

-- Cast didn't go through, recheck any charge data if necessary
function HealComm:UNIT_SPELLCAST_INTERRUPTED(unit, castGUID, spellID)
	local spellName = GetSpellInfo(spellID)
	if( not spellData[spellName] ) then return end

	local guid = castGUIDs[spellID]
	if( guid ) then
		ResetChargeData(guid, spellID)
	end
end

function HealComm:UNIT_SPELLCAST_DELAYED(unit, castGUID, spellID)
	local spellName = GetSpellInfo(spellID)
	local casterGUID = UnitGUID(unit)
	if( not pendingHeals[casterGUID] or not pendingHeals[casterGUID][spellName] ) then return end

	-- Direct heal delayed
	if( pendingHeals[casterGUID][spellName].bitType == DIRECT_HEALS ) then
		local startTime, endTime = select(4, CastingInfo())
		if( startTime and endTime ) then
			local startTimeRelative = startTime / 1000 - GetTime()
			local endTimeRelative = endTime / 1000 - GetTime()
			parseHealDelayed(casterGUID, startTimeRelative, endTimeRelative, spellID)
			sendMessage(format("F::%d:%.3f:%.3f", spellID, startTimeRelative, endTimeRelative))
		end
		-- Channel heal delayed
	elseif( pendingHeals[casterGUID][spellName].bitType == CHANNEL_HEALS ) then
		local startTime, endTime = select(4, ChannelInfo())
		if( startTime and endTime ) then
			local startTimeRelative = startTime / 1000 - GetTime()
			local endTimeRelative = endTime / 1000 - GetTime()
			parseHealDelayed(casterGUID, startTimeRelative, endTimeRelative, spellID)
			sendMessage(format("F::%d:%.3f:%.3f", spellID, startTimeRelative, endTimeRelative))
		end
	end
end

HealComm.UNIT_SPELLCAST_CHANNEL_UPDATE = HealComm.UNIT_SPELLCAST_DELAYED

-- Need to keep track of mouseover as it can change in the split second after/before casts
function HealComm:UPDATE_MOUSEOVER_UNIT()
	mouseoverGUID = UnitCanAssist("player", "mouseover") and UnitGUID("mouseover")
	mouseoverName = UnitCanAssist("player", "mouseover") and UnitName("mouseover")
end

-- Keep track of our last target/friendly target for the sake of /targetlast and /targetlastfriend
function HealComm:PLAYER_TARGET_CHANGED()
	if( lastGUID and lastName ) then
		if( lastIsFriend ) then
			lastFriendlyGUID, lastFriendlyName = lastGUID, lastName
		end

		lastTargetGUID, lastTargetName = lastGUID, lastName
	end

	-- Despite the fact that it's called target last friend, UnitIsFriend won't actually work
	lastGUID = UnitGUID("target")
	lastName = UnitName("target")
	lastIsFriend = UnitCanAssist("player", "target")
end

-- Unit was targeted through a function
function HealComm:Target(unit)
	if( self.resetFrame:IsShown() and UnitCanAssist("player", unit) ) then
		setCastData(6, UnitName(unit), UnitGUID(unit))
	end

	self.resetFrame:Hide()
	hadTargetingCursor = nil
end

-- This is only needed when auto self cast is off, in which case this is called right after UNIT_SPELLCAST_SENT
-- because the player got a waiting-for-cast icon up and they pressed a key binding to target someone
HealComm.TargetUnit = HealComm.Target

-- Works the same as the above except it's called when you have a cursor icon and you click on a secure frame with a target attribute set
HealComm.SpellTargetUnit = HealComm.Target

-- Used in /assist macros
function HealComm:AssistUnit(unit)
	if( self.resetFrame:IsShown() and UnitCanAssist("player", unit .. "target") ) then
		setCastData(6, UnitName(unit .. "target"), UnitGUID(unit .. "target"))
	end

	self.resetFrame:Hide()
	hadTargetingCursor = nil
end

-- Target last was used, the only reason this is called with reset frame being shown is we're casting on a valid unit
-- don't have to worry about the GUID no longer being invalid etc
function HealComm:TargetLast(guid, name)
	if( name and guid and self.resetFrame:IsShown() ) then
		setCastData(6, name, guid)
	end

	self.resetFrame:Hide()
	hadTargetingCursor = nil
end

function HealComm:TargetLastFriend()
	self:TargetLast(lastFriendlyGUID, lastFriendlyName)
end

function HealComm:TargetLastTarget()
	self:TargetLast(lastTargetGUID, lastTargetName)
end

-- Spell was cast somehow
function HealComm:CastSpell(arg, unit)

	-- Macro Heals with /stopcasting do not trigger UNIT_SPELLCAST_SENT
	-- so we must set lastSentID here
	local spellID
	if tonumber(arg) then spellID = arg else spellID = select(7, GetSpellInfo(arg)) end
	local spellName = GetSpellInfo(spellID)
	if spellName and (hotData[spellName] or spellData[spellName]) then
		lastSentID = penanceIDs[spellID] or spellID
		guidPriorities[lastSentID] = nil
	end

	-- If the spell is waiting for a target and it's a spell action button then we know that the GUID has to be mouseover or a key binding cast.
	if( unit and UnitCanAssist("player", unit)  ) then
		setCastData(4, UnitName(unit), UnitGUID(unit))
		-- No unit, or it's a unit we can't assist
	elseif( not SpellIsTargeting() ) then
		if( UnitCanAssist("player", "target") ) then
			setCastData(4, UnitName("target"), UnitGUID("target"))
		else
			setCastData(4, playerName, playerGUID)
		end

		hadTargetingCursor = nil
	else
		hadTargetingCursor = true
	end
end

HealComm.CastSpellByName = HealComm.CastSpell
HealComm.CastSpellByID = HealComm.CastSpell
HealComm.UseAction = HealComm.CastSpell

-- Make sure we don't have invalid units in this
local function sanityCheckMapping()
	for guid, unit in pairs(guidToUnit) do
		-- Unit no longer exists, remove all healing for them
		if guid ~= UnitGUID(unit) then
			-- Check for (and remove) any active heals
			for _, tbl in pairs({ pendingHeals, pendingHots }) do
				if tbl[guid] then
					for _, pending in pairs(tbl[guid]) do
						if( pending.bitType ) then
							parseHealEnd(guid, pending, nil, pending.spellID, true)
						end
					end
				end
			end

			pendingHeals[guid] = nil
			pendingHots[guid] = nil

			-- Remove any heals that are on them
			removeAllRecords(guid)

			guidToUnit[guid] = nil
			guidToGroup[guid] = nil
		end
	end
end

-- 5s poll that tries to solve the problem of X running out of range while a HoT is ticking
-- this is not really perfect far from it in fact. If I can find a better solution I will switch to that.
if( not HealComm.hotMonitor ) then
	HealComm.hotMonitor = CreateFrame("Frame")
	HealComm.hotMonitor:Hide()
	HealComm.hotMonitor.timeElapsed = 0
	HealComm.hotMonitor:SetScript("OnUpdate", function(self, elapsed)
		self.timeElapsed = self.timeElapsed + elapsed
		if( self.timeElapsed < 5 ) then return end
		self.timeElapsed = self.timeElapsed - 5

		-- For the time being, it will only remove them if they don't exist and it found a valid unit
		-- units that leave the raid are automatically removed
		local found
		for guid in pairs(activeHots) do
			if( guidToUnit[guid] and not UnitIsVisible(guidToUnit[guid]) ) then
				removeAllRecords(guid)
			else
				found = true
			end
		end

		if( not found ) then
			self:Hide()
		end
	end)
end

-- After the player leaves a group, tables are wiped out or released for GC
local function clearGUIDData()
	clearPendingHeals()

	wipe(compressGUID)
	wipe(decompressGUID)
	wipe(activePets)

	playerGUID = playerGUID or UnitGUID("player")
	HealComm.guidToUnit = {[playerGUID] = "player"}
	guidToUnit = HealComm.guidToUnit

	HealComm.guidToGroup = {}
	guidToGroup = HealComm.guidToGroup

	HealComm.activeHots = {}
	activeHots = HealComm.activeHots

	HealComm.pendingHeals = {}
	pendingHeals = HealComm.pendingHeals

	HealComm.pendingHots = {}
	pendingHots = HealComm.pendingHots

	HealComm.bucketHeals = {}
	bucketHeals = HealComm.bucketHeals
end

-- Keeps track of pet GUIDs, as pets are considered vehicles this will also map vehicle GUIDs to unit
function HealComm:UNIT_PET(unit)
	local guid = UnitGUID(unit)
	unit = guidToUnit[guid]

	if not unit then return end

	local pet = self.unitToPet[unit]
	local petGUID = pet and UnitGUID(pet)

	-- We have an active pet guid from this user and it's different, kill it
	local activeGUID = activePets[unit]
	if activeGUID and petGUID and activeGUID ~= petGUID then
		removeAllRecords(activeGUID)

		rawset(self.compressGUID, activeGUID, nil)
		rawset(self.decompressGUID, "p-" ..  strsub(guid, 8), nil)
		guidToUnit[activeGUID] = nil
		guidToGroup[activeGUID] = nil
		activePets[unit] = nil
	end

	-- Add the new record
	if petGUID then
		guidToUnit[petGUID] = pet
		guidToGroup[petGUID] = guidToGroup[guid]
		activePets[unit] = petGUID
	end
end

-- Keep track of raid GUIDs
function HealComm:GROUP_ROSTER_UPDATE()
	updateDistributionChannel()

	wipe(activePets)

	local function update(unit)
		local guid = UnitGUID(unit)

		if guid then
			local raidID = UnitInRaid(unit)
			local group = raidID and select(3, GetRaidRosterInfo(raidID)) or 1

			guidToUnit[guid] = unit
			guidToGroup[guid] = group

			local pet = self.unitToPet[unit]
			local petGUID = pet and UnitGUID(pet)

			activePets[unit] = petGUID

			if petGUID then
				guidToUnit[petGUID] = pet
				guidToGroup[petGUID] = group
			end
		end
	end

	if GetNumGroupMembers() == 0 then
		clearGUIDData()
		update("player")
	elseif not IsInRaid() then
		update("player")

		for i = 1, MAX_PARTY_MEMBERS do
			update(format("party%d", i))
		end
	else
		for i = 1, MAX_RAID_MEMBERS do
			update(format("raid%d", i))
		end
	end

	sanityCheckMapping()
end

-- PLAYER_ALIVE = got talent data
function HealComm:PLAYER_ALIVE()
	self:CHARACTER_POINTS_CHANGED()
	self.eventFrame:UnregisterEvent("PLAYER_ALIVE")
end

-- Initialize the library
function HealComm:OnInitialize()
	-- If another instance already loaded then the tables should be wiped to prevent old data from persisting
	-- in case of a spell being removed later on, only can happen if a newer LoD version is loaded
	wipe(spellData)
	wipe(hotData)
	wipe(itemSetsData)
	wipe(talentData)

	-- Load all of the classes formulas and such
	if LoadClassData then
		LoadClassData()
	end

	do
		local FirstAid = GetSpellInfo(746)

		spellData[FirstAid] = {
			_isChanneled = true,
			ticks = {6, 6, 7, 7, 8, 8, 8, 8, 8, 8, 8, 8, 8, 8},
			interval = 1,
			averages = {66, 114, 161, 301, 400, 640, 800, 1104, 1360, 2000, 2800, 3400, 4800, 5800}
		}

		local _GetHealTargets = GetHealTargets

		GetHealTargets = function(bitType, guid, spellID, amount)
			local spellName = GetSpellInfo(spellID)

			if spellName == FirstAid then
				return compressGUID[guid]
			end

			if _GetHealTargets then
				return _GetHealTargets(bitType, guid, spellID, amount)
			end
		end

		local _CalculateHealing = CalculateHealing

		CalculateHealing = function(guid, spellID, unit)
			local spellName, spellRank = GetSpellInfo(spellID), SpellIDToRank[spellID]

			if spellName == FirstAid then
				local healAmount =  spellData[spellName].averages[spellRank]
				if not healAmount then return end

				local ticks = spellData[spellName].ticks[spellRank]

				return CHANNEL_HEALS, ceil(healAmount / ticks), ticks, spellData[spellName].interval
			end

			if _CalculateHealing then
				return _CalculateHealing(guid, spellID, unit)
			end
		end
	end

	-- Cache glyphs initially
	for id = 1, GetNumGlyphSockets() do
		local enabled, _, glyphID = GetGlyphSocketInfo(id)
		if( enabled and glyphID ) then
			glyphCache[glyphID] = true
			glyphCache[id] = glyphID
		end
	end

	self:PLAYER_EQUIPMENT_CHANGED()

	-- When first logging in talent data isn't available until at least PLAYER_ALIVE, so if we don't have data
	-- will wait for that event otherwise will just cache it right now
	if( GetNumTalentTabs() == 0 ) then
		self.eventFrame:RegisterEvent("PLAYER_ALIVE")
	else
		self:CHARACTER_POINTS_CHANGED()
	end

	if( ResetChargeData ) then
		HealComm.eventFrame:RegisterUnitEvent("UNIT_SPELLCAST_INTERRUPTED", "player", "")
	end

	-- Finally, register it all
	self.eventFrame:RegisterEvent("CHAT_MSG_ADDON")
	self.eventFrame:RegisterEvent("UNIT_SPELLCAST_SENT")
	self.eventFrame:RegisterUnitEvent("UNIT_SPELLCAST_START", "player", "")
	self.eventFrame:RegisterUnitEvent("UNIT_SPELLCAST_STOP", "player", "")
	self.eventFrame:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_START", "player", "")
	self.eventFrame:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_STOP", "player", "")
	self.eventFrame:RegisterUnitEvent("UNIT_SPELLCAST_DELAYED", "player", "")
	self.eventFrame:RegisterUnitEvent("UNIT_SPELLCAST_CHANNEL_UPDATE", "player", "")
	self.eventFrame:RegisterEvent("UNIT_SPELLCAST_SUCCEEDED", "player", "")
	self.eventFrame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
	self.eventFrame:RegisterEvent("PLAYER_EQUIPMENT_CHANGED")
	self.eventFrame:RegisterEvent("PLAYER_TARGET_CHANGED")
	self.eventFrame:RegisterEvent("UPDATE_MOUSEOVER_UNIT")
	self.eventFrame:RegisterEvent("PLAYER_LEVEL_UP")
	self.eventFrame:RegisterEvent("CHARACTER_POINTS_CHANGED")
	self.eventFrame:RegisterUnitEvent("UNIT_AURA", "player", "")
	if isWrath then
		self.eventFrame:RegisterEvent("GLYPH_ADDED")
		self.eventFrame:RegisterEvent("GLYPH_REMOVED")
		self.eventFrame:RegisterEvent("GLYPH_UPDATED")
	end

	if( self.initialized ) then return end
	self.initialized = true

	self.resetFrame = CreateFrame("Frame")
	self.resetFrame:Hide()
	self.resetFrame:SetScript("OnUpdate", function(self) self:Hide() end)

	-- You can't unhook secure hooks after they are done, so will hook once and the HealComm table will update with the latest functions
	-- automagically. If a new function is ever used it'll need a specific variable to indicate those set of hooks.
	-- By default most of these are mapped to a more generic function, but I call separate ones so I don't have to rehook
	-- if it turns out I need to know something specific
	hooksecurefunc("TargetUnit", function(...) HealComm:TargetUnit(...) end)
	hooksecurefunc("SpellTargetUnit", function(...) HealComm:SpellTargetUnit(...) end)
	hooksecurefunc("AssistUnit", function(...) HealComm:AssistUnit(...) end)
	hooksecurefunc("UseAction", function(...) HealComm:UseAction(...) end)
	hooksecurefunc("TargetLastFriend", function(...) HealComm:TargetLastFriend(...) end)
	hooksecurefunc("TargetLastTarget", function(...) HealComm:TargetLastTarget(...) end)
	hooksecurefunc("CastSpellByName", function(...) HealComm:CastSpellByName(...) end)
	hooksecurefunc("CastSpellByID", function(...) HealComm:CastSpellByID(...) end)
end

-- General event handler
local function OnEvent(self, event, ...)
	if event == 'COMBAT_LOG_EVENT_UNFILTERED' then
		HealComm[event](HealComm, CombatLogGetCurrentEventInfo())
	else
		HealComm[event](HealComm, ...)
	end
end

-- Event handler
HealComm.eventFrame = HealComm.frame or HealComm.eventFrame or CreateFrame("Frame")
HealComm.eventFrame:UnregisterAllEvents()
HealComm.eventFrame:RegisterEvent("UNIT_PET")
HealComm.eventFrame:SetScript("OnEvent", OnEvent)
HealComm.frame = nil

-- At PLAYER_LEAVING_WORLD (Actually more like MIRROR_TIMER_STOP but anyway) UnitGUID("player") returns nil, delay registering
-- events and set a playerGUID/playerName combo for all players on PLAYER_LOGIN not just the healers.
function HealComm:PLAYER_LOGIN()
	playerGUID = UnitGUID("player")
	playerName = UnitName("player")
	playerLevel = UnitLevel("player")

	-- Oddly enough player GUID is not available on file load, so keep the map of player GUID to themselves too
	guidToUnit[playerGUID] = "player"

	self:OnInitialize()

	self.eventFrame:UnregisterEvent("PLAYER_LOGIN")
	self.eventFrame:RegisterEvent("ZONE_CHANGED_NEW_AREA")
	self.eventFrame:RegisterEvent("PLAYER_ENTERING_WORLD")
	self.eventFrame:RegisterEvent("GROUP_ROSTER_UPDATE")

	self:ZONE_CHANGED_NEW_AREA()
	self:GROUP_ROSTER_UPDATE()
end

if( not IsLoggedIn() ) then
	HealComm.eventFrame:RegisterEvent("PLAYER_LOGIN")
else
	HealComm:PLAYER_LOGIN()
end
