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)
	if tonumber(guid:sub(1,5)) then
		return bit.band(tonumber(guid:sub(1,5)),GUID_TYPE_MASK) == 0
	end
	return nil
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,name)
	knownEnemy = knownEnemies[guid]
	if not knownEnemy then
		knownEnemy = {firstSeen = GetTime(), isPlayer = IsPlayerGUID(guid), name = name, lastHitGiven = 0, lastHitTaken = 0}
		knownEnemies[guid] = knownEnemy
	end
	knownEnemy.lastSeen = GetTime()
	knownEnemy.isAlive = alive
end

function events:COMBAT_LOG_EVENT_UNFILTERED() 
	if inCombat then
		local  timestamp, event, hideCaster, sourceGUID, sourceName, sourceFlags, sourceRaidFlags, destGUID, destName, destFlags, destRaidFlags = CombatLogGetCurrentEventInfo()
		if UnitGUID("player") == destGUID then
			if IsAttackEvent(event) and sourceGUID and sourceGUID ~= destGUID and sourceGUID ~= ENVIROMENT_GUID then
				UpdateKnownEnemies(sourceGUID,true,sourceName)
			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,destName)
		end                                            
		
		if IsDeathEvent(event) then
			UpdateKnownEnemies(sourceGUID,false,sourceName)
		end
	end
	local count = 0
	for _, enemyTable in pairs(knownEnemies) do
		if enemyTable.isAlive and enemyTable.lastSeen + 5 >= GetTime() then
			--print(enemyTable.name)
			count = count+1
		end
	end
	--print("Fighting " .. count)
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",
	text = "player has been in combat for at least {time} seconds.",
	test = function(checkTable)
		return inCombat and GetTime() - combatStartTime >= tonumber(checkTable.time)
	end,
	arguments = {
		time = {label = "Seconds", type = "integer", default = 3}
	}
})

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

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

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

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

WhatsNext:AddConditionalStatement("NPC In Combat Party",{
	category = "Combat",
	text = "any party member is in combat with {npcUnit} NPC.",
	test = function(checkTable) 
		if select(2,UnitDetailedThreatSituation("player",tostring(checkTable.npcUnit))) then
			return true
		end
		for i = 1,5 do
			if select(2,UnitDetailedThreatSituation("party"..i,tostring(checkTable.npcUnit))) then
				return true
			end
		end
	end,
	arguments = {
		npcUnit = {label = "NPC", type = "unit", default = "target", possibleValues = WhatsNext.valueLists.unitAny},
	}
})

WhatsNext:AddConditionalStatement("NPC In Combat Raid",{
	category = "Combat",
	text = "any raid member is in combat with {npcUnit} NPC.",
	test = function(checkTable) 
		if select(2,UnitDetailedThreatSituation("player",tostring(checkTable.npcUnit))) then
			return true
		end
		for i = 1,40 do
			if select(2,UnitDetailedThreatSituation("raid"..i,tostring(checkTable.npcUnit))) then
				return true
			end
		end
	end,
	arguments = {
		npcUnit = {label = "NPC", type = "unit", default = "target", possibleValues = WhatsNext.valueLists.unitAny},
	}
})

WhatsNext:AddConditionalStatement("Maximum Enemy Count",{
	category = "Combat",
	text = "player has taken or dealt damage to {amount} or fewer enemies in the last {seconds} seconds.",
	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,
	arguments = {
		amount = {label = "Enemy Count", type = "integer", default = 2, min = 1, max = 30},
		seconds = {label = "Seconds", type = "integer", default = 3, min = 1, max = 60},
	}
})

WhatsNext:AddConditionalStatement("Minimum Enemy Count",{
	category = "Combat",
	text = "player has taken or dealt damage to {amount} or more enemies in the last {seconds} seconds.",
	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,
	arguments = {
		amount = {label = "Enemy Count", type = "integer", default = 2, min = 1, max = 30},
		seconds = {label = "Seconds", type = "integer", default = 3, min = 1, max = 60},
	}
})

WhatsNext:AddConditionalStatement("Maximum Player Enemy Count",{
	category = "Combat",
	text = "player has taken or dealt damage to {amount} or fewer enemy players in the last {seconds} seconds.",
	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,
	arguments = {
		amount = {label = "Enemy Count", type = "integer", default = 2, min = 1, max = 30},
		seconds = {label = "Seconds", type = "integer", default = 3, min = 1, max = 60},
	}
})

WhatsNext:AddConditionalStatement("Minimum Player Enemy Count",{
	category = "Combat",
	text = "player has taken or dealt damage to {amount} or more enemy players in the last {seconds} seconds.",
	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,
	arguments = {
		amount = {label = "Enemy Count", type = "integer", default = 2, min = 1, max = 30},
		seconds = {label = "Seconds", type = "integer", default = 3, min = 1, max = 60},
	}
})

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

WhatsNext:AddConditionalStatement("Targeted By Hostile", {
	category = "Tanking",
	text = "{unit} is targeted by at least 1 enemy.",
	test = function(checkTable) 
		for _,unit2 in pairs(WhatsNext.allUnits) do
			if UnitIsEnemy("player",unit2) and not UnitIsEnemy("player",checkTable.unit) and UnitIsUnit(unit2.."target",checkTable.unit) then
				return true
			end
		end
		return false
	end,
	arguments = {
		unit = {label = "unit", type = "unit", default = "target", possibleValues = WhatsNext.valueLists.unitAny},
	}
})

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

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

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

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

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

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

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