---@class DBM
local DBM = DBM

---------------
--  Globals  --
---------------
---@class DBMInfoFrame
local infoFrame = {}
DBM.InfoFrame = infoFrame

-------------------
-- Local Globals --
-------------------
local isRetail = WOW_PROJECT_ID == (WOW_PROJECT_MAINLINE or 1)
local isWrath = WOW_PROJECT_ID == (WOW_PROJECT_WRATH_CLASSIC or 11)

local DDM, UIDropDownMenu_AddButton, UIDropDownMenu_Initialize, ToggleDropDownMenu
if isWrath then
	DDM = LibStub:GetLibrary("LibDropDownMenu")
	UIDropDownMenu_AddButton = DDM.UIDropDownMenu_AddButton
	UIDropDownMenu_Initialize = DDM.UIDropDownMenu_Initialize
	ToggleDropDownMenu = DDM.ToggleDropDownMenu
end

local L = DBM_CORE_L
local UnitClass, GetTime, GetPartyAssignment, UnitGroupRolesAssigned, GetRaidTargetIndex, UnitExists, UnitGetTotalAbsorbs, UnitName, UnitHealth, UnitPower, UnitPowerMax, UnitIsDeadOrGhost, UnitThreatSituation, UnitPosition, UnitIsUnit = UnitClass, GetTime, GetPartyAssignment, UnitGroupRolesAssigned, GetRaidTargetIndex, UnitExists, UnitGetTotalAbsorbs, UnitName, UnitHealth, UnitPower, UnitPowerMax, UnitIsDeadOrGhost, UnitThreatSituation, UnitPosition, UnitIsUnit
local error, tostring, type, pairs, ipairs, select, tonumber, tsort, twipe, mfloor, mmax, mmin, mrandom, schar, ssplit = error, tostring, type, pairs, ipairs, select, tonumber, table.sort, table.wipe, math.floor, math.max, math.min, math.random, string.char, string.split
local NORMAL_FONT_COLOR, SPELL_FAILED_OUT_OF_RANGE = NORMAL_FONT_COLOR, SPELL_FAILED_OUT_OF_RANGE
local RAID_CLASS_COLORS = _G["CUSTOM_CLASS_COLORS"] or RAID_CLASS_COLORS-- for Phanx' Class Colors

--Hard code STANDARD_TEXT_FONT since skinning mods like to taint it (or worse, set it to nil, wtf?)
local standardFont
if LOCALE_koKR then
	standardFont = "Fonts\\2002.TTF"
elseif LOCALE_zhCN then
	standardFont = "Fonts\\ARKai_T.ttf"
elseif LOCALE_zhTW then
	standardFont = "Fonts\\blei00d.TTF"
elseif LOCALE_ruRU then
	standardFont = "Fonts\\FRIZQT___CYR.TTF"
else
	standardFont = "Fonts\\FRIZQT__.TTF"
end

--------------
--  Locals  --
--------------
local frame, initializeDropdown, initializeDropdownLegacy, currentMapId, currentEvent, createFrame
local maxLines, modLines, maxCols, modCols, prevLines = 5, 5, 1, 1, 0
local sortMethod = 1--1 Default, 2 SortAsc, 3 GroupId
local lines, sortedLines, icons, value = {}, {}, {}, {}
local playerName = UnitName("player")
---@cast playerName string

---------------------
--  Dropdown Menu  --
---------------------
do
	local function toggleLocked()
		DBM.Options.InfoFrameLocked = not DBM.Options.InfoFrameLocked
	end

	local function isLocked()
		return DBM.Options.InfoFrameLocked
	end

	local function toggleShowSelf()
		DBM.Options.InfoFrameShowSelf = not DBM.Options.InfoFrameShowSelf
	end

	local function isShowSelf()
		return DBM.Options.InfoFrameShowSelf
	end

	local function setLines(arg1, line)
		if not isWrath then line = arg1 end -- New dropdown code
		prevLines = 0
		DBM.Options.InfoFrameLines = line
		if line ~= 0 then
			maxLines = line
		else
			maxLines = modLines or 5
		end
	end

	local function isLinesSelected(line)
		return DBM.Options.InfoFrameLines == line
	end

	local function setCols(arg1, col)
		if not isWrath then col = arg1 end -- New dropdown code
		prevLines = 0
		DBM.Options.InfoFrameCols = col
		if col ~= 0 then
			maxCols = col
		else
			maxCols = modCols or 5
		end
	end

	local function isColsSelected(col)
		return DBM.Options.InfoFrameCols == col
	end

	function initializeDropdown(owner, rootDescription)
		rootDescription:CreateCheckbox(LOCK_FRAME, isLocked, toggleLocked)
		rootDescription:CreateCheckbox(L.INFOFRAME_SHOW_SELF, isShowSelf, toggleShowSelf)

		local lines = rootDescription:CreateButton(L.INFOFRAME_SETLINES)
		for _, v in ipairs({ 0, 3, 5, 8, 10, 15, 20, isRetail and 30 or 40 }) do
			lines:CreateRadio(v == 0 and L.INFOFRAME_LINESDEFAULT or L.INFOFRAME_LINES_TO:format(v), isLinesSelected, setLines, v)
		end

		local cols = rootDescription:CreateButton(L.INFOFRAME_SETCOLS)
		for _, v in ipairs({ 0, 1, 2, 3, 4, 5, 6 }) do
			cols:CreateRadio(v == 0 and L.INFOFRAME_LINESDEFAULT or L.INFOFRAME_COLS_TO:format(v), isColsSelected, setCols, v)
		end

		rootDescription:CreateButton(HIDE, infoFrame.Hide)
	end

	function initializeDropdownLegacy(_, level, menu)
		if level == 1 then
			UIDropDownMenu_AddButton({
				text = LOCK_FRAME,
				keepShownOnClick = true,
				checked = isLocked(),
				func = toggleLocked
			}, 1)
			UIDropDownMenu_AddButton({
				text = L.INFOFRAME_SHOW_SELF,
				keepShownOnClick = true,
				checked = isShowSelf(),
				func = toggleShowSelf
			}, 1)
			UIDropDownMenu_AddButton({
				text = L.INFOFRAME_SETLINES,
				notCheckable = true,
				hasArrow = true,
				keepShownOnClick = true,
				menuList = "lines",
			}, 1)
			UIDropDownMenu_AddButton({
				text = L.INFOFRAME_SETCOLS,
				notCheckable = true,
				hasArrow = true,
				keepShownOnClick = true,
				menuList = "cols",
			}, 1)
			UIDropDownMenu_AddButton({
				text = HIDE,
				notCheckable = true,
				func = infoFrame.Hide,
				arg1 = infoFrame
			}, 1)
		elseif level == 2 then
			if menu == "lines" then
				for _, v in ipairs({ 0, 3, 5, 8, 10, 15, 20, isRetail and 30 or 40 }) do
					UIDropDownMenu_AddButton({
						text = v == 0 and L.INFOFRAME_LINESDEFAULT or L.INFOFRAME_LINES_TO:format(v),
						func = setLines,
						arg1 = v,
						checked = isLinesSelected(v)
					}, 2)
				end
			elseif menu == "cols" then
				for _, v in ipairs({ 0, 1, 2, 3, 4, 5, 6 }) do
					UIDropDownMenu_AddButton({
						text = v == 0 and L.INFOFRAME_LINESDEFAULT or L.INFOFRAME_COLS_TO:format(v),
						func = setCols,
						arg1 = v,
						checked = isColsSelected(v)
					}, 2)
				end
			end
		end
	end
end

------------------------
--  Create the frame  --
------------------------
function createFrame()
	---@class DBMInfoFrameFrame: Frame, BackdropTemplate
	frame = CreateFrame("Frame", "DBMInfoFrame", UIParent, "BackdropTemplate")
	frame:Hide()
	frame:SetFrameStrata("DIALOG")
	frame.backdropInfo = {
		bgFile		= "Interface\\DialogFrame\\UI-DialogBox-Background", -- 131071
		tile		= true,
		tileSize	= 16
	}
	frame:ApplyBackdrop()
	frame:SetPoint(DBM.Options.InfoFramePoint, UIParent, DBM.Options.InfoFramePoint, DBM.Options.InfoFrameX, DBM.Options.InfoFrameY)
	frame:SetSize(10, 10)
	frame:SetClampedToScreen(true)
	frame:EnableMouse(true)
	frame:SetToplevel(true)
	frame:SetMovable(true)
	frame:RegisterForDrag("LeftButton")
	frame:SetScript("OnDragStart", function(self)
		if not DBM.Options.InfoFrameLocked then
			self:StartMoving()
		end
	end)
	frame:SetScript("OnDragStop", function(self)
		self:StopMovingOrSizing()
		local point, _, _, x, y = self:GetPoint(1)
		DBM.Options.InfoFrameX = x
		DBM.Options.InfoFrameY = y
		DBM.Options.InfoFramePoint = point
	end)
	frame:SetScript("OnEvent", function(self, event, ...)
		if infoFrame[event] then
			infoFrame[event](self, ...)
		end
	end)
	frame:SetScript("OnMouseDown", function(self, button)
		if button == "RightButton" then
			if isWrath then
				-- no clue what is going on with DDM here
				---@diagnostic disable-next-line: param-type-mismatch
				local dropdownFrame = DDM.Create_DropDownMenu("Frame", "DBMInfoFrameDropdown", frame)
				---@diagnostic disable-next-line: param-type-mismatch
				UIDropDownMenu_Initialize(dropdownFrame, initializeDropdownLegacy)
				---@diagnostic disable-next-line: param-type-mismatch
				ToggleDropDownMenu(1, nil, dropdownFrame, "cursor", 5, -10)
			else
				MenuUtil.CreateContextMenu(frame, initializeDropdown)
			end
		end
	end)

	local header = frame:CreateFontString(nil, "OVERLAY", "GameFontNormal")
	header:SetTextColor(1, 1, 1)
	header:SetPoint("BOTTOMLEFT", frame, "TOPLEFT", 2, 2)
	frame.header = header
	infoFrame:SetHeader()

	frame.lines = {}
end

------------------------
--  Update functions  --
------------------------
local updateCallbacks = {}

local function sortFuncDesc(a, b)
	return lines[a] > lines[b]
end

local function sortFuncAsc(a, b)
	return lines[a] < lines[b]
end

local function sortGroupId(a, b)
	return DBM.GetGroupId(DBM, a) < DBM.GetGroupId(DBM, b)
end

local function updateLines(preSorted)
	twipe(sortedLines)
	if preSorted then
		-- Copy table as code from mod keeps around references this this and the "normal" table is wiped regularly
		for i, v in ipairs(preSorted) do
			sortedLines[i] = v
		end
	else
		for i in pairs(lines) do
			sortedLines[#sortedLines + 1] = i
		end
		if sortMethod == 3 then
			tsort(sortedLines, sortGroupId)
		elseif sortMethod == 2 then
			tsort(sortedLines, sortFuncAsc)
		else
			tsort(sortedLines, sortFuncDesc)
		end
	end
	for _, v in ipairs(updateCallbacks) do
		v(sortedLines)
	end
end

--[[
local function namesortFuncAsc(a, b)
	return a < b
end

local function updateNamesortLines()
	twipe(sortedLines)
	for i in pairs(lines) do
		sortedLines[#sortedLines + 1] = i
	end
	tsort(sortedLines, namesortFuncAsc)
	for _, v in ipairs(updateCallbacks) do
		v(sortedLines)
	end
end
]]--

local function updateLinesCustomSort(sortFunc)
	twipe(sortedLines)
	for i in pairs(lines) do
		sortedLines[#sortedLines + 1] = i
	end
	tsort(sortedLines, sortFunc)
	for _, v in ipairs(updateCallbacks) do
		v(sortedLines)
	end
end

local function updateIcons()
	twipe(icons)
	for uId in DBM:GetGroupMembers() do
		local icon = GetRaidTargetIndex(uId)
		local icon2 = GetRaidTargetIndex(uId .. "target")
		if icon and icon <= 8 then
			icons[DBM:GetUnitFullName(uId)] = icon
		end
		if icon2 and icon2 <= 8 then
			icons[DBM:GetUnitFullName(uId .. "target")] = icon2
		end
	end
	for i = 1, 10 do
		local icon = GetRaidTargetIndex("boss" .. i)
		if icon then
			icons[UnitName("boss" .. i)] = icon
		end
	end
end

local function updateHealth()
	twipe(lines)
	local threshold = value[1]
	for uId in DBM:GetGroupMembers() do
		if UnitHealth(uId) < threshold and not UnitIsDeadOrGhost(uId) then
			lines[DBM:GetUnitFullName(uId)] = UnitHealth(uId) - threshold
		end
	end
	updateLines()
	updateIcons()
end

local bossHealthSortedLines = {}
local function updateBossHealth()
	twipe(lines)
	twipe(bossHealthSortedLines)
	local bossHealth, localizedBossNames, bossIcons = DBM:GetCachedBossHealth()
	for cId, name in pairs(value[1]) do
		if type(name) ~= "string" then
			name = localizedBossNames[cId] or ("Boss " .. cId)
		end
		lines[name] = bossHealth[cId] or math.huge
		bossHealthSortedLines[#bossHealthSortedLines + 1] = name
	end
	tsort(bossHealthSortedLines, function(e1, e2)
		return lines[e1] > lines[e2]
	end)
	for name, hp in pairs(lines) do
		lines[name] = hp < math.huge and (math.ceil(hp) .. "%") or DBM_COMMON_L.UNKNOWN
	end
	updateLines(bossHealthSortedLines)
	updateIcons()
	-- updateIcons doesn't work 100% reliably for bosses in classic (no boss unit ids), so add cached data if updateIcons didn't find the boss.
	-- Example: no one is targeting the sheep in the Mechanical Menagerie fight while it has reflect up, without the code below the icon would disappear in this case.
	-- Also, this is necessary for bosses with customized names (e.g., Mechanical Menagerie)
	for cId, icon in pairs(bossIcons) do
		local renamedBoss = value[1][cId]
		local bossName = type(renamedBoss) == "string" and renamedBoss or localizedBossNames[cId]
		if bossName and not icons[bossName] then
			icons[bossName] = icon
		end
	end
end

local function updatePlayerPower()
	twipe(lines)
	local threshold = value[1]
	local powerType = value[2]
	local spellFilter = value[3]
	-- Value 4 is the noUpdate handler
	-- Value 5 is sorting method, handled in show handler
	for uId in DBM:GetGroupMembers() do
		if not spellFilter or not DBM:UnitDebuff(uId, spellFilter) then
			local maxPower = UnitPowerMax(uId, powerType)
			if maxPower ~= 0 and not UnitIsDeadOrGhost(uId) and UnitPower(uId, powerType) / UnitPowerMax(uId, powerType) * 100 >= threshold then
				lines[DBM:GetUnitFullName(uId)] = UnitPower(uId, powerType)
			end
		end
	end
	if DBM.Options.InfoFrameShowSelf and not lines[playerName] and UnitPower("player", powerType) > 0 then
		lines[playerName] = UnitPower("player", powerType)
	end
	updateLines()
	updateIcons()
end

local function updateEnemyPower()
	twipe(lines)
	local threshold = value[1]
	local powerType = value[2]
	local specificUnit = value[3]
	if powerType then -- Only do power type defined
		if specificUnit then
			if UnitExists(specificUnit) then
				local currentPower, maxPower = UnitPower(specificUnit, powerType), UnitPowerMax(specificUnit, powerType)
				if maxPower and maxPower > 0 then
					local percent = currentPower / maxPower * 100
					if percent >= threshold then
						lines[UnitName(specificUnit)] = mfloor(percent) .. "%"
					end
				end
			end
		else
			for i = 1, 10 do
				local uId = "boss" .. i
				local currentPower, maxPower = UnitPower(uId), UnitPowerMax(uId)
				if maxPower and maxPower > 0 then
					local percent = currentPower / maxPower * 100
					if percent >= threshold then
						lines[UnitName(uId)] = mfloor(percent) .. "%"
					end
				end
			end
		end
	else -- Check primary power type and alternate power types together. This should only be used if BOTH power types exist on same boss
		if specificUnit then
			if UnitExists(specificUnit) then
				-- Primary Power
				local currentPower, maxPower = UnitPower(specificUnit), UnitPowerMax(specificUnit)
				if maxPower and maxPower > 0 then
					local percent = currentPower / maxPower * 100
					if percent >= threshold then
						lines[UnitName(specificUnit)] = mfloor(percent) .. "%"
					end
				end
				-- Alternate Power
				local currentAltPower, maxAltPower = UnitPower(specificUnit, 10), UnitPowerMax(specificUnit, 10)
				if maxAltPower and maxAltPower > 0 then
					if currentAltPower / maxAltPower * 100 >= threshold then
						lines[UnitName(specificUnit)] = L.INFOFRAME_ALT .. currentAltPower
					end
				end
			end
		else
			for i = 1, 10 do
				local uId = "boss" .. i
				-- Primary Power
				local currentPower, maxPower = UnitPower(uId), UnitPowerMax(uId)
				if maxPower and maxPower > 0 then
					if currentPower / maxPower * 100 >= threshold then
						lines[UnitName(uId)] = currentPower
					end
				end
				-- Alternate Power
				local currentAltPower, maxAltPower = UnitPower(uId, 10), UnitPowerMax(uId, 10)
				if maxAltPower and maxAltPower > 0 then
					if currentAltPower / maxAltPower * 100 >= threshold then
						lines[UnitName(uId)] = L.INFOFRAME_ALT .. currentAltPower
					end
				end
			end
		end
	end
	updateLines()
	updateIcons()
end

local function updateEnemyAbsorb()
	twipe(lines)
	local spellInput = value[1]
	local totalAbsorb = value[2]
	local specificUnit = value[3]
	if specificUnit then
		if UnitExists(specificUnit) then
			local absorbAmount
			if spellInput then -- Get specific spell absorb
				absorbAmount = select(16, DBM:UnitBuff(specificUnit, spellInput)) or select(16, DBM:UnitDebuff(specificUnit, spellInput))
			else -- Get all of them
				absorbAmount = UnitGetTotalAbsorbs(specificUnit)
			end
			if absorbAmount and absorbAmount > 0 then
				if totalAbsorb then
					lines[UnitName(specificUnit)] = mfloor(absorbAmount / totalAbsorb * 100) .. "%"
				else
					lines[UnitName(specificUnit)] = mfloor(absorbAmount)
				end
			end
		end
	else--Generic absorbs for bosses. Not to be mistaken for updateMultiEnemyAbsorb, which supports checking multiple units that might or might not be bosses
		for i = 1, 10 do
			local uId = "boss" .. i
			if UnitExists(uId) then
				local absorbAmount
				if spellInput then -- Get specific spell absorb
					absorbAmount = select(16, DBM:UnitBuff(uId, spellInput)) or select(16, DBM:UnitDebuff(uId, spellInput))
				else -- Get all of them
					absorbAmount = UnitGetTotalAbsorbs(uId)
				end
				if absorbAmount and absorbAmount > 0 then
					if totalAbsorb then
						lines[UnitName(uId)] = mfloor(totalAbsorb and absorbAmount / totalAbsorb * 100) .. "%"
					else
						lines[UnitName(uId)] = mfloor(absorbAmount)
					end
				end
			end
		end
	end
	updateLines()
	updateIcons()
end

--Really hate splitting this off from updateEnemyAbsorb but having them merged was even uglier
--Note, this method also less efficient than boss only or single unit absorb tracking.
--Don't use this function to be lazy (ie just passing 1 guid instead of finding valid unit at mod level)
local function updateMultiEnemyAbsorb()
	twipe(lines)
	local spellInput = value[1]
	local totalAbsorb = value[2]
	local guidTable = value[3]--Multi target by table
	local guidTracked = {}
	for i = 1, 10 do
		if #guidTable == #guidTracked then--Stop searching, found everything we're looking for.
			break
		end
		local uId = "boss" .. i
		if UnitExists(uId) then
			local targetGUID = UnitGUID(uId)
			if targetGUID and guidTable[targetGUID] and not guidTracked[targetGUID] then
				guidTracked[targetGUID] = true
				local absorbAmount
				if spellInput then -- Get specific spell absorb
					absorbAmount = select(16, DBM:UnitBuff(uId, spellInput)) or select(16, DBM:UnitDebuff(uId, spellInput))
				else -- Get all of them
					absorbAmount = UnitGetTotalAbsorbs(uId)
				end
				if absorbAmount and absorbAmount > 0 then
					if totalAbsorb then
						lines[UnitName(uId)] = mfloor(totalAbsorb and absorbAmount / totalAbsorb * 100) .. "%"
					else
						lines[UnitName(uId)] = mfloor(absorbAmount)
					end
				end
			end
		end
	end
	for unitId in DBM:GetGroupMembers() do--Do not use self on this function, because self might be bossModPrototype
		if #guidTable == #guidTracked then--Stop searching, found everything we're looking for.
			break
		end
		local uId = unitId .. "target"
		local targetGUID = UnitGUID(unitId)
		if targetGUID and guidTable[targetGUID] and not guidTracked[targetGUID] then
			guidTracked[targetGUID] = true
			local absorbAmount
			if spellInput then -- Get specific spell absorb
				absorbAmount = select(16, DBM:UnitBuff(uId, spellInput)) or select(16, DBM:UnitDebuff(uId, spellInput))
			else -- Get all of them
				absorbAmount = UnitGetTotalAbsorbs(uId)
			end
			if absorbAmount and absorbAmount > 0 then
				if totalAbsorb then
					lines[UnitName(uId)] = mfloor(totalAbsorb and absorbAmount / totalAbsorb * 100) .. "%"
				else
					lines[UnitName(uId)] = mfloor(absorbAmount)
				end
			end
		end
	end
	updateLines()
	updateIcons()
end

local function updateAllAbsorb()
	twipe(lines)
	local spellInput = value[1]
	local totalAbsorb = value[2]
	for i = 1, 10 do
		local uId = "boss" .. i
		if UnitExists(uId) then
			local absorbAmount
			if spellInput then -- Get specific spell absorb
				absorbAmount = select(16, DBM:UnitBuff(uId, spellInput)) or select(16, DBM:UnitDebuff(uId, spellInput))
			else -- Get all of them
				absorbAmount = UnitGetTotalAbsorbs(uId)
			end
			if absorbAmount and absorbAmount > 0 then
				if totalAbsorb then
					lines[UnitName(uId)] = mfloor(totalAbsorb and absorbAmount / totalAbsorb * 100) .. "%"
				else
					lines[UnitName(uId)] = mfloor(absorbAmount)
				end
			end
		end
	end
	if spellInput then
		for uId in DBM:GetGroupMembers() do
			local absorbAmount = select(16, DBM:UnitBuff(uId, spellInput)) or select(16, DBM:UnitDebuff(uId, spellInput))
			if absorbAmount and absorbAmount > 0 then
				if totalAbsorb then
					lines[UnitName(uId)] = mfloor(totalAbsorb and absorbAmount / totalAbsorb * 100) .. "%"
				else
					lines[UnitName(uId)] = mfloor(absorbAmount)
				end
			end
		end
	end
	updateLines()
	updateIcons()
end

local function updatePlayerAbsorb()
	twipe(lines)
	local spellInput = value[1]
	local totalAbsorb = value[2]
	for uId in DBM:GetGroupMembers() do
		local absorbAmount
		if spellInput then -- Get specific spell absorb
			absorbAmount = select(16, DBM:UnitBuff(uId, spellInput)) or select(16, DBM:UnitDebuff(uId, spellInput))
		else--Not even spell input given, this is a very generic infoframe
			absorbAmount = UnitGetTotalAbsorbs(uId)
		end
		if absorbAmount and absorbAmount > 0 then
			if totalAbsorb then
				lines[UnitName(uId)] = mfloor(totalAbsorb and absorbAmount / totalAbsorb * 100) .. "%"
			else
				lines[UnitName(uId)] = mfloor(absorbAmount)
			end
		end
	end
	updateLines()
	updateIcons()
end

-- Buffs that are good to have, therefor bad not to have them.
local function updatePlayerBuffs()
	twipe(lines)
	local spellName = value[1]
	local tankIgnored = value[2]
	for uId in DBM:GetGroupMembers() do
		if tankIgnored and (UnitGroupRolesAssigned(uId) == "TANK" or GetPartyAssignment("MAINTANK", uId, true)) then
		else
			if not DBM:UnitBuff(uId, spellName) and not UnitIsDeadOrGhost(uId) then
				local unitName = DBM:GetUnitFullName(uId)
				if unitName then
					lines[unitName] = ""
				end
			end
		end
	end
	updateLines()
	updateIcons()
end

-- Debuffs that are good to have, therefor it's bad NOT to have them.
local function updateGoodPlayerDebuffs()
	twipe(lines)
	local spellInput = value[1]
	local tankIgnored = value[2]
	for uId in DBM:GetGroupMembers() do
		if tankIgnored and (UnitGroupRolesAssigned(uId) == "TANK" or GetPartyAssignment("MAINTANK", uId, true)) then
		else
			if not DBM:UnitDebuff(uId, spellInput) and not UnitIsDeadOrGhost(uId) then
				local unitName = DBM:GetUnitFullName(uId)
				if unitName then
					lines[unitName] = ""
				end
			end
		end
	end
	updateLines()
	updateIcons()
end

-- Debuffs that are bad to have, therefor it is bad to have them.
-- Function will auto handle spellName or spellId via DBMs unit debuff handler and spellInput type
local function updateBadPlayerDebuffs()
	twipe(lines)
	local spellInput = value[1]
	local tankIgnored = value[2]
	for uId in DBM:GetGroupMembers() do
		if tankIgnored and (UnitGroupRolesAssigned(uId) == "TANK" or GetPartyAssignment("MAINTANK", uId, true)) then
		else
			if DBM:UnitDebuff(uId, spellInput) and not UnitIsDeadOrGhost(uId) then
				lines[DBM:GetUnitFullName(uId)] = ""
			end
		end
	end
	updateLines()
	updateIcons()
end

--Debuffs with important durations that we track
local function updatePlayerDebuffRemaining()
	twipe(lines)
	local spellInput = value[1]
	for uId in DBM:GetGroupMembers() do
		local expires = select(6, DBM:UnitDebuff(uId, spellInput))
		if expires then
			if expires == 0 then
				lines[DBM:GetUnitFullName(uId)] = 9000--Force sorting the unknowns under the ones we do know.
			else
				local debuffTime = expires - GetTime()
				lines[DBM:GetUnitFullName(uId)] = mfloor(debuffTime)
			end
		end
	end
	updateLines()
	updateIcons()
end

-- Buffs with important durations that we track
local function updatePlayerBuffRemaining()
	twipe(lines)
	local spellInput = value[1]
	for uId in DBM:GetGroupMembers() do
		local expires = select(6, DBM:UnitBuff(uId, spellInput))
		if expires then
			if expires == 0 then
				lines[DBM:GetUnitFullName(uId)] = 9000--Force sorting the unknowns under the ones we do know.
			else
				local debuffTime = expires - GetTime()
				lines[DBM:GetUnitFullName(uId)] = mfloor(debuffTime)
			end
		end
	end
	updateLines()
	updateIcons()
end

-- Debuffs that are bad to have, but we want to show players who do NOT have them
-- Function will auto handle spellName or spellId via DBMs unit debuff handler and spellInput type
local function updateReverseBadPlayerDebuffs()
	twipe(lines)
	local spellInput = value[1]
	local tankIgnored = value[2]
	for uId in DBM:GetGroupMembers() do
		if tankIgnored and (UnitGroupRolesAssigned(uId) == "TANK" or GetPartyAssignment("MAINTANK", uId, true)) then
		else
			if not DBM:UnitDebuff(uId, spellInput) and not UnitIsDeadOrGhost(uId) and not DBM:UnitBuff(uId, 27827) then--27827 Spirit of Redemption. This particular info frame wants to ignore this
				local unitName = DBM:GetUnitFullName(uId)
				if unitName then
					lines[unitName] = ""
				end
			end
		end
	end
	updateLines()
	updateIcons()
end

local function updatePlayerBuffStacks()
	twipe(lines)
	local spellInput = value[1]
	for uId in DBM:GetGroupMembers() do
		local spellName, _, count, _, _, _, _, _, _, _, _, _, _, _, _, count2 = DBM:UnitBuff(uId, spellInput)
		if spellName and (count or count2) then
			lines[DBM:GetUnitFullName(uId)] = count2 or count
		end
	end
	updateIcons()
	updateLines()
end

local function updatePlayerDebuffStacks()
	twipe(lines)
	local spellInput = value[1]
	for uId in DBM:GetGroupMembers() do
		local spellName, _, count, _, _, _, _, _, _, _, _, _, _, _, _, count2 = DBM:UnitDebuff(uId, spellInput)
		if spellName and (count or count2) then
			lines[DBM:GetUnitFullName(uId)] = count2 or count
		end
	end
	updateIcons()
	updateLines()
end

local function updatePlayerAggro()
	twipe(lines)
	local aggroType = value[1]
	local tankIgnored = value[2]
	for uId in DBM:GetGroupMembers() do
		if tankIgnored and (UnitGroupRolesAssigned(uId) == "TANK" or GetPartyAssignment("MAINTANK", uId, true)) then
		else
			local currentThreat = UnitThreatSituation(uId) or 0
			if currentThreat >= aggroType then
				lines[DBM:GetUnitFullName(uId)] = ""
			end
		end
	end
	updateLines()
	updateIcons()
end

local function updatePlayerTargets()
	twipe(lines)
	local cId = value[1]
	for uId, _ in DBM:GetGroupMembers() do
		if DBM:GetUnitCreatureId(uId .. "target") ~= cId and (UnitGroupRolesAssigned(uId) == "DAMAGER" or UnitGroupRolesAssigned(uId) == "NONE") then
			local unitName = DBM:GetUnitFullName(uId)
			if unitName then
				lines[unitName] = ""
			end
		end
	end
	updateLines()
	updateIcons()
end

local function updateByFunction()
	local func = value[1]
	local sortFunc = value[2]
	local useIcon = value[3]
	local presortedLines
	lines, presortedLines = func()
	if sortFunc then
		if type(sortFunc) == "function" then
			updateLinesCustomSort(sortFunc)
		else
			updateLines()
		end
	else
		updateLines(presortedLines)
	end
	if useIcon then
		updateIcons()
	end
end

-- Unsorted table maintained by mod and just sent here.
-- Never updated by onupdate method, requires manual updates when mod updates table
local function updateByTable(table)
	twipe(lines)
	if table then
		for i, v in pairs(table) do
			lines[i] = v
		end
	end
	updateLines()
	updateIcons()
end

local function updateTest()
	twipe(lines)
	lines["Alpha"] = 1
	lines["Beta"] = 10
	lines["Gamma"] = 25
	lines["Delta"] = 50
	lines["Epsilon"] = 100
	updateLines()
end

local test2Table
local function updateTest2()
	twipe(lines)
	if not test2Table then
		test2Table = {}
		for _ = 1, 40 do
			local ident = ""
			for _ = 1, mrandom(5, 12) do
				ident = ident .. schar(mrandom(65, 90))
			end
			test2Table[ident] = mrandom(1, 10) * 10
		end
	end
	for k, v in pairs(test2Table) do
		lines[k] = v
	end
	updateLines()
end

local test3Counter = 0
local function updateTest3()
	twipe(lines)
	if not test2Table then
		test2Table = {}
		for _ = 1, 40 do
			local ident = ""
			for _ = 1, mrandom(5, 12) do
				ident = ident .. schar(mrandom(65, 90))
			end
			test2Table[ident] = mrandom(1, 10) * 10
		end
	end
	if test3Counter < 40 then
		local count = 0
		for k, _ in pairs(test2Table) do
			if count > 40 - test3Counter then
				break
			end
			count = count + 1
			lines[k] = mrandom(1, 10) * 10
		end
		test3Counter = test3Counter + 1
	end
	updateLines()
end

local events = {
	["health"] = updateHealth,
	["bosshealth"] = updateBossHealth,
	["playerpower"] = updatePlayerPower,
	["enemypower"] = updateEnemyPower,
	["enemyabsorb"] = updateEnemyAbsorb,
	["multienemyabsorb"] = updateMultiEnemyAbsorb,
	["allabsorb"] = updateAllAbsorb,
	["playerabsorb"] = updatePlayerAbsorb,
	["playerbuff"] = updatePlayerBuffs,
	["playergooddebuff"] = updateGoodPlayerDebuffs,
	["playerbaddebuff"] = updateBadPlayerDebuffs,
	["playerdebuffremaining"] = updatePlayerDebuffRemaining,
	["playerbuffremaining"] = updatePlayerBuffRemaining,
	["reverseplayerbaddebuff"] = updateReverseBadPlayerDebuffs,
	["playeraggro"] = updatePlayerAggro,
	["playerbuffstacks"] = updatePlayerBuffStacks,
	["playerdebuffstacks"] = updatePlayerDebuffStacks,
	["playertargets"] = updatePlayerTargets,
	["function"] = updateByFunction,
	["table"] = updateByTable,
	["test"] = updateTest,
	["test2"] = updateTest2,
	["test3"] = updateTest3
}

local midnightRestrictedEvents = {
	["health"] = true,
	["bosshealth"] = true,
	["enemyabsorb"] = true,
	["multienemyabsorb"] = true,
	["allabsorb"] = true,
	["playerabsorb"] = true,
	["playerbuff"] = true,
	["playerbuffremaining"] = true,
	["playerpower"] = true,
	["enemypower"] = true,
	["playergooddebuff"] = true,
	["playerbaddebuff"] = true,
	["reverseplayerbaddebuff"] = true,
	["playerdebuffstacks"] = true

}

----------------
--  OnUpdate  --
----------------
local friendlyEvents = {
	["health"] = true,
	["playerpower"] = true,
	["playerabsorb"] = true,
	["playerbuff"] = true,
	["playergooddebuff"] = true,
	["playerbaddebuff"] = true,
	["playerdebuffremaining"] = true,
	["playerbuffremaining"] = true,
	["reverseplayerbaddebuff"] = true,
	["playeraggro"] = true,
	["playerbuffstacks"] = true,
	["playerdebuffstacks"] = true,
	["playertargets"] = true
}

local function onUpdate(frame, table)
	if events[currentEvent] then
		events[currentEvent](table)
	else
		if frame then
			frame:Hide()
		end
	end
	local color = NORMAL_FONT_COLOR
	infoFrame:ClearLines()
	local linesShown = 0
	for i = 1, #sortedLines do
		if linesShown >= maxLines * maxCols then
			break
		end
		local leftText = sortedLines[i]
		if not leftText then
			error("DBM InfoFrame: leftText cannot be nil, Notify DBM author. Infoframe force shutting down ", 2)
			frame:Hide()
			return
		elseif type(leftText) ~= "string" then
			leftText = tostring(leftText)
		end
		local rightText = lines[leftText]
		local extra, extraName = ssplit("*", leftText) -- Find just unit name, if extra info had to be added to make unique
		local icon = icons[extraName or leftText]
		local textWithIcon = icon and ("|TInterface\\TargetingFrame\\UI-RaidTargetingIcon_%d:0|t%s"):format(icon, leftText)
		if friendlyEvents[currentEvent] then
			local unitId = DBM:GetRaidUnitId(DBM:GetUnitFullName(extraName or leftText)) or "player"--Prevent nil logical error
			if unitId then
				local mapId = select(-1, UnitPosition(unitId))--In instances in 10.2.5, blizzard truncates UnitPosition to scrub x and y args, meaning it only has 2 returns, not 4, so can't select 4 anymore, have to -1 so it auto chooses last arg be it a 2 or a 4
				if mapId == currentMapId then
					local _, class = UnitClass(unitId)
					if class then
						color = RAID_CLASS_COLORS[class]
						if DBM.Options.StripServerName then -- StripServerName option is checked here, even though it's checked in GetShortServerName function, because we have to apply custom 3rd column hack reconstruction
							local shortName = DBM:GetShortServerName(extraName or leftText)
							if extraName then -- 3 column hack is present, we need to reconstruct leftText with shortened name
								leftText = extra.."*"..shortName
							else -- LeftText is name, just replace it with shortname
								leftText = shortName
							end
						end
					end
					linesShown = linesShown + 1
					if unitId and UnitIsUnit(unitId, "player") then -- It's player.
						if currentEvent == "health" or currentEvent == "playerpower" or currentEvent == "playerabsorb" or currentEvent == "playerbuff" or currentEvent == "playergooddebuff" or currentEvent == "playerbaddebuff" or currentEvent == "playerdebuffremaining" or currentEvent == "playerdebuffstacks" or currentEvent == "playerbuffremaining" or currentEvent == "playertargets" or currentEvent == "playeraggro" then--Red
							infoFrame:SetLine(linesShown, textWithIcon or leftText, rightText, 255, 0, 0, 255, 255, 255)
						else -- Green
							infoFrame:SetLine(linesShown, textWithIcon or leftText, rightText, 0, 255, 0, 255, 255, 255)
						end
					else -- It's not player, do nothing special with it. Ordinary class colored text.
						if currentEvent == "playerdebuffremaining" or currentEvent == "playerbuffremaining" then
							local numberValue = tonumber(rightText)
							if numberValue < 6 then
								infoFrame:SetLine(linesShown, textWithIcon or leftText, rightText, color.r, color.g, color.b, 255, 0, 0)--Red
							elseif numberValue < 11 then
								infoFrame:SetLine(linesShown, textWithIcon or leftText, rightText, color.r, color.g, color.b, 255, 127.5, 0)--Orange
							else
								if numberValue == 9000 then -- Out of range players
									infoFrame:SetLine(linesShown, textWithIcon or leftText, SPELL_FAILED_OUT_OF_RANGE, color.r, color.g, color.b, 255, 0, 0)--Red
								else
									infoFrame:SetLine(linesShown, textWithIcon or leftText, rightText, color.r, color.g, color.b, 255, 255, 255)--White
								end
							end
						else
							infoFrame:SetLine(linesShown, textWithIcon or leftText, rightText, color.r, color.g, color.b, 255, 255, 255)
						end
					end
				end
			end
		else
			local color2 = NORMAL_FONT_COLOR -- Only custom into frames will have chance of putting player names on right side
			local unitId = DBM:GetRaidUnitId(DBM:GetUnitFullName(extraName or leftText))
			local unitId2 = DBM:GetRaidUnitId(DBM:GetUnitFullName(rightText))
			-- Class color names in custom functions too, IF unitID exists
			if unitId then -- Check left text
				local _, class = UnitClass(unitId)
				if class then
					color = RAID_CLASS_COLORS[class]
					if DBM.Options.StripServerName then--StripServerName option is checked here, even though it's checked in GetShortServerName function, because we have to apply custom 3rd column hack reconstruction
						local shortName = DBM:GetShortServerName(extraName or leftText)
						if extraName then -- 3 column hack is present, we need to reconstruct leftText with shortened name
							leftText = extra.."*"..shortName
						else -- LeftText is name, just replace it with shortname
							leftText = shortName
						end
					end
				end
			else
				color = NORMAL_FONT_COLOR
				leftText = extraName or leftText
			end
			if unitId2 then -- Check right text
				local _, class = UnitClass(unitId2)
				if class then
					color2 = RAID_CLASS_COLORS[class]
					rightText = DBM:GetShortServerName(rightText)
				end
			end
			linesShown = linesShown + 1
			infoFrame:SetLine(linesShown, textWithIcon or leftText, rightText, color.r, color.g, color.b, color2.r, color2.g, color2.b)
		end
	end
	local maxWidth1, maxWidth2, linesPerRow = {}, {}, 5
	if linesShown > 5 then
		linesPerRow = mmin(maxLines, mfloor(linesShown / maxCols + 0.99))
	end
	local shouldUpdate = prevLines ~= linesShown
	for i = 1, linesShown do
		if shouldUpdate then
			infoFrame:AlignLine(i * 2 - 1, linesPerRow * 2)
			infoFrame:AlignLine(i * 2, linesPerRow * 2)
		end
		local m = mfloor(i / linesPerRow + 0.99)
		frame.lines[i * 2 - 1]:SetWidth(0) -- Hack here, because the string width doesn't calculate properly.
		frame.lines[i * 2]:SetWidth(0)
		maxWidth1[m] = mmax(maxWidth1[m] or 0, frame.lines[i * 2 - 1]:GetStringWidth())
		maxWidth2[m] = mmax(maxWidth2[m] or 0, frame.lines[i * 2]:GetStringWidth())
	end
	local size = DBM.Options.InfoFrameFontSize
	local width = 0
	for i, _ in pairs(maxWidth1) do
		local maxWid, maxWid2 = maxWidth1[i], maxWidth2[i]
		width = width + maxWid + maxWid2 + size + (size / 2)
		for ii = 1, linesPerRow do
			local m = ((i - 1) * linesPerRow * 2) + (ii * 2)
			if not frame.lines[m] then
				break
			end
			frame.lines[m - 1]:SetSize(maxWid, size)
			frame.lines[m]:SetSize(maxWid2, size)
		end
	end
	if width == 0 then
		width = 105
	end
	local height = size
	if linesShown > linesPerRow then
		height = height + (linesPerRow * size)
	else
		height = height + (mmin(linesShown, maxLines) * size)
	end
	frame:SetSize(width, height)
	frame:Show()
	prevLines = linesShown
end

---------------
--  Methods  --
---------------
--Arg 1: spellName, health/powervalue, customfunction, table type. Arg 2: TankIgnore, Powertype, SortFunction, totalAbsorb, sortmethod (table/stacks). Arg 3: SpellFilter, UseIcon. Arg 4: disable onUpdate. Arg 5: sortmethod (playerpower)
function infoFrame:Show(modMaxLines, event, ...)
	if DBM.Options.DontShowInfoFrame and not (event or ""):find("test") then
		return
	end
	if midnightRestrictedEvents[event] and DBM:IsPostMidnight() then
		return
	end
	prevLines = 0
	currentMapId = select(-1, UnitPosition("player"))
	modLines = modMaxLines
	if DBM.Options.InfoFrameLines and DBM.Options.InfoFrameLines ~= 0 then
		maxLines = DBM.Options.InfoFrameLines
	else
		maxLines = modMaxLines or 5
	end
	if DBM.Options.InfoFrameCols and DBM.Options.InfoFrameCols ~= 0 then
		maxCols = DBM.Options.InfoFrameCols
	else
		-- TODO, still needs more finite control via gui later on
		-- I think dynamic options modMaxLines/10 modMaxLines/5 are both valid options, as well as just capping at 2 >= 10
		maxCols = modMaxLines and modMaxLines >= 10 and 2 or 1
	end
	twipe(value)
	for i = 1, select("#", ...) do
		value[i] = select(i, ...)
	end
	if not frame then
		createFrame()
	end
	-- Orders event to use spellID no matter what and not spell name
	if event:find("byspellid") then
		event = event:gsub("byspellid", "") -- Strip off the byspellid, it served it's purpose, it simply told infoframe to not convert to spellName
		if type(value[1]) ~= "number" then
			error("DBM-InfoFrame: byspellid method must use spellId", 2)
			return
		end
	-- If spellId is given as value one and it's not a byspellid event, convert to spellname
	-- This also allows spell name to be given by mod, since value 1 verifies it's a number
	elseif type(value[1]) == "number" and event ~= "health" and event ~= "function" and event ~= "table" and event ~= "playertargets" and event ~= "playeraggro" and event ~= "playerpower" and event ~= "enemypower" and event ~= "test" and event ~= "test2" then
		-- Outside of "byspellid" functions, typical frames will still use spell NAME matching not spellID.
		-- This just determines if we convert the spell input to a spell Name, if a spellId was provided for a non byspellid infoframe
		value[1] = DBM:GetSpellName(value[1])
	elseif event == "bosshealth" then
		if type(value[1]) ~= "table" then
			error("DBM-InfoFrame: bosshealth frame expects a table of bossIds -> names or a mod as parameter, got " .. type(value[1]), 2)
		end
		if value[1].multiMobPullDetection or value[1].creatureId then -- Looks like a mod
			-- Get default bosses from mod
			local mod = value[1]
			value[1] = {}
			if mod.multiMobPullDetection then
				for _, v in ipairs(mod.multiMobPullDetection) do
					value[1][v] = true
				end
			elseif mod.creatureId then -- Boss health frame for a single target? Sure, let's support it anyways.
				value[1][mod.creatureId] = true
			end
		end -- else: is not a mod, just use as is
	end
	currentEvent = event
	if event == "playerbuff" or event == "playerbaddebuff" or event == "playergooddebuff" then
		sortMethod = 3 -- Sort by group ID
	elseif event == "health" or event == "playerdebuffremaining" or event == "bosshealth" then
		sortMethod = 2 -- Sort lowest first
	elseif (event == "playerdebuffstacks" or event == "table") and value[2] and type(value[2]) == "number" then
		sortMethod = value[2]
	elseif event == "playerpower" and value[5] and type(value[5]) == "number" then
		sortMethod = value[5]
	else
		sortMethod = 1 -- Sort highest first
	end
	if events[currentEvent] then
		events[currentEvent](value[1])
	else
		error("DBM-InfoFrame: Unsupported event", 2)
		return
	end
	if not friendlyEvents[currentEvent] then
		twipe(icons)
	end
	frame:Show()
	onUpdate(frame, value[1])
	if not frame.ticker and not value[4] and event ~= "table" then
		frame.ticker = C_Timer.NewTicker(0.5, function()
			onUpdate(frame)
		end)
	elseif frame.ticker and value[4] then -- Redundancy, in event calling a non onupdate infoframe show without a hide event to unschedule ticker based infoframe
		frame.ticker:Cancel()
		frame.ticker = nil
	end
end

function infoFrame:RegisterCallback(cb)
	updateCallbacks[#updateCallbacks + 1] = cb
end

function infoFrame:Update(time)
	if not frame then
		createFrame()
	end
	if frame:IsShown() then
		if time then
			DBM:Unschedule(onUpdate)
			DBM:Schedule(time, onUpdate, frame)
		else
			onUpdate(frame)
		end
	end
end

function infoFrame:UpdateTable(table, time)
	if not frame then
		createFrame()
	end
	if frame:IsShown() and table then
		if time then
			DBM:Unschedule(onUpdate)
			DBM:Schedule(time, onUpdate, frame, table)
		else
			onUpdate(frame, table)
		end
	end
end

function infoFrame:SetHeader(text)
	if not frame then
		createFrame()
	end
	frame.header:SetText(text or L.INFOFRAME_TITLE)
end

function infoFrame:ClearLines()
	if not frame then
		createFrame()
	end
	for i = 1, #frame.lines do
		frame.lines[i]:SetText("")
		frame.lines[i]:Hide()
	end
end

function infoFrame:AlignLine(lineNum, linesPerRow)
	local size = DBM.Options.InfoFrameFontSize
	local line = frame.lines[lineNum]
	line:SetJustifyH(lineNum % 2 == 0 and "RIGHT" or "LEFT")
	if lineNum == 1 then -- 1st entry left
		line:SetPoint("TOPLEFT", frame, "TOPLEFT", size / 2, -(size / 2))
	elseif lineNum == 2 then -- 1st entry right
		line:SetPoint("TOPLEFT", frame.lines[1], "TOPRIGHT", size / 2, 0)
	else
		if lineNum % linesPerRow == 1 then -- Column 2-x, 1st entry left
			line:SetPoint("TOPLEFT", frame.lines[lineNum - linesPerRow + 1], "TOPRIGHT", size, 0)
		elseif lineNum % 2 == 0 then -- Column 2-x, Right entry
			line:SetPoint("TOPLEFT", frame.lines[lineNum - 1], "TOPRIGHT", size / 2, 0)
		else -- Column 2-x, Left entry
			line:SetPoint("TOPLEFT", frame.lines[lineNum - 2], "LEFT", 0, -(size / 2))
		end
	end
end

function infoFrame:UpdateStyle()
	if not frame then
		createFrame()
	end
	prevLines = 0
	local font = DBM.Options.InfoFrameFont == "standardFont" and standardFont or DBM.Options.InfoFrameFont
	local size = DBM.Options.InfoFrameFontSize
	local style = DBM.Options.InfoFrameFontStyle == "none" and nil or DBM.Options.InfoFrameFontStyle
	for i = 1, #frame.lines do
		frame.lines[i]:SetFont(font, size, style)
	end
end

function infoFrame:SetLine(lineNum, leftText, rightText, colorR, colorG, colorB, color2R, color2G, color2B)
	if not frame then
		createFrame()
	end
	lineNum = lineNum * 2 - 1
	if not frame.lines[lineNum] then
		local font = DBM.Options.InfoFrameFont == "standardFont" and standardFont or DBM.Options.InfoFrameFont
		local size = DBM.Options.InfoFrameFontSize
		local style = DBM.Options.InfoFrameFontStyle == "none" and nil or DBM.Options.InfoFrameFontStyle
		frame.lines[lineNum] = frame:CreateFontString("Line" .. lineNum, "OVERLAY", "GameFontNormal")
		frame.lines[lineNum]:SetFont(font, size, style)
		frame.lines[lineNum + 1] = frame:CreateFontString("Line" .. lineNum + 1, "OVERLAY", "GameFontNormal")
		frame.lines[lineNum + 1]:SetFont(font, size, style)
		frame.lines[lineNum + 1]:SetJustifyH("RIGHT")
	end
	frame.lines[lineNum]:SetText(leftText)
	frame.lines[lineNum]:SetTextColor(colorR or 255, colorG or 255, colorB or 255)
	frame.lines[lineNum]:Show()
	frame.lines[lineNum + 1]:SetText(rightText)
	frame.lines[lineNum + 1]:SetTextColor(color2R or 255, color2G or 255, color2B or 255)
	frame.lines[lineNum + 1]:Show()
end

function infoFrame:Hide()
	twipe(lines)
	twipe(icons)
	twipe(sortedLines)
	twipe(updateCallbacks)
	infoFrame:SetHeader()
	twipe(value)
	if frame then
		if frame.ticker then
			frame.ticker:Cancel()
			frame.ticker = nil
		end
		frame:Hide()
	end
	currentEvent = nil
	sortMethod = 1
end

function infoFrame:SetLines(lines)
	modLines = lines
	if DBM.Options.InfoFrameLines == 0 then
		maxLines = lines
	end
end

function infoFrame:SetColumns(columns)
	modCols = columns
	if DBM.Options.InfoFrameCols == 0 then
		maxCols = columns
	end
end

function infoFrame:IsShown()
	return frame and frame:IsShown()
end

function infoFrame:SetSortingAsc()
	sortMethod = 2
end

function infoFrame:SetSortingGroupId()
	sortMethod = 3
end
