
KLHTM_Abilities = { } -- Gives the properties of each special attack: threat, rage, next attack damage.

KLHTM_MyData = { } -- Total dam / threat / rage / hits for each attack
KLHTM_RaidData = { }
KLHTM_ThreatParameters = { }

-- lukon mod: stores player classes. key = name, value = "Priest", etc.
-- players whose class is cannot be found are given a dummy value of "".
KLHTM_PlayerClasses = { }

KLHTM_MasterTarget = nil
KLHTM_KnockbackDiscoveryIsActive = false
KLHTM_IsKnockbackInvestigator = false

KLHTM_RaidThreatOffset = 0 -- This is the difference between your personal threat and your raid threat
-- personal threat will not reset when raid threat is reset, from now. i.e. Raid = personal + offset.

-- Feign Death Stuff
KLHTM_Original_UIErrorsFrame_OnEvent = nil
KLHTM_FeignDeathResistTime = 0

-- key = GetTime(), value = threat. Only keep them when we are out of combat.
KLHTM_RecentAttacks = { }

KLHTM_MySpellRanks = { } -- lists of <spell name, rank number> pairs

KLHTM_GlobalThreat = { } -- table listing all our current additive threat modifiers

KLHTM_States = 
{
	["feigndeath"] = 
	{
		["value"] = false,
		["lastchange"] = 0,
	},
	["arcaneshroud"] = -- from Fetish of the Sandreaver
	{
		["value"] = false,
		["lastchange"] = 0,
		["duration"] = 15.0
	},
	["incombat"] = 
	{
		["value"] = false,
		["lastchange"] = 0,
	},
	["playercharmed"] = 
	{
		["value"] = false,
		["lastchange"] = 0,
	},
	["burningadrenaline"] = 
	{
		["value"] = false,
		["lastchange"] = 0,
		["duration"] = 20.0,
	},
	["afk"] = 
	{
		["value"] = false,
		["lastchange"] = 0,
	}
}

-- Value = true / false. State = "incombat" / "feigndeath" etc
-- returns non-nil if there was a change
function KLHTM_SetState(State, Value)
	
	if KLHTM_States[State].value ~= Value then
		KLHTM_States[State].value = Value
		KLHTM_States[State].lastchange = GetTime()
		
		return true
	end

end

--[[ This is a hook for the error messages that are displayed on your UI. We use it to intercept
the "resisted" message that pops up when Feign Death is resisted 
]] 
function KLHTM_UIErrorsFrame_OnEvent(event, message)
	
	if message == ERR_FEIGN_DEATH_RESISTED then
		-- register the resist
		KLHTM_PrintTrace("FeignDeathDebug", "Feign Death Resist message intercepted.")
		KLHTM_FeignDeathResistTime = GetTime()
	end
	
	KLHTM_Original_UIErrorsFrame_OnEvent(event, message)
end

-- this is called at startup
function KLHTM_InitialiseData()

	KLHTM_ResetThreatParameters()
	KLHTM_LongIntervalDataUpdate()
	KLHTM_ShortIntervalDataUpdate()
	KLHTM_ResetMyData("Both")
	
	KLHTM_Original_UIErrorsFrame_OnEvent = UIErrorsFrame_OnEvent
	UIErrorsFrame_OnEvent = KLHTM_UIErrorsFrame_OnEvent
	
end

-- These are the amounts you lose, as a fraction. Should all be negative.
KLHTM_Knockbacks =
{	
	[KLHTM_String("knockbacks", "knockaway")] = 
	{
		["#Default"] = -0.5,
		[KLHTM_String("mobs", "onyxia")] = -0.333,
	},
	[KLHTM_String("knockbacks", "wingbuffet")] = 
	{
		["#Default"] = -0.5,
		[KLHTM_String("mobs", "onyxia")] = 0,
	},
	[KLHTM_String("knockbacks", "uppercut")] = 
	{
		["#Default"] = 0,
	},
}

KLHTM_Abilities = 
{	
	[KLHTM_String("spell", "heroicstrike")] = 
	{	["default"] = 
		{
			["threat"] = 145,
			["nextattack"] = 138,
			["rage"] = 15,
		},
		["9"] = 
		{
			["threat"] = 175,
			["nextattack"] = 157,
		}
	},
	[KLHTM_String("spell", "maul")] = 
	{	
		["default"] = 
		{
			["threat"] = 0,
			["nextattack"] = 128,
			["rage"] = 15,
			["multiplier"] = 1.75,
		}
	},
	[KLHTM_String("spell", "swipe")] = 
	{	
		["default"] = 
		{
			["threat"] = 0,
			["rage"] = 20,
			["multiplier"] = 1.75,
		},
	},
	[KLHTM_String("spell", "shieldslam")] = 
	{	
		["default"] = 
		{
			["threat"] = 210,
			["rage"] = 30,
		}
	},
	[KLHTM_String("spell", "revenge")] = 
	{	
		["default"] = 
		{
			["threat"] = 315,
			["rage"] = 5,
		},
		["6"] = 
		{
			["threat"] = 355,
		},
	},
	[KLHTM_String("spell", "shieldbash")] = 
	{	
		["default"] = 
		{
			["threat"] = 180,
			["rage"] = 10,
		},
	},
	[KLHTM_String("spell", "sunder")] = 
	{	
		["default"] = 
		{
			["threat"] = 260,
			["rage"] = 15,
		},
	},
	[KLHTM_String("spell", "feint")] = 
	{
		["default"] = 
		{
			["threat"] = -600,
			["rage"] = 0,
		},
		["5"] = 
		{
			["threat"] = -800
		}
	},
	[KLHTM_String("spell", "cower")] = 
	{
		["default"] = 
		{
			["threat"] = -600,
			["rage"] = 0,
		},
	},
}

local temptable = nil

function KLHTM_ResetAbilities()

	temptable = KLHTM_Abilities[KLHTM_String("spell", "heroicstrike")]
	temptable.default.threat = 145
	temptable.default.nextattack = 138
	temptable.default.rage = 15
	temptable["9"].threat = 175
	temptable["9"].nextattack = 157
	
	temptable = KLHTM_Abilities[KLHTM_String("spell", "maul")]
	temptable.default.threat = 0
	temptable.default.nextattack = 128
	temptable.default.rage = 15
	temptable.default.multiplier = 1.75
	
	temptable = KLHTM_Abilities[KLHTM_String("spell", "swipe")]
	temptable.default.threat = 0
	temptable.default.rage = 20
	temptable.default.multiplier = 1.75
	
	temptable = KLHTM_Abilities[KLHTM_String("spell", "shieldslam")]
	temptable.default.threat = 210
	temptable.default.rage = 30
	
	temptable = KLHTM_Abilities[KLHTM_String("spell", "revenge")]
	temptable.default.threat = 315
	temptable.default.rage = 5
	temptable["6"].threat = 355
	
	temptable = KLHTM_Abilities[KLHTM_String("spell", "shieldbash")]
	temptable.default.threat = 180
	temptable.default.rage = 10
	
	temptable = KLHTM_Abilities[KLHTM_String("spell", "sunder")]
	temptable.default.threat = 260
	temptable.default.rage = 15
	
	temptable = KLHTM_Abilities[KLHTM_String("spell", "feint")]
	temptable.default.threat = -600
	temptable.default.rage = 0
	temptable["5"].threat = -800
	
	temptable = KLHTM_Abilities[KLHTM_String("spell", "cower")]
	temptable.default.threat = -600
	temptable.default.rage = 0

end

function KLHTM_Ability(Name, Value)
	
	-- First search for a spell rank override
	if KLHTM_MySpellRanks[Name] and KLHTM_Abilities[Name][KLHTM_MySpellRanks[Name]] and KLHTM_Abilities[Name][KLHTM_MySpellRanks[Name]][Value] then
	
		KLHTM_PrintTrace("SpellRank", string.format("Using rank %d for %s %s.", KLHTM_MySpellRanks[Name], Name, Value))
		return KLHTM_Abilities[Name][KLHTM_MySpellRanks[Name]][Value]
	
	else
		return KLHTM_Abilities[Name].default[Value]
	end
end

KLHTM_ThreatParameters = 
{	
	["defiance"] = 0.0,
	["healing"] = 0.5,
	["meleeaggrogain"] = 1.1,
	["rangeaggrogain"] = 1.3,
	["ragegain"] = 5.0,
	["energygain"] = 2.5,
	["managain"] = 0.5,
	["impalebonus"] = 0.0,
	["savagefurybonus"] = 0.0,
	["globalthreatmod"] = 1.0,
	["arcanist8active"] = false,
	["tranquilairactive"] = false,
	["nemesis8active"] = false,
	["netherwind3active"] = false,
	["magearcanedamage"] = 1.0,
	["masterdemonologist"] = 0.0,
	["salvationactive"] = false,
	["righteousfuryactive"] = false,
	["priestspellthreat"] = 1.0,
	["priestshadowdamage"] = 1.0,
	["paladinholythreat"] = 1.0,
}

function KLHTM_ResetThreatParameters()

	KLHTM_ThreatParameters.defiance = 0.0
	KLHTM_ThreatParameters.healing = 0.5
	KLHTM_ThreatParameters.meleeaggrogain = 1.1
	KLHTM_ThreatParameters.rangeaggrogain = 1.3
	KLHTM_ThreatParameters.ragegain = 5.0
	KLHTM_ThreatParameters.energygain = 2.5
	KLHTM_ThreatParameters.managain = 0.5
	KLHTM_ThreatParameters.impalebonus = 0.0
	KLHTM_ThreatParameters.savagefurybonus = 0.0
	KLHTM_ThreatParameters.globalthreatmod = 1.0
	KLHTM_ThreatParameters.arcanist8active = false
	KLHTM_ThreatParameters.tranquilairactive = false
	KLHTM_ThreatParameters.nemesis8active = false
	KLHTM_ThreatParameters.netherwind3active = false
	KLHTM_ThreatParameters.magearcanedamage = 1.0
	KLHTM_ThreatParameters.masterdemonologist = 0.0
	KLHTM_ThreatParameters.salvationactive = false
	KLHTM_ThreatParameters.righteousfuryactive = false
	KLHTM_ThreatParameters.priestspellthreat = 1.0
	KLHTM_ThreatParameters.priestshadowdamage = 1.0
	KLHTM_ThreatParameters.paladinholythreat = 1.0

end 

KLHTM_GlobalThreat = 
{
	["Value"] = 0.0,
	["Modifiers"] = { }
}

function KLHTM_TestThreatParameters()
	
	-- 1) Print out spell ranks
	local key
	local value
	
	for key, value in KLHTM_MySpellRanks do
		KLHTM_Print(string.format(KLHTM_String("print", "data", "abilityrank"), key, value))
	end
	
	-- 2) Print out global threat
	KLHTM_Print(string.format(KLHTM_String("print", "data", "globalthreat"), KLHTM_GlobalThreat.Value))
	for key, value in KLHTM_GlobalThreat.Modifiers do
		if value.isactive == false then
			break
		end
		
		KLHTM_Print(string.format(KLHTM_String("print", "data", "globalthreatmod"), value.reason, value.value))
	end

	-- 3) Print out threat for specific abilities
	if UnitClass("player") == KLHTM_String("class", "priest") then
		KLHTM_Print(string.format(KLHTM_String("print", "data", "multiplier"), KLHTM_String("class", "priest"), KLHTM_String("print", "data", "damage"), KLHTM_ThreatParameters.priestspellthreat))
		KLHTM_Print(string.format(KLHTM_String("print", "data", "multiplier"), KLHTM_String("class", "priest"), KLHTM_String("print", "data", "shadowspell"), KLHTM_ThreatParameters.priestshadowdamage))
	
	elseif UnitClass("player") == KLHTM_String("class", "warlock") then
		KLHTM_Print(string.format(KLHTM_String("print", "data", "setactive"), KLHTM_String("sets", "nemesis"), 8, KLHTM_BoolToString(KLHTM_ThreatParameters.nemesis8active)))
	
	elseif UnitClass("player") == KLHTM_String("class", "mage") then
		KLHTM_Print(string.format(KLHTM_String("print", "data", "setactive"), KLHTM_String("sets", "netherwind"), 3, KLHTM_BoolToString(KLHTM_ThreatParameters.netherwind3active)))
		KLHTM_Print(string.format(KLHTM_String("print", "data", "multiplier"), KLHTM_String("class", "mage"), KLHTM_String("print", "data", "arcanespell"), KLHTM_ThreatParameters.magearcanedamage))
		
	elseif UnitClass("player") == KLHTM_String("class", "paladin") then
		KLHTM_Print(string.format(KLHTM_String("print", "data", "multiplier"), KLHTM_String("class", "paladin"), KLHTM_String("print", "data", "holyspell"), KLHTM_ThreatParameters.paladinholythreat))
	end
	
	KLHTM_Print(string.format(KLHTM_String("print", "data", "healing"),  KLHTM_ThreatParameters.healing))
end

function KLHTM_NewDataSet()

	return
	{
		["hits"] = 0,
		["damage"] = 0,
		["threat"] = 0,
		["rage"] = 0,
	}
end

-- Set NotRaidData to non-nil to clear only the main frame, not the raid frame
-- DataSet = "Main" or "Raid" or "Both"
function KLHTM_ResetMyData(DataSet)

	-- 1) Recalculate KLHTM_RaidThreatOffset
	if DataSet == "Main" then
		KLHTM_RaidThreatOffset = KLHTM_RaidThreatOffset + KLHTM_MyData[KLHTM_String("threatsource", "total")].threat
	
	elseif DataSet == "Raid" then
		-- do nothing, just clear the names

	else
		KLHTM_RaidThreatOffset = 0
	end
	
	-- Clear Main Data
	if DataSet == "Main" or DataSet == "Both" then

		KLHTM_MyData = 
		{
			[KLHTM_String("threatsource", "whitedamage")] = KLHTM_NewDataSet(),
			[KLHTM_String("spell", "sunder")] = KLHTM_NewDataSet(),
			[KLHTM_String("spell", "heroicstrike")] = KLHTM_NewDataSet(),
			[KLHTM_String("spell", "shieldslam")] = KLHTM_NewDataSet(),
			[KLHTM_String("spell", "revenge")] = KLHTM_NewDataSet(),
			[KLHTM_String("spell", "shieldbash")] = KLHTM_NewDataSet(),
			[KLHTM_String("threatsource", "special")] = KLHTM_NewDataSet(),
			[KLHTM_String("threatsource", "dot")] = KLHTM_NewDataSet(),
			[KLHTM_String("threatsource", "healing")] = KLHTM_NewDataSet(),
			[KLHTM_String("threatsource", "powergain")] = KLHTM_NewDataSet(),
			[KLHTM_String("threatsource", "total")] = KLHTM_NewDataSet(),
			[KLHTM_String("threatsource", "threatwipe")] = KLHTM_NewDataSet(),
			[KLHTM_String("spell", "taunt")] = KLHTM_NewDataSet(),
			[KLHTM_String("spell", "maul")] = KLHTM_NewDataSet(),
			[KLHTM_String("spell", "swipe")] = KLHTM_NewDataSet(),
		}
	
		if KLHTM_MainWindow then
			KLHTM_MainWindow.Table.DataSource = KLHTM_MyData
			KLHTM_RedrawTable(KLHTM_MainWindow)
		end
		
		KLHTM_LastAttack = nil
		KLHTM_SecondLastAttack = nil
	end
	
	if DataSet == "Raid" or DataSet == "Both" then
		
		KLHTM_RaidData = 
		{
			[UnitName("player")] = 0,
		}
		
		if KLHTM_RaidWindow then
			KLHTM_RaidWindow.Table.DataSource = KLHTM_RaidData
			KLHTM_RedrawTable(KLHTM_RaidWindow)
		end
	end

end

-- The player's characteristics that affect threat, such as gear and talents, can change from time to time.
-- Every so often we recalculate them.

KLHTM_LastDataUpdate = 0		-- value from GetTime
KLHTM_DataUpdateInterval = 1.0  -- in seconds

-- Called from the mod's OnUpdate() method
function KLHTM_DataOnUpdate()

	KLHTM_ShortIntervalDataUpdate()

	local currenttime = GetTime()
	
	if currenttime > KLHTM_LastDataUpdate + KLHTM_DataUpdateInterval then
		KLHTM_LastDataUpdate = currenttime
		KLHTM_LongIntervalDataUpdate()
	end

end

local combatevent = 
{
	["hits"] = 1,
	["rage"] = 0,
	["damage"] = 0,
	["threat"] = 0,
}

-- Refreshes the information about rapidly changing values
function KLHTM_ShortIntervalDataUpdate()

	-- hunter: check for FD
	if UnitClass("player") == KLHTM_String("class", "hunter") then
		
		-- Check for feign death debuff
		local currentfd = KLHTM_IsBuffTexturePresent("Interface\\Icons\\Ability_Rogue_FeignDeath")
		
		if KLHTM_SetState("feigndeath", currentfd) and (currentfd == true) then
			
			-- first check for resist.
			if math.abs(KLHTM_FeignDeathResistTime - GetTime()) < 1.0 then
				--resisted. Nothing happens
				KLHTM_PrintTrace("FeignDeathDebug", "Feign Death was resisted!")
				
			else -- wipe threat
			
				combatevent.threat = - KLHTM_MyRaidThreat() -- Combat.lua
				
				KLHTM_AddAttackToData(KLHTM_String("threatsource", "threatwipe"), combatevent)
				KLHTM_AddAttackToData(KLHTM_String("threatsource", "total"), combatevent)
				KLHTM_RedrawTable(KLHTM_MainWindow)
			end
			
		end
	end
 
	-- update charmed state
	if UnitIsCharmed("player") == 1 then
		KLHTM_SetState("playercharmed", true)
	else
		KLHTM_SetState("playercharmed", false)
	end

	-- update combat state
	if UnitAffectingCombat("player") == 1 then
		
		if KLHTM_SetState("incombat", true) then
			-- player has just joined combat
			KLHTM_PrintTrace("InCombat", "You entered combat.")
			
			KLHTM_ResetRaidThreat()
			local key
			local value
		
			for key, value in KLHTM_RecentAttacks do
				KLHTM_RaidThreatOffset = KLHTM_RaidThreatOffset + value[2]
			end	
		end
	
	else
		if (KLHTM_States.playercharmed.value == true) or (GetTime() - KLHTM_States.playercharmed.lastchange < 2.0) then
			KLHTM_SetState("incombat", true) -- should not be a change
		else
			if KLHTM_SetState("incombat", false) then
				KLHTM_PrintTrace("InCombat", "You left combat.")
			end 
		end
	end
	
	-- update states with a "duration" parameter. They are all designed to deactivate on their own, but e.g.
	-- burning adrenaline isn't working, so we put in this mechanism as a backup
	local state
	local data
	
	for state, data in KLHTM_States do
		if data.duration and (data.value == true) and (GetTime() > data.lastchange + data.duration + 1.0) then
			KLHTM_SetState(state, false)
			KLHTM_PrintTrace("StateTimeout", "Deactivated the state " .. state .. " because it had passed its duration.")
		end
	end 
	
	-- remove old combat events ( > 1 second) from KLHTM_RecentAttacks array
	local value
	local key
	local timenow = GetTime()
	
	for key, value in KLHTM_RecentAttacks do
		if timenow - value[1] > 1.0 then
			table.remove(KLHTM_RecentAttacks, key)
		end
	end	
	
	-- Redo your global threat
	KLHTM_ResetGlobalThreat()

end

-- values in KLHTM_GlobalThreat.Modifiers have
-- value, name, isactive

function KLHTM_ResetGlobalThreat()

	-- 1) reset
	local x
	for x = 1, table.getn(KLHTM_GlobalThreat.Modifiers) do
		KLHTM_GlobalThreat.Modifiers[x].isactive = false
	end
	
	KLHTM_GlobalThreat.Value = 0.0

	-- 2) rebuild
	KLHTM_ModifyGlobalThreat(1.0, KLHTM_String("threatmod", "basevalue"))
	
	-- Tranquil Air
	if KLHTM_IsBuffTexturePresent("Interface\\Icons\\Spell_Nature_Brilliance") == true then
		KLHTM_ModifyGlobalThreat(-0.2, KLHTM_String("threatmod", "tranquilair"))
	end
	
	-- Blessing of Salvation
	if KLHTM_IsBuffTexturePresent("Interface\\Icons\\Spell_Holy_SealOfSalvation") == true then
		KLHTM_ModifyGlobalThreat(-0.3, KLHTM_String("threatmod", "salvation"))
		
	elseif KLHTM_IsBuffTexturePresent("Interface\\Icons\\Spell_Holy_GreaterBlessingofSalvation") == true then
		KLHTM_ModifyGlobalThreat(-0.3, KLHTM_String("threatmod", "salvation"))
	end
	
	-- Burning Adrenaline
	if KLHTM_States["burningadrenaline"].value == true then
		KLHTM_ModifyGlobalThreat(-0.75, KLHTM_String("spell", "burningadrenaline"))
	end
	
	-- Fetish of the Sand Reaver
	if KLHTM_States["arcaneshroud"].value == true then
		KLHTM_ModifyGlobalThreat(-0.7, KLHTM_String("spell", "arcaneshroud"))
	end
	
	local linkstring
	local enchant
	
	-- +2% threat enchant to gloves
	linkstring = GetInventoryItemLink("player", 10) or "" 
	enchant = string.gsub(linkstring, ".-item:%d+:(%d+).*", "%1", 1)
	
	if enchant == 2613 then
		KLHTM_ModifyGlobalThreat(0.02, KLHTM_String("threatsource", "glovethreatenchant"))
	end
	
	-- -2% threat enchant to back
	linkstring = GetInventoryItemLink("player", 15) or ""
	enchant = string.gsub(linkstring, ".-item:%d+:(%d+).*", "%1", 1)
	
	if enchant == 2621 then
		KLHTM_ModifyGlobalThreat(-0.02, KLHTM_String("threatsource", "backthreatenchant"))
	end
	
	local stance
	
	-- Warrior
	if UnitClass("player") == KLHTM_String("class", "warrior") then
		stance = KLHTM_GetStanceIndex()
		
		if stance == 1 then
			KLHTM_ModifyGlobalThreat(-0.2, KLHTM_String("threatmod", "battlestance"))
			
		elseif stance == 2 then
			KLHTM_ModifyGlobalThreat(0.3, KLHTM_String("threatmod", "defensivestance"))
			KLHTM_ModifyGlobalThreat(KLHTM_ThreatParameters.defiance, KLHTM_String("talent", "defiance"))
	
		elseif stance == 3 then
			KLHTM_ModifyGlobalThreat(-0.2, KLHTM_String("threatmod", "berserkerstance"))
		end
		
	-- Druid Stances
	elseif UnitClass("player") == KLHTM_String("class", "druid") then
		stance = KLHTM_GetStanceIndex()
		
		if stance == 1 then
			KLHTM_ModifyGlobalThreat(0.3, KLHTM_String("threatmod", "bearform"))
			KLHTM_ModifyGlobalThreat(KLHTM_ThreatParameters.feralinstinct, KLHTM_String("talent", "feralinstinct"))
		end
	
	-- Rogue
	elseif UnitClass("player") == KLHTM_String("class", "rogue") then
		KLHTM_ModifyGlobalThreat(-0.2, KLHTM_String("class", "rogue"))
		
	-- Arcanist
	elseif UnitClass("player") == KLHTM_String("class", "mage") and KLHTM_ThreatParameters.arcanist8active == true then
		KLHTM_ModifyGlobalThreat(-0.15, KLHTM_String("sets", "arcanist") .. " 8/8")
		
	-- Warlock
	elseif UnitClass("player") == KLHTM_String("class", "warlock") and KLHTM_ThreatParameters.masterdemonologist ~= 0 then
		KLHTM_ModifyGlobalThreat(KLHTM_ThreatParameters.masterdemonologist, KLHTM_String("talent", "masterdemonologist"))
	end	
end

function KLHTM_GetStanceIndex()
	
	local x = 0
	local isactive
	local texture 
	
	while true do
		x = x + 1
		texture, _, isactive = GetShapeshiftFormInfo(x)
		
		if texture == nil then
			return 0
		end
		
		if isactive == 1 then
			return x
		end
	end
	
end

function KLHTM_ModifyGlobalThreat(Value, Reason)
	
	KLHTM_GlobalThreat.Value = KLHTM_GlobalThreat.Value + Value
	
	local x
	for x = 1, table.getn(KLHTM_GlobalThreat.Modifiers) do
		
		if KLHTM_GlobalThreat.Modifiers[x].isactive == false then
			KLHTM_GlobalThreat.Modifiers[x].value = Value
			KLHTM_GlobalThreat.Modifiers[x].reason = Reason
			KLHTM_GlobalThreat.Modifiers[x].isactive = true 
			return
		end
	end
	
	table.insert(KLHTM_GlobalThreat.Modifiers, {["value"] = Value, ["reason"] = Reason, ["isactive"] = true})
	
end 

-- refreshes our information about largely fixed player values
function KLHTM_LongIntervalDataUpdate()

	KLHTM_ResetAbilities()
	KLHTM_ResetThreatParameters()
	KLHTM_RedoSpellRanks()
	
	-- talent / gear searching
	local info
	local rank
	local key
	local value
	
	if UnitClass("player") == KLHTM_String("class", "warrior") then

		if KLHTM_GetSetPieces(KLHTM_String("sets", "might")) == 8 then
			for key, value in KLHTM_Abilities[KLHTM_String("spell", "sunder")] do
				if value.threat then
					value.threat = value.threat * 1.15
				end
			end
		end

		-- improved sunder armor
		rank = KLHTM_GetTalentRank(KLHTM_String("spell", "sunder"))
		for key, value in KLHTM_Abilities[KLHTM_String("spell", "sunder")] do
			if value.rage then
				value.rage = value.rage - rank
			end
		end

		-- improved heroic strike
		rank = KLHTM_GetTalentRank(KLHTM_String("spell", "heroicstrike"))
		for key, value in KLHTM_Abilities[KLHTM_String("spell", "heroicstrike")] do
			if value.rage then
				value.rage = value.rage - rank
			end
		end

		-- defiance
		rank = KLHTM_GetTalentRank(KLHTM_String("talent", "defiance"))
		KLHTM_ThreatParameters.defiance = 0.03 * rank

		-- impale
		rank = KLHTM_GetTalentRank(KLHTM_String("talent", "impale"))
		KLHTM_ThreatParameters.impalebonus = 0.05 * rank

	elseif UnitClass("player") == KLHTM_String("class", "rogue") then
		
		if KLHTM_GetSetPieces(KLHTM_String("sets", "bloodfang")) > 4 then
			for key, value in KLHTM_Abilities[KLHTM_String("spell", "feint")] do
				if value.threat then
					value.threat = value.threat * 1.25
				end
			end
		end
		
	elseif UnitClass("player") == KLHTM_String("class", "priest") then
		
		-- silent resolve
		rank = KLHTM_GetTalentRank(KLHTM_String("talent", "silentresolve")) 
		KLHTM_ThreatParameters.priestspellthreat = 1 - (0.04 * rank)
		KLHTM_ThreatParameters.healing = KLHTM_ThreatParameters.healing * (1 - (0.04 * rank))
		
		-- shadow affinity
		rank = KLHTM_GetTalentRank(KLHTM_String("talent", "shadowaffinity"))
		if rank == 1 then
			KLHTM_ThreatParameters.priestshadowdamage = 0.92
		elseif rank == 2 then
			KLHTM_ThreatParameters.priestshadowdamage = 0.84
		elseif rank == 3 then
			KLHTM_ThreatParameters.priestshadowdamage = 0.75
		end
	
	elseif UnitClass("player") == KLHTM_String("class", "druid") then
		
		-- subtlety
		rank = KLHTM_GetTalentRank(KLHTM_String("talent", "druidsubtlety"))
		KLHTM_ThreatParameters.healing = KLHTM_ThreatParameters.healing * (1 - (0.04 * rank))
	
		-- feral instinct
		rank = KLHTM_GetTalentRank(KLHTM_String("talent", "feralinstinct"))
		KLHTM_ThreatParameters.feralinstinct = 0.03 * rank
		
		-- ferocity
		rank = KLHTM_GetTalentRank(KLHTM_String("talent", "ferocity"))
		for key, value in KLHTM_Abilities[KLHTM_String("spell", "maul")] do
			if value.rage then
				value.rage = value.rage - rank
			end
		end
		
		for key, value in KLHTM_Abilities[KLHTM_String("spell", "swipe")] do
			if value.rage then
				value.rage = value.rage - rank
			end
		end
				
		-- savage fury
		rank = KLHTM_GetTalentRank(KLHTM_String("talent", "savagefury"))
		KLHTM_ThreatParameters.savagefurybonus = rank * 0.1
		
	elseif UnitClass("player") == KLHTM_String("class", "mage") then
		
		-- arcanist 8 piece
		if KLHTM_GetSetPieces(KLHTM_String("sets", "arcanist")) == 8 then
			KLHTM_ThreatParameters.arcanist8active = true
		end
		
		-- arcane subtelty
		rank = KLHTM_GetTalentRank(KLHTM_String("talent", "arcanesubtlety"))
		if rank > 0 then 
			KLHTM_ThreatParameters.magearcanedamage = 0.9 - 0.1 * rank
		end
		
		-- netherwind 3 piece
		if KLHTM_GetSetPieces(KLHTM_String("sets", "netherwind")) > 2 then
			KLHTM_ThreatParameters.netherwind3active = true
		end
		
	elseif UnitClass("player") == KLHTM_String("class", "warlock") then
		-- master demonologist
		rank = KLHTM_GetTalentRank(KLHTM_String("talent", "masterdemonologist"))
		
		-- check for imp
		if UnitCreatureFamily("pet") == KLHTM_String("misc", "imp") then
			KLHTM_ThreatParameters.masterdemonologist = -0.04 * rank
		end
		
		-- nemesis 8 piece
		if KLHTM_GetSetPieces(KLHTM_String("sets", "nemesis")) == 8 then
			KLHTM_ThreatParameters.nemesis8active = true
		end
		
	elseif UnitClass("player") == KLHTM_String("class", "paladin") then
		-- healing
		KLHTM_ThreatParameters.healing = KLHTM_ThreatParameters.healing * 0.5
		
		-- righteous fury buff:
		if KLHTM_IsBuffTexturePresent("Interface\\Icons\\Spell_Holy_SealOfFury") then
			KLHTM_ThreatParameters.paladinholythreat = KLHTM_ThreatParameters.paladinholythreat + 0.6
		
			-- talents
			rank = KLHTM_GetTalentRank(KLHTM_String("talent", "righteousfury"))
			if rank == 1 then
				KLHTM_ThreatParameters.paladinholythreat = KLHTM_ThreatParameters.paladinholythreat + 0.16
			elseif rank == 2 then
				KLHTM_ThreatParameters.paladinholythreat = KLHTM_ThreatParameters.paladinholythreat + 0.33
			elseif rank == 3 then
				KLHTM_ThreatParameters.paladinholythreat = KLHTM_ThreatParameters.paladinholythreat + 0.5
			end
		end
	end
end

-- memory creation: 1 gfind for each ability in KLHTM_Abilities that you have. 
-- At most 5 calls (warriors with Shield Slam) = 320 bytes per call.
function KLHTM_RedoSpellRanks()
	
	-- fill the KLHTM_MySpellRanks array with key-value pairs, e.g.
	-- we get the rank from GetSpellName(number, "spell") = {name, "rank 5"}
	-- KLHTM_MySpellRanks["Sunder Armor"] = 5
	
	local index = 0
	local name
	local rankstring
	local rank
	
	while true do
		index = index + 1
		name, rankstring = GetSpellName(index, "spell")
		
		if name == nil then
			break
		end
		
		if KLHTM_Abilities[name] then
			rank = 0
			
			for rank in string.gfind(rankstring, KLHTM_String("misc", "spellrank")) do
				KLHTM_MySpellRanks[name] = rank
			end
		end
	end
end

function KLHTM_IsBuffTexturePresent(Texture)
	
	local x
	local texture
	
	for x = 1, 16 do
		texture = UnitBuff("player", x)
		
		if texture == nil then
			break
			
		elseif texture == Texture then
			return true
		end
	end
			
	return false
end

-- return: true if FD is up, false otherwise.
function GetFeignDeathValue()

	local x
	local texture
	local fdtexture = "Interface\\Icons\\Ability_Rogue_FeignDeath"
	
	for x = 1, 16 do
		texture = UnitBuff("player", x)
		if texture == nil then
			break
		elseif texture == fdtexture then
			return true
		end
	end
			
	return false
end

KLHTM_SpellSets = 
{
	["Warlock Destruction"] = 
	{
		KLHTM_String("spell", "shadowbolt"),
		KLHTM_String("spell", "immolate"),
		KLHTM_String("spell", "conflagrate"), 
		KLHTM_String("spell", "searingpain"), 
		KLHTM_String("spell", "rainoffire"), 
		KLHTM_String("spell", "soulfire"), 
		KLHTM_String("spell", "shadowburn"), 
		KLHTM_String("spell", "hellfire"), 
	},
	["Mage Offensive Arcane"] = 
	{
		KLHTM_String("spell", "arcanemissiles"), 
		KLHTM_String("spell", "arcaneexplosion"), 
		KLHTM_String("spell", "counterspell"), 
	},
	["Priest Shadow Spells"] = 
	{
		KLHTM_String("spell", "mindflay"), 
		KLHTM_String("spell", "devouringplague"), 
		KLHTM_String("spell", "shadowwordpain"), 
		KLHTM_String("spell", "mindblast"), 
		KLHTM_String("spell", "manaburn"), 
	}
}

-- return: true / false
function KLHTM_SpellMatchesSet(SetName, SpellName)
	
	local x = 0
	local spellset = KLHTM_SpellSets[SetName]
	local spell
	
	while true do
		x = x + 1
		spell = spellset[x]
		
		if spell == nil then 
			return false
		
		elseif spell == SpellName then
			return true
		end
	end
	
end

function KLHTM_TestTalents()
	local myclass = UnitClass("player")
	
	local key
	local value
	local rank
	local numtalents = 0
	
	for key, value in KLHTM_TalentInfo do
		if value[3] == myclass then
			rank = KLHTM_GetTalentRank(key)
			KLHTM_Print(string.format(KLHTM_String("print", "data", "talentpoint"), rank, key))
			numtalents = numtalents + 1
		end
	end
	
	KLHTM_Print(string.format(KLHTM_String("print", "data", "talent"), numtalents, myclass))
end

function KLHTM_GetTalentRank(Talent)
	
	local info = KLHTM_TalentInfo[Talent]
	local rank
	_, _, _, _, rank = GetTalentInfo(info[1], info[2])
	
	return rank
end

-- Values are {Page, Talent}
KLHTM_TalentInfo = 
{	
	[KLHTM_String("spell", "sunder")] = {3, 10, KLHTM_String("class", "warrior")},
	[KLHTM_String("spell", "heroicstrike")] = {1, 1, KLHTM_String("class", "warrior")},
	[KLHTM_String("talent", "defiance")] = {3, 9, KLHTM_String("class", "warrior")},
	[KLHTM_String("talent", "impale")] = {1, 11, KLHTM_String("class", "warrior")},
	[KLHTM_String("talent", "silentresolve")] = {1, 3, KLHTM_String("class", "priest")},
	[KLHTM_String("talent", "shadowaffinity")] = {3, 3, KLHTM_String("class", "priest")},
	[KLHTM_String("talent", "druidsubtlety")] = {3, 8, KLHTM_String("class", "druid")},
	[KLHTM_String("talent", "feralinstinct")] = {2, 3, KLHTM_String("class", "druid")},
	[KLHTM_String("talent", "ferocity")] = {2, 1, KLHTM_String("class", "druid")},
	[KLHTM_String("talent", "savagefury")] = {2, 13, KLHTM_String("class", "druid")},
	[KLHTM_String("talent", "masterdemonologist")] = {2, 15, KLHTM_String("class", "warlock")},
	[KLHTM_String("talent", "arcanesubtlety")] = {1, 1, KLHTM_String("class", "mage")},
	[KLHTM_String("talent", "righteousfury")] = {2, 7, KLHTM_String("class", "paladin")},
}

-------------------------------------------
--        Checking for Set Pieces        --
-------------------------------------------

-- This will print a list of all the (significant) set pieces you are wearing.
function KLHTM_DebugItemSets()
	
	local setname
	local output
	local pieces
	
	for setname in KLHTM_ItemSets do
		output = setname .. ": {" 
		_, pieces = KLHTM_GetSetPieces(setname, "non-nil")
		output = output .. pieces .. "}"
		KLHTM_Print(output)
	end
	
end

-- the key is the description, the value is the item slot index, for GetInventoryItemLink
KLHTM_ItemSlots = 
{
	["head"] = 1,
	["legs"] = 7,
	["shoulder"] = 3,
	["feet"] = 8,
	["waist"] = 6,
	["wrist"] = 9, 
	["chest"] = 5,
	["hands"] = 10,
}

-- values are item numbers. The numbers will be contained in an item link string.
KLHTM_ItemSets = 
{
	[KLHTM_String("sets", "might")] = 
	{
		["head"] = "16866",
		["legs"] = "16867",
		["shoulder"]= "16868",
		["feet"] = "16862",
		["waist"] = "16864",
		["wrist"] = "16861",
		["chest"] = "16865",
		["hands"] = "16863",
	},
	[KLHTM_String("sets", "bloodfang")] = 
	{
		["head"] = "16908",
		["legs"] = "16909",
		["shoulder"]= "16832",
		["feet"] = "16906",
		["waist"] = "16910",
		["wrist"] = "16911",
		["chest"] = "16905",
		["hands"] = "16907",
	},
	[KLHTM_String("sets", "arcanist")] = 
	{
		["head"] = "16795",
		["legs"] = "16796",
		["shoulder"]= "16797",
		["feet"] = "16800",
		["waist"] = "16802",
		["wrist"] = "16799",
		["chest"] = "16798",
		["hands"] = "16801",
	},
	[KLHTM_String("sets", "netherwind")] = 
	{
		["head"] = "16914",
		["legs"] = "16915",
		["shoulder"]= "16917",
		["feet"] = "16912",
		["waist"] = "16818",
		["wrist"] = "16918",
		["chest"] = "16916",
		["hands"] = "16913",
	},
	[KLHTM_String("sets", "nemesis")] = 
	{
		["head"] = "16929",
		["legs"] = "16930",
		["shoulder"]= "16932",
		["feet"] = "16927",
		["waist"] = "16933",
		["wrist"] = "16934",
		["chest"] = "16931",
		["hands"] = "16928",
	},
}

local setlist

function KLHTM_GetSetPieces(SetName, IsDebug)
	
	-- 1) Get the set list
	setlist = KLHTM_ItemSets[SetName]
	
	if setlist == nil then
		KLHTM_PrintTrace("Assertion", string.format("The set '" .. SetName .. "' does not exist in our database."))
		return 0
	end
	
	local slotname
	local slotnumber
	local debugout = ""
	local itemlink
	local numitems = 0
	
	for slotname, slotnumber in KLHTM_ItemSlots do
		itemlink = GetInventoryItemLink("player", slotnumber)
		
		if (itemlink ~= nil) and string.find(itemlink, setlist[slotname]) then
			numitems = numitems + 1
			
			if IsDebug then
				if numitems > 1 then
					debugout = debugout .. ", "
				end
				
				debugout = debugout .. slotname
			end
		end
	end
	
	return numitems, debugout
	
end

-- lukon mod: searches the group \ raid roster to update the player class table
function KLHTM_UpdateRaidClasses()
	
	local raidMembers = GetNumRaidMembers();
	
	if (raidMembers == 0) then
		
		local groupMembers = GetNumPartyMembers();
		for x = 1, groupMembers do
			KLHTM_PlayerClasses[UnitName("party" .. x)] = UnitClass("party" .. x);
		end
		
		-- also do yourself
		KLHTM_PlayerClasses[UnitName("player")] = UnitClass("player");
	else
		
		local class
		local name
		
		for x = 1, raidMembers do
			name, _, _, _, class = GetRaidRosterInfo(x);
			if name then
				KLHTM_PlayerClasses[name] = class;
			end
		end
	end
end
