
local DAMAGE_TAKEN_MEMORY_LENGTH = 4
local EARLY_CAST_EXPIRE_MARGIN = 3
local MINIMUM_TIME_BETWEEN_INTERRUPTS = .3

local combatStartTime = 0
local lastInterrupt = 0
local checkResults = {}
local inCombat = false
local lastBlockableAttackTaken = 0
local lastUnblockableAttackTaken = 0
local lastNotBehindTarget = 0
local enemyCount = 0
local categories
local knownEnemies = {}
local powerGeneration = {
	["Revenge"] = 15,
	["Shield Slam"] = 20,
	["Berserker Rage"] = 10,
	["Battle Shout"] = 20,
	["Commanding Shout"] = 20,
	["Sword and Board"] = 5,
}


function WhatsNext_GetPowerGeneration(spellName)
	local rtnAmount = powerGeneration[spellName] or 0
	if spellName == "Shield Slam" and UnitBuff("player", "Sword and Board") then
		rtnAmount = rtnAmount + powerGeneration["Sword and Board"] 
	end
	
	return rtnAmount
end

local function GetRangeToRaidMember(unit)
	local _, zx1, zy1, zx2, zy2 = GetCurrentMapZone();
	local px,py = GetPlayerMapPosition("player");
	local rx,ry = GetPlayerMapPosition(unit);
	if (rx == 0 and ry == 0) or (px == 0 and py == 0) or not ( zx1 and zx2 and zy1 and zy2) then
		return 100000
	end
	local dx,dy = math.abs(px - rx) * (zx1 - zx2), math.abs(py - ry) * (zy1 - zy2)
	return math.sqrt(dx*dx + dy*dy)
end

local checks = {
	["Combat Time"] = {
		category = "Basic",
		test = function(checkTable)
			return GetTime() - combatStartTime >= tonumber(checkTable.time)
		end,
		text = function(checkTable) 
			return "player has been in combat for at least " .. tostring(checkTable.time) .. " seconds."
		end,
		arguments = {
			["time"] = "integer"
		}
	},
	["Minimum Power"] = {
		category = "Power",
		test = function(checkTable) 
			return UnitPower("player") >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's power is at or above " .. tostring(checkTable.amount) .. "."
		end,
		arguments = {
			["amount"] = "integer"
		}
	},
	["Excess Power"] = {
		category = "Power",
		test = function(checkTable) 
			return UnitPowerMax("player") < UnitPower("player") + WhatsNext_GetPowerGeneration(checkTable.spellName)
		end,
		text = function(checkTable) 
			return tostring(checkTable.spellName) .. " would generate excess power."
		end,
		arguments = {
			["spellName"] = "text"
		}
	},
	["Maximum Power"] = {
		category = "Power",
		test = function(checkTable) 
			return UnitPower("player") <= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's power is at or below " .. tostring(checkTable.amount) .. "."
		end,
		arguments = {
			["amount"] = "integer"
		}
	},
	["Minimum Missing Power"] = {
		category = "Power",
		test = function(checkTable) 
			return UnitPowerMax("player") - UnitPower("player") >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's power is at least " .. tostring(checkTable.amount) .. " below their maximum power."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Maximum Missing Power"] = {
		category = "Power",
		test = function(checkTable) 
			return UnitPowerMax("player") - UnitPower("player") <= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's power is no more than " .. tostring(checkTable.amount) .. " below their maximum power."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Minimum Resultant Power"] = {
		category = "Power",
		test = function(checkTable) 
			local _, _, _, cost, _, _, _, _, _ = GetSpellInfo(checkTable.spellName)
			return UnitPower("player") - cost >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's power would be at least " .. tostring(checkTable.amount) .. " after casting ".. tostring(checkTable.spellName) .."."
		end,
		arguments = {["amount"] = "integer",["spellName"] = "text"}
	},
	["Maximum Resultant Power"] = {
		category = "Power",
		test = function(checkTable) 
			local _, _, _, cost, _, _, _, _, _ = GetSpellInfo(checkTable.spellName)
			return UnitPowerMax("player") - cost <= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's power would be no more than " .. tostring(checkTable.amount) .. " after casting ".. tostring(checkTable.spellName) .."."
		end,
		arguments = {["amount"] = "integer",["spellName"] = "text"}
	},
	["Minimum Cost"] = {
		category = "Ability",
		test = function(checkTable) 
			local _, _, _, cost, _, _, _, _, _ = GetSpellInfo(checkTable.spellName)
			return cost >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return tostring(checkTable.spellName) .. " costs at least " .. tostring(checkTable.amount) .. " power."
		end,
		arguments = {["amount"] = "integer",["spellName"] = "text"}
	},
	["Maximum Cost"] = {
		category = "Ability",
		test = function(checkTable) 
			local _, _, _, cost, _, _, _, _, _ = GetSpellInfo(checkTable.spellName)
			return cost <= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return tostring(checkTable.spellName) .. " costs no more than " .. tostring(checkTable.amount) .. " power."
		end,
		arguments = {["amount"] = "integer",["spellName"] = "text"}
	},
	["Target Is Interruptable"] = {
		category = "Casting",
		test = function(checkTable) 
			local spell1, _, _, _, _, _, _, _, notInterruptible1  = UnitCastingInfo("target")
			local spell2, _, _, _, _, _, _, notInterruptible2  = UnitChannelInfo("target")
			return (spell1 and not notInterruptible1)  or (spell2 and not notInterruptible2)
		end,
		text = function(checkTable) 
			return "target is casting an interruptable spell."
		end,
		arguments = {}
	},
	["Target Is Casting"] = {
		category = "Casting",
		test = function(checkTable) 
			return UnitCastingInfo("target") or UnitChannelInfo("target")
		end,
		text = function(checkTable) 
			return "target is casting a spell."
		end,
		arguments = {}
	},
	["Target Is Casting Specific"] = {
		category = "Casting",
		test = function(checkTable) 
			return UnitCastingInfo("target") == checkTable.spellName or UnitChannelInfo("target") == checkTable.spellName
		end,
		text = function(checkTable) 
			return "target is casting " .. tostring(checkTable.spellName)  .. "."
		end,
		arguments = {["spellName"] = "text"}
	},
	["Target Is Uninterruptable"] = {
		category = "Casting",
		test = function(checkTable) 
			local spell1, _, _, _, _, _, _, _, notInterruptible1  = UnitCastingInfo("target")
			local spell2, _, _, _, _, _, _, notInterruptible2  = UnitChannelInfo("target")
			return (spell1 and notInterruptible1)  or (spell2 and notInterruptible2)
		end,
		text = function(checkTable) 
			return "target is casting an uninterruptable spell."
		end,
		arguments = {}
	},
	["Tanking"] = {
		category = "Tanking",
		test = function(checkTable) 
			local status = UnitThreatSituation("player")
			return status and status > 1
		end,
		text = function(checkTable) 
			return "player is tanking at least 1 enemy NPC."
		end,
		arguments = {}
	},
	["Tanking Target"] = {
		category = "Tanking",
		test = function(checkTable) 
			local status = UnitThreatSituation("player")
			return status and status > 1
		end,
		text = function(checkTable) 
			return "player is tanking the target NPC."
		end,
		arguments = {}
	},
	["Player Crowd Controlled"] = {
		category = "Basic",
		test = function(checkTable) 
			return HasFullControl() ~= 1
		end,
		text = function(checkTable) 
			return "player is suffering from some form of crowd control."
		end,
		arguments = {}
	}, 
	["Target Has Debuff"] = {
		category = "Buff/Debuff",
		test = function(checkTable)
			local name, _, _, count, _, _, _  = UnitDebuff("target", checkTable.debuffName)
			return UnitName("target") and name and math.max(count,1) >= tonumber(checkTable.count or 1)
		end,
		text = function(checkTable)
			return "target has " .. tostring(checkTable.count  or 1) .. " stack(s) of the " .. tostring(checkTable.debuffName) .. " debuff."
		end,
		arguments = {["debuffName"] = "text", ["count"] = "integer"}	
	},
	["Target Need Debuff"] = {
		category = "Buff/Debuff",
		test = function(checkTable) 
			local name, _, _, count, _, _, expirationTime  = UnitDebuff("target", checkTable.debuffName)
			return UnitName("target") and (not name or (math.max(count,1) < tonumber(checkTable.count or 1)) or expirationTime - tonumber(checkTable.time or EARLY_CAST_EXPIRE_MARGIN) < GetTime())
		end,
		text = function(checkTable) 
			return "target does not have " .. tostring(checkTable.count or 1) .. " stack(s) of " .. tostring(checkTable.debuffName) .. " or it will drop off within " .. tostring(checkTable.time or EARLY_CAST_EXPIRE_MARGIN) .. " seconds." 
		end,
		arguments = {["debuffName"] = "text", ["time"] = "integer", ["count"] = "integer"}
	},
	["In Range"] = {
		category = "Position",
		test = function(checkTable) 
			return UnitName("target") and IsSpellInRange(checkTable.spellName,"target") == 1
		end,
		text = function(checkTable) 
			return "target is within range of " .. tostring(checkTable.spellName) .. "."
		end,
		arguments = {["spellName"] = "text"}
	},
	["Target In 9.9 Yards"] = {
		category = "Position",
		test = function(checkTable) 
			return UnitName("target") and CheckInteractDistance("target", 3)
		end,
		text = function(checkTable) 
			return "target is within 9.9 yards."
		end,
		arguments = {}
	},
	["Target In 11.11 Yards"] = {
		category = "Position",
		test = function(checkTable) 
			return UnitName("target") and CheckInteractDistance("target", 2)
		end,
		text = function(checkTable) 
			return "target is within 11.11 yards."
		end,
		arguments = {}
	},
	["Target In 28 Yards"] = {
		category = "Position",
		test = function(checkTable) 
			return UnitName("target") and CheckInteractDistance("target", 1)
		end,
		text = function(checkTable) 
			return "target is within 28 yards."
		end,
		arguments = {}
	},
	["In Combat"] = {
		category = "Basic",
		test = function(checkTable) 
			return inCombat
		end,
		text = function(checkTable) 
			return "player is in combat."
		end,
		arguments = {}
	},
	["Taking Damage"] = {
		category = "Tanking",
		test = function(checkTable) 
			return GetTime() - math.max(lastBlockableAttackTaken,lastUnblockableAttackTaken) <= DAMAGE_TAKEN_MEMORY_LENGTH
		end,
		text = function(checkTable) 
			return "player has taken damage from an enemy unit in the last " .. DAMAGE_TAKEN_MEMORY_LENGTH .. " seconds."
		end,
		arguments = {}
	},
	["Taking Blockable Damage"] = {
		category = "Tanking",
		test = function(checkTable) 
			return GetTime() - lastBlockableAttackTaken <= DAMAGE_TAKEN_MEMORY_LENGTH
		end,
		text = function(checkTable) 
			return "player has taken blockable damage from an enemy unit in the last " .. DAMAGE_TAKEN_MEMORY_LENGTH .. " seconds."
		end,
		arguments = {}
	},
	["Taking Unblockable Damage"] = {
		category = "Tanking",
		test = function(checkTable) 
			return GetTime() - lastUnblockableAttackTaken <= DAMAGE_TAKEN_MEMORY_LENGTH
		end,
		text = function(checkTable) 
			return "player has taken unblockable damage from an enemy unit in the last " .. DAMAGE_TAKEN_MEMORY_LENGTH .. " seconds."
		end,
		arguments = {}
	},
	["Maximum Health Percent"] = {
		category = "Health",
		test = function(checkTable) 
			return UnitHealth("player")/UnitHealthMax("player") <= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's health percent is less then or equal to " .. tostring(checkTable.amount) .. "."
		end,
		arguments = {["amount"] = "percent"}
	},
	["Target Maximum Health Percent"] = {
		category = "Health",
		test = function(checkTable) 
			return UnitHealth("target")/UnitHealthMax("target") <= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "target's health percent is less then or equal to " .. tostring(checkTable.amount) .. "."
		end,
		arguments = {["amount"] = "percent"}
	},
	["Maximum Enemy Count"] = {
		category = "Basic",
		test = function(checkTable) 
			return enemyCount <= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "exists " .. tostring(checkTable.amount) .. " or fewer known enemies."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Minimum Enemy Count"] = {
		category = "Basic",
		test = function(checkTable) 
			return enemyCount >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "exists " .. tostring(checkTable.amount) .. " or more known enemies."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Attackable Target"] = {
		category = "Basic",
		test = function(checkTable) 
			return UnitName("target") and UnitCanAttack("player","target") and not UnitIsDeadOrGhost("target") 
		end,
		text = function(checkTable) 
			return "target can be attacked."
		end,
		arguments = {}
	},
	["In Boss Fight"] = {
		category = "Basic",
		test = function(checkTable) 
			return UnitExists("boss1") or UnitExists("boss2") or UnitExists("boss3") or UnitExists("boss4")
		end,
		text = function(checkTable) 
			return "player is in combat with an boss enemy unit."
		end,
		arguments = {}
	},
	["Target Is"] = {
		category = "Basic",
		test = function(checkTable) 
			return UnitName("target") == checkTable.name
		end,
		text = function(checkTable) 
			return "target's name is ".. tostring(checkTable.name) .. "."
		end,
		arguments = {["name"] = "text"}
	},
	["Player Buffed"] = {
		category = "Buff/Debuff",
		test = function(checkTable) 
			local name, _, _, count, _, _, _, unit = UnitBuff("player", checkTable.buffName) 
			return name and math.max(count,1) >=  tonumber(checkTable.count or 1) 
		end,
		text = function(checkTable) 
			return "player has " .. tostring(checkTable.count or 1) .. " stack(s) of the " .. tostring(checkTable.buffName) .. " buff."
		end,
		arguments = {buffName = "text", ["count"] = "integer"}
	},
	["Player Buffed By Player"] = {
		category = "Buff/Debuff",
		test = function(checkTable) 
			local name, _, _, count, _, _, _, unit = UnitBuff("player", checkTable.buffName) 
			return name and UnitIsUnit(unit,"player") and math.max(count,1) >=  tonumber(checkTable.count or 1) 
		end,
		text = function(checkTable) 
			return "player has " .. tostring(checkTable.count or 1) .. " stack(s) of their own " .. tostring(checkTable.buffName) .. " buff."
		end,
		arguments = {buffName = "text", ["count"] = "integer"}
	},
	["Raid Buffed"] = {
		category = "Buff/Debuff",
		test = function(checkTable) 
			if not UnitBuff("player", checkTable.buffName) then
				return false
			end
			for i = 1,40 do
				local raidUnit = "raid"..i
				if UnitExists(raidUnit) and GetRangeToRaidMember(i) <= tonumber(checkTable.yards) and not UnitBuff(raidUnit, checkTable.buffName) then
					return false
				end				
			end
			return true
		end,
		text = function(checkTable) 
			return "all raid members in " .. tostring(checkTable.yards) .. " yards have the " .. tostring(checkTable.buffName) .. " buff."
		end,
		arguments = {["buffName"] = "text",["yards"] = "integer"}
	},
	["Raid Buffed By Player"] = {
		category = "Buff/Debuff",
		test = function(checkTable)
			local name, _, _, _, _, _, _, unit = UnitBuff("player", checkTable.buffName) 
			if not name or not UnitIsUnit(unit,"player") then
				return false
			end	
			for i = 1,40 do
				local raidUnit = "raid"..i
				if UnitExists(raidUnit) then
					local name, _, _, _, _, _, _, unit = UnitBuff(raidUnit, checkTable.buffName) 
					if GetRangeToRaidMember(raidUnit) <= tonumber(checkTable.yards) and (not name or not UnitIsUnit(unit,"player")) then
						return false
					end				
				end
			end
			return true
		end,
		text = function(checkTable) 
			return "all raid members in " .. tostring(checkTable.yards) .. " yards have player's " .. tostring(checkTable.buffName) .. " buff."
		end,
		arguments = {["buffName"] = "text",["yards"] = "integer"}
	},
	["Ability Cooldown"] = {
		category = "Ability",
		test = function(checkTable)
			local start, duration = GetSpellCooldown(checkTable.spellName)
			return start and start > 0 and start + duration - GetTime() > tonumber(checkTable.time)
		end,
		text = function(checkTable)
			return tostring(checkTable.spellName) .. " will be on cooldown for more than " .. tostring(checkTable.time) .. " second(s)."
		end,
		arguments = {["spellName"] = "text",["time"] = "integer"}
	},
	["Combo Points"] = {
		category = "Resources",
		test = function(checkTable) 
			return GetComboPoints("player","target") >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player has at least "..tostring(checkTable.amount).." combo points on target."
		end,
		arguments = {
			["amount"] = "integer"
		}
	},
	["Stealthed"] = {
		category = "Basic",
		test = function(checkTable) 
			return IsStealthed() == 1
		end,
		text = function(checkTable) 
			return "player is stealthed."
		end,
		arguments = {}
	},
	["Strength"] = {
		category = "Primary Stats",
		test = function(checkTable) 
			return select(2,UnitStat("player",1)) >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's total strength is at or above ".. tostring(checkTable.amount) .. "."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Base Strength"] = {
		category = "Primary Stats",
		test = function(checkTable) 
			return select(1,UnitStat("player",1)) >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's base strength is at or above ".. tostring(checkTable.amount) .. "."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Agility"] = {
		category = "Primary Stats",
		test = function(checkTable) 
			return select(2,UnitStat("player",2)) >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's total agility is at or above ".. tostring(checkTable.amount) .. "."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Base Agility"] = {
		category = "Primary Stats",
		test = function(checkTable) 
			return select(1,UnitStat("player",2)) >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's base agility is at or above ".. tostring(checkTable.amount) .. "."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Stamina"] = {
		category = "Primary Stats",
		test = function(checkTable) 
			return select(2,UnitStat("player",3)) >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's total stamina is at or above ".. tostring(checkTable.amount) .. "."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Base Stamina"] = {
		category = "Primary Stats",
		test = function(checkTable) 
			return select(1,UnitStat("player",3)) >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's base stamina is at or above ".. tostring(checkTable.amount) .. "."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Intellect"] = {
		category = "Primary Stats",
		test = function(checkTable) 
			return select(2,UnitStat("player",4)) >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's total intellect is at or above ".. tostring(checkTable.amount) .. "."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Base Intellect"] = {
		category = "Primary Stats",
		test = function(checkTable) 
			return select(1,UnitStat("player",4)) >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's base intellect is at or above ".. tostring(checkTable.amount) .. "."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Spirit"] = {
		category = "Primary Stats",
		test = function(checkTable) 
			return select(2,UnitStat("player",5)) >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's total spirit is at or above ".. tostring(checkTable.amount) .. "."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Base Spirit"] = {
		category = "Primary Stats",
		test = function(checkTable) 
			return select(1,UnitStat("player",5)) >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's base spirit is at or above ".. tostring(checkTable.amount) .. "."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Dodge Rating"] = {
		category = "Secondary Stats",
		test = function(checkTable) 
			return GetCombatRating(3) >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's dodge rating is at or above ".. tostring(checkTable.amount) .. "."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Parry Rating"] = {
		category = "Secondary Stats",
		test = function(checkTable) 
			return GetCombatRating(4) >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's parry rating is at or above ".. tostring(checkTable.amount) .. "."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Melee Hit Rating"] = {
		category = "Secondary Stats",
		test = function(checkTable) 
			return GetCombatRating(6) >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's melee hit rating is at or above ".. tostring(checkTable.amount) .. "."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Range Hit Rating"] = {
		category = "Secondary Stats",
		test = function(checkTable) 
			return GetCombatRating(7) >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's range hit rating is at or above ".. tostring(checkTable.amount) .. "."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Spell Hit Rating"] = {
		category = "Secondary Stats",
		test = function(checkTable) 
			return GetCombatRating(8) >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's spell hit rating is at or above ".. tostring(checkTable.amount) .. "."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Melee Crit Rating"] = {
		category = "Secondary Stats",
		test = function(checkTable) 
			return GetCombatRating(9) >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's melee crit rating is at or above ".. tostring(checkTable.amount) .. "."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Range Crit Rating"] = {
		category = "Secondary Stats",
		test = function(checkTable) 
			return GetCombatRating(10) >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's range crit rating is at or above ".. tostring(checkTable.amount) .. "."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Spell Crit Rating"] = {
		category = "Secondary Stats",
		test = function(checkTable) 
			return GetCombatRating(11) >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's spell crit rating is at or above ".. tostring(checkTable.amount) .. "."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Melee Haste Rating"] = {
		category = "Secondary Stats",
		test = function(checkTable) 
			return GetCombatRating(19) >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's melee haste rating is at or above ".. tostring(checkTable.amount) .. "."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Range Haste Rating"] = {
		category = "Secondary Stats",
		test = function(checkTable) 
			return GetCombatRating(19) >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's range haste rating is at or above ".. tostring(checkTable.amount) .. "."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Spell Haste Rating"] = {
		category = "Secondary Stats",
		test = function(checkTable) 
			return GetCombatRating(20) >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's spell haste rating is at or above ".. tostring(checkTable.amount) .. "."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Expertise Rating"] = {
		category = "Secondary Stats",
		test = function(checkTable) 
			return GetCombatRating(24) >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's expertise rating is at or above ".. tostring(checkTable.amount) .. "."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Mastery Rating"] = {
		category = "Secondary Stats",
		test = function(checkTable) 
			return GetCombatRating(26) >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player's mastery rating is at or above ".. tostring(checkTable.amount) .. "."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Rune Ready Count"] = {
		category = "Resources",
		test = function(checkTable) 
			local count = 0
			for i = 1,6 do
				if select(3,GetRuneCooldown(i)) then
					count = count + 1
				end
			end
			return  count >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player has at least ".. tostring(checkTable.amount) .. " runes off cooldown."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Blood Rune Ready Count"] = {
		category = "Resources",
		test = function(checkTable) 
			local count = 0
			for i = 1,6 do
				local type = GetRuneType(i)
				if select(3,GetRuneCooldown(i)) and (type == 1 or type == 2) then
					count = count + 1
				end
			end
			return  count >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player has at least ".. tostring(checkTable.amount) .. " blood runes off cooldown."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Frost Rune Ready Count"] = {
		category = "Resources",
		test = function(checkTable) 
			local count = 0
			for i = 1,6 do
				local type = GetRuneType(i)
				if select(3,GetRuneCooldown(i)) and (type == 3 or type == 2) then
					count = count + 1
				end
			end
			return  count >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player has at least ".. tostring(checkTable.amount) .. " frost runes off cooldown."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Death Rune Ready Count"] = {
		category = "Resources",
		test = function(checkTable) 
			local count = 0
			for i = 1,6 do
				local type = GetRuneType(i)
				if select(3,GetRuneCooldown(i)) and (type == 4 or type == 2) then
					count = count + 1
				end
			end
			return  count >= tonumber(checkTable.amount)
		end,
		text = function(checkTable) 
			return "player has at least ".. tostring(checkTable.amount) .. " death runes off cooldown."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Time Till Death"] = {
		category = "Death Clocks",
		test = function(checkTable) 
			local unit = DeathClock and DeathClock.units and DeathClock.units[UnitGUID("target")]
			return unit and unit.time and unit.time <= tonumber(checkTable.time) 
		end,
		text = function(checkTable) 
			return "target is expected to die within " .. tostring(checkTable.time) .. " seconds. (Requires the Death Clocks addon)"
		end,
		arguments = {["time"] = "integer"}
	},
	["Life Expectancy"] = {
		category = "Death Clocks",
		test = function(checkTable) 
			local unit = DeathClock and DeathClock.units and DeathClock.units[UnitGUID("target")]
			return unit and unit.time and unit.time >= tonumber(checkTable.time) 
		end,
		text = function(checkTable) 
			return "target is expected to live for at least " .. tostring(checkTable.time) .. " seconds. (Requires the Death Clocks addon)"
		end,
		arguments = {["time"] = "integer"}
	},
	["Holy Power"] = {
		category = "Resources",
		test = function(checkTable) 
			return tonumber(checkTable.amount) >= UnitPower("player" ,9)
		end,
		text = function(checkTable) 
			return "player has at least " .. tostring(checkTable.amount) .. " holy power."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Soul Shards"] = {
		category = "Resources",
		test = function(checkTable) 
			return tonumber(checkTable.amount) >= UnitPower("player" ,7)
		end,
		text = function(checkTable) 
			return "player has at least " .. tostring(checkTable.amount) .. " soul shards."
		end,
		arguments = {["amount"] = "integer"}
	},
	["Not Behind Target Detected"] = {
		category = "Position",
		test = function(checkTable) 
			return GetTime() - lastNotBehindTarget <= tonumber(checkTable.time)
		end,
		text = function(checkTable) 
			return "has been detected player was not behind their target in the last " .. tostring(checkTable.time) .. " seconds."
		end,
		arguments = {["time"] = "integer"}
	
	},
	
}

function WhatsNext:PrintChecks()
	for checkType,checkEntry in pairs(WhatsNext.defaultChecks) do
		print(checkType)
		print("   ", checkEntry.text({}))
	end
end

function WhatsNext:ResetTestChecks()
	SetMapToCurrentZone() 
	wipe(checkResults)
end

function WhatsNext:TestCheck(checkTable)
	if not checkResults[self:GetCheckText(checkTable)] then
		if checks[checkTable.type].test(checkTable) then
			checkResults[self:GetCheckText(checkTable)] = checkTable.invert and 0 or 1
		else
			checkResults[self:GetCheckText(checkTable)] = checkTable.invert and 1 or 0
		end
	end
	return checkResults[self:GetCheckText(checkTable)] == 1
end

function WhatsNext:TestChecks(checkList)
	for i,checkTable in ipairs(checkList or {}) do
		if not self:TestCheck(checkTable) then
			return false
		end
	end
	return true
end

function WhatsNext:GetArguments(checkType)
	if checks[checkType] then
		return checks[checkType].arguments
	end
end

function WhatsNext:GetCheckText(checkTable)
	if checks[checkTable.type] then
		return (checkTable.invert and "Not if " or "If ") .. checks[checkTable.type].text(checkTable)
	end
end

function WhatsNext:GetSampleCheckTexts(category)
	local sampleTexts = {}
	for checkType, check in pairs(checks) do
		if not category or category == check.category then
			sampleTexts[checkType] = self:GetSampleCheckText(checkType)
		end
	end
	return sampleTexts
end

function WhatsNext:GetCheckCategories()
	if not categories then
		categories = {}
		for type, check in pairs(checks) do
			 if not tContains(categories,check.category) then
				tinsert(categories,check.category)
			 end
		end
	end
	return categories
end

function WhatsNext:GetSampleCheckText(checkType)
	local check = checks[checkType]
	local checkTable = {type = checkType}
	for var,varType in pairs(check.arguments) do
		checkTable[var] = "{"..var.."}"
	end
	return  "If " .. check.text(checkTable)
end

-------------------
-- Set up events
-------------------

local events = {}

function events:UI_ERROR_MESSAGE(message)
	if message == "You must be behind your target." then
		lastNotBehindTarget = GetTime()
	end
end	

function events:PLAYER_REGEN_ENABLED() 
	inCombat = false
	wipe(knownEnemies)
	enemyCount = 0
end

function events:PLAYER_REGEN_DISABLED() 
	inCombat = true
	combatStartTime = GetTime()
	enemyCount = 0
end

function events:COMBAT_LOG_EVENT_UNFILTERED(timeStamp,event,hideCaster,sourceGUID,sourceName,sourceFlags,sourceRaidFlags,destGUID,destName,destFlags,destRaidFlags,spellID,spellName) 
	local damage = strsub(event,-6)  == "DAMAGE"
	if UnitGUID("player") == destGUID then
		if damage and sourceGUID and not knownEnemies[sourceGUID] then
			enemyCount = enemyCount + 1
			knownEnemies[sourceGUID] = 1
		end
		if event == "SWING_DAMAGE" or event == "SWING_MISSED" or event == "RANGE_DAMAGE" or event == "RANGE_MISSED" then
			lastBlockableAttackTaken = GetTime()
		elseif event == "SPELL_DAMAGE" or event == "SPELL_PERIODIC_DAMAGE" then
			lastUnblockableAttackTaken = GetTime()
		elseif event == "SPELL_MISSED" or event == "SPELL_PERIODIC_MISSED" then
			lastUnblockableAttackTaken = GetTime()
		end
	elseif damage and UnitGUID("player") == sourceGUID and destGUID and not knownEnemies[destGUID] then
		enemyCount = enemyCount + 1
		knownEnemies[destGUID] = 1
	end
	
	if (event == "UNIT_DIED" or event == "PARTY_KILL") and knownEnemies[destGUID] == 1 then
		knownEnemies[destGUID] = 0
		enemyCount = enemyCount - 1
	end
end

local function EventFunc(self, event, ...)
	events[event](event, ...)
end
local eventHandler = CreateFrame("Frame", nil, WorldFrame)
eventHandler:SetScript("OnEvent", EventFunc)
for eventname in pairs(events) do eventHandler:RegisterEvent(eventname) end	