local GUID_TYPE_MASK = 0x00F
local ENVIROMENT_GUID = "0x0000000000000000"
local EVENT_SWING_DAMAGE = "SWING_DAMAGE"
local EVENT_SWING_MISSED = "SWING_MISSED"
local EVENT_RANGE_DAMAGE = "RANGE_DAMAGE"
local EVENT_RANGE_MISSED = "RANGE_MISSED"
local EVENT_SPELL_DAMAGE = "SPELL_DAMAGE"
local EVENT_SPELL_PERIODIC_DAMAGE = "SPELL_PERIODIC_DAMAGE"
local EVENT_SPELL_MISSED = "SPELL_MISSED"
local EVENT_SPELL_PERIODIC_MISSED = "SPELL_PERIODIC_MISSED"
local EVENT_UNIT_DIED = "UNIT_DIED"
local EVENT_PARTY_KILL = "PARTY_KILL"

local _, threatpct
local combatStartTime = 0
local combatEndTime = GetTime()
local inCombat = false
local lastBlockableAttackTaken = 0
local lastUnblockableAttackTaken = 0
local knownEnemy
local knownEnemies = {}
local count

local events = {}

local status, damage

function events:PLAYER_REGEN_ENABLED() 
	inCombat = false
	combatEndTime = GetTime()
	wipe(knownEnemies)
end

function events:PLAYER_REGEN_DISABLED() 
	inCombat = true
	combatStartTime = GetTime()
	wipe(knownEnemies)
end

function IsPlayerGUID(guid)
	return bit.band(tonumber(guid:sub(1,5)),GUID_TYPE_MASK) == 0
end

local function IsPhysicalAttackEvent(event)
	return event == EVENT_SWING_DAMAGE or event == EVENT_SWING_MISSED or event == EVENT_RANGE_DAMAGE or event == EVENT_RANGE_MISSED
end

local function IsMagicalAttackEvent(event)
	return event == EVENT_SPELL_DAMAGE or event == EVENT_SPELL_PERIODIC_DAMAGE or event == EVENT_SPELL_MISSED or event == EVENT_SPELL_PERIODIC_MISSED
end

local function IsAttackEvent(event)
	return IsPhysicalAttackEvent(event) or IsMagicalAttackEvent(event)
end

local function IsDeathEvent(event)
	return event == EVENT_UNIT_DIED or event == EVENT_PARTY_KILL
end

local function UpdateKnownEnemies(guid,alive)
	knownEnemy = knownEnemies[guid]
	if not knownEnemy then
		knownEnemy = {firstSeen = GetTime(), isPlayer = IsPlayerGUID(guid)}
		knownEnemies[guid] = knownEnemy
	end
	knownEnemy.lastSeen = GetTime()
	knownEnemy.isAlive = alive
end

function events:COMBAT_LOG_EVENT_UNFILTERED(timeStamp,event,hideCaster,sourceGUID,sourceName,sourceFlags,sourceRaidFlags,destGUID,destName,destFlags,destRaidFlags,spellID,spellName) 
	if inCombat then
		if UnitGUID("player") == destGUID then
			if IsAttackEvent(event) and sourceGUID and sourceGUID ~= destGUID and sourceGUID ~= ENVIROMENT_GUID then
				UpdateKnownEnemies(sourceGUID,true)
			end
			if IsPhysicalAttackEvent(event) then
				lastBlockableAttackTaken = GetTime()
			elseif IsMagicalAttackEvent(event) then
				lastUnblockableAttackTaken = GetTime()
			end
		elseif IsAttackEvent(event) and UnitGUID("player") == sourceGUID and destGUID and destGUID ~= ENVIROMENT_GUID then
			UpdateKnownEnemies(destGUID,true)
		end                                            
		
		if IsDeathEvent(event) then
			UpdateKnownEnemies(sourceGUID,false)
		end
	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	

WhatsNext:AddConditionalStatement("Combat Time",{
	category = "Combat",
	test = function(checkTable)
		return inCombat and 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 = {type = "integer", default = 3}
	}
})

WhatsNext:AddConditionalStatement("No Combat Time",{
	category = "Combat",
	test = function(checkTable)
		return not inCombat and GetTime() - combatEndTime >= tonumber(checkTable.time)
	end,
	text = function(checkTable) 
		return "player has been out of combat for at least " .. tostring(checkTable.time) .. " seconds."
	end,
	arguments = {
		time = {type = "integer", default = 3}
	}
})

WhatsNext:AddConditionalStatement("In Combat",{
	category = "Combat",
	test = function(checkTable) 
		return inCombat
	end,
	text = function(checkTable) 
		return "player is in combat."
	end,
	arguments = {}
})

WhatsNext:AddConditionalStatement("In PvP Combat",{
	category = "Combat",
	test = function(checkTable) 
		for _, enemyTable in pairs(knownEnemies) do
			if enemyTable.isAlive and enemyTable.isPlayer then
				return true
			end
		end
		return false
	end,
	text = function(checkTable) 
		return "player is in PvP combat."
	end,
	arguments = {}
})

WhatsNext:AddConditionalStatement("NPC In Combat",{
	category = "Combat",
	test = function(checkTable) 
		return select(2,UnitDetailedThreatSituation(tostring(checkTable.unit),tostring(checkTable.npcUnit)))
	end,
	text = function(checkTable) 
		return GetUnitDisplayText(checkTable.unit) .. " is in combat with " .. GetUnitDisplayText(checkTable.npcUnit) .. " NPC."
	end,
	arguments = {
		unit = {type = "dropdown", default = "player", possibleValues = WhatsNext.valueLists.unitAny},
		npcUnit = {type = "dropdown", default = "target", possibleValues = WhatsNext.valueLists.unitAny},
	}
})

WhatsNext:AddConditionalStatement("Maximum Enemy Count",{
	category = "Combat",
	test = function(checkTable) 
		count = 0
		for _, enemyTable in pairs(knownEnemies) do
			if enemyTable.isAlive and enemyTable.lastSeen + tonumber(checkTable.seconds) >= GetTime() then
				count = count + 1
			end
		end
		return count <= tonumber(checkTable.amount)
	end,
	text = function(checkTable) 
		return "player has taken or dealt damage to " .. tostring(checkTable.amount) .. " or fewer enemies in the last " .. tostring(checkTable.seconds) .. " seconds."
	end,
	arguments = {
		amount = {type = "integer", default = 2},
		seconds = {type = "integer", default = 3},
	}
})

WhatsNext:AddConditionalStatement("Minimum Enemy Count",{
	category = "Combat",
	test = function(checkTable) 
		count = 0
		for _, enemyTable in pairs(knownEnemies) do
			if enemyTable.isAlive and enemyTable.lastSeen + tonumber(checkTable.seconds) >= GetTime() then
				count = count + 1
			end
		end
		return count >= tonumber(checkTable.amount)
	end,
	text = function(checkTable) 
		return "player has taken or dealt damage to " .. tostring(checkTable.amount) .. " or more enemies in the last " .. tostring(checkTable.seconds) .. " seconds."
	end,
	arguments = {
		amount = {type = "integer", default = 2},
		seconds = {type = "integer", default = 3},
	}
})

WhatsNext:AddConditionalStatement("Maximum Player Enemy Count",{
	category = "Combat",
	test = function(checkTable) 
		count = 0
		for _, enemyTable in pairs(knownEnemies) do
			if enemyTable.isAlive and enemyTable.isPlayer and enemyTable.lastSeen + tonumber(checkTable.seconds) >= GetTime() then
				count = count + 1
			end
		end
		return count <= tonumber(checkTable.amount)
	end,
	text = function(checkTable) 
		return "player has taken or dealt damage to " .. tostring(checkTable.amount) .. " or fewer enemy players in the last " .. tostring(checkTable.seconds) .. " seconds."
	end,
	arguments = {
		amount = {type = "integer", default = 2},
		seconds = {type = "integer", default = 3},
	}
})

WhatsNext:AddConditionalStatement("Minimum Player Enemy Count",{
	category = "Combat",
	test = function(checkTable) 
		count = 0
		for _, enemyTable in pairs(knownEnemies) do
			if enemyTable.isAlive and enemyTable.isPlayer and enemyTable.lastSeen + tonumber(checkTable.seconds) >= GetTime() then
				count = count + 1
			end
		end
		return count >= tonumber(checkTable.amount)
	end,
	text = function(checkTable) 
		return "player has taken or dealt damage to " .. tostring(checkTable.amount) .. " or more enemy players in the last " .. tostring(checkTable.seconds) .. " seconds."
	end,
	arguments = {
		amount = {type = "integer", default = 2},
		seconds = {type = "integer", default = 3},
	}
})

WhatsNext:AddConditionalStatement("In Boss Fight",{
	category = "Combat",
	test = function(checkTable) 
		return inCombat and (UnitExists("boss1") or UnitExists("boss2") or UnitExists("boss3") or UnitExists("boss4"))
	end,
	text = function(checkTable) 
		return "player is in combat with a boss enemy unit."
	end,
	arguments = {}
})

WhatsNext:AddConditionalStatement("Tanking", {
	category = "Tanking",
	test = function(checkTable) 
		status = UnitThreatSituation(tostring(checkTable.unit))
		return status and status > 1
	end,
	text = function(checkTable) 
		return GetUnitDisplayText(checkTable.unit) .. " is tanking at least 1 enemy NPC."
	end,
	arguments = {
		unit = {type = "dropdown", default = "player", possibleValues = WhatsNext.valueLists.unitGroup},
	}
})

WhatsNext:AddConditionalStatement("Tanking Target",{
	category = "Tanking",
	test = function(checkTable) 
		status = UnitThreatSituation(tostring(checkTable.unit1),tostring(checkTable.unit2))
		return status and status > 1
	end,
	text = function(checkTable) 
		return GetUnitDisplayText(checkTable.unit1) .. " is tanking the " .. GetUnitDisplayText(checkTable.unit2) .. " NPC."
	end,
	arguments = {
		unit1 = {type = "dropdown", default = "player", possibleValues = WhatsNext.valueLists.unitGroup},
		unit2 = {type = "dropdown", default = "target", possibleValues = WhatsNext.valueLists.unitAny},
	}
})

WhatsNext:AddConditionalStatement("Taking Damage",{
	category = "Tanking",
	test = function(checkTable) 
		return GetTime() - math.max(lastBlockableAttackTaken,lastUnblockableAttackTaken) <= tonumber(checkTable.time) 
	end,
	text = function(checkTable) 
		return "player has taken damage from any enemy unit in the last " .. tostring(checkTable.time)  .. " seconds."
	end,
	arguments = {
		time = {type = "integer", default = 4}
	}
})

WhatsNext:AddConditionalStatement("Taking Blockable Damage",{
	category = "Tanking",
	test = function(checkTable) 
		return GetTime() - lastBlockableAttackTaken <= tonumber(checkTable.time) 
	end,
	text = function(checkTable) 
		return "player has taken melee or range damage from any enemy unit in the last " .. tostring(checkTable.time) .. " seconds."
	end,
	arguments = {
		time = {type = "integer", default = 4}
	}
})

WhatsNext:AddConditionalStatement("Taking Unblockable Damage",{
	category = "Tanking",
	test = function(checkTable) 
		return GetTime() - lastUnblockableAttackTaken <= tonumber(checkTable.time) 
	end,
	text = function(checkTable) 
		return "player has taken spell damage from any enemy unit in the last " .. tostring(checkTable.time)  .. " seconds."
	end,
	arguments = {
		time = {type = "integer", default = 4}
	}
})

WhatsNext:AddConditionalStatement("Threat Percent",{
	category = "Threat",
	test = function(checkTable) 
		_,_,threatpct = UnitDetailedThreatSituation(checkTable.unit1,checkTable.unit2);
		return threatpct and tonumber( checkTable.threatPercent) and threatpct > tonumber(checkTable.threatPercent) / 100
	end,
	text = function(checkTable) 
		threatpct = tonumber(checkTable.threatPercent)
		return GetUnitDisplayText(checkTable.unit1) .. " has at least " .. tostring(threatpct and threatpct*100 or checkTable.threatPercent) .. "% of the threat on " .. GetUnitDisplayText(checkTable.unit2)  .. " needed to pull agro."
	end,
	arguments = {
		unit1 = {type = "dropdown", default = "player", possibleValues = WhatsNext.valueLists.unitAny},
		unit2 = {type = "dropdown", default = "target", possibleValues = WhatsNext.valueLists.unitAny},
		threatPercent = {type = "percent", default = .95}
	}
})

WhatsNext:AddConditionalStatement("Raw Threat Percent",{
	category = "Threat",
	test = function(checkTable) 
		_,_,_,threatpct = UnitDetailedThreatSituation(checkTable.unit1,checkTable.unit2);
		return threatpct and tonumber( checkTable.threatPercent) and threatpct > tonumber( checkTable.threatPercent) / 100
	end,
	text = function(checkTable) 
		threatpct = tonumber(checkTable.threatPercent)
		return GetUnitDisplayText(checkTable.unit1) .. " has at least " .. tostring(threatpct and threatpct*100 or checkTable.threatPercent) .. "% of the tank's threat on " .. GetUnitDisplayText(checkTable.unit2)  .. "."
	end,
	arguments = {
		unit1 = {type = "dropdown", default = "player", possibleValues = WhatsNext.valueLists.unitAny},
		unit2 = {type = "dropdown", default = "target", possibleValues = WhatsNext.valueLists.unitAny},
		threatPercent = {type = "percent", default = .95}
	}
})

WhatsNext:AddConditionalStatement("Raw Threat",{
	category = "Threat",
	test = function(checkTable) 
		_,_,_,_,threatpct = UnitDetailedThreatSituation(checkTable.unit1,checkTable.unit2);
		return threatpct and tonumber(checkTable.threat) and threatpct > tonumber(checkTable.threat)
	end,
	text = function(checkTable) 
		return GetUnitDisplayText(checkTable.unit1) .. " has at least " .. tostring(checkTable.threat) .. " threat on " .. GetUnitDisplayText(checkTable.unit2)  .. "."
	end,
	arguments = {
		unit1 = {type = "dropdown", default = "player", possibleValues = WhatsNext.valueLists.unitAny},
		unit2 = {type = "dropdown", default = "target", possibleValues = WhatsNext.valueLists.unitAny},
		threat = {type = "integer", default = 40000}
	}
})