--[[
Name: Parser-3.0
Revision: $Rev: 30308 $
Author: Cameron Kenneth Knight (ckknight@gmail.com),
        Mikord (of MSBT)
Website: http://wiki.wowace.com/index.php/Parser-3.0
Documentation: http://wiki.wowace.com/index.php/Parser-3.0
SVN: svn://svn.wowace.com/wowace/trunk/Parser-3.0/Parser-3.0
Description: Combat Parser library
Dependencies: AceLibrary
License: LGPL v2.1
]]

local MAJOR_VERSION = "Parser-3.0"
local MAJOR_LOCALE_VERSION = "Parser-Locale-3.0"
local MINOR_VERSION = "$Revision: 30308 $"

-- This ensures the code is only executed if the libary doesn't already exist, or is a newer version
if not AceLibrary then error(MAJOR_VERSION .. " requires AceLibrary.") end
if not AceLibrary:IsNewVersion(MAJOR_VERSION, MINOR_VERSION) then return end

if not AceLibrary:HasInstance("AceOO-2.0") then error(MAJOR_VERSION .. " requires AceOO-2.0.") end

local hasPet = false
do
	local _,class = UnitClass("player")
	if class == "WARLOCK" or class == "HUNTER" or class == "PRIEST" then
		hasPet = true
	end
end

local EVENTTYPE_DAMAGE = "Damage"
local EVENTTYPE_NOTIFICATION = "Notification"
local EVENTTYPE_HEAL = "Heal"
local EVENTTYPE_ENVIRONMENTAL = "Environmental"
local EVENTTYPE_MISS = "Miss"
local DAMAGETYPE_PHYSICAL = "Physical"
local DAMAGETYPE_HOLY = "Holy"
local DAMAGETYPE_FIRE = "Fire"
local DAMAGETYPE_NATURE = "Nature"
local DAMAGETYPE_FROST = "Frost"
local DAMAGETYPE_SHADOW = "Shadow"
local DAMAGETYPE_ARCANE = "Arcane"
local DAMAGETYPE_UNKNOWN = "Unknown"
local NOTIFICATIONTYPE_HONOR_GAIN = "Honor gain"
local NOTIFICATIONTYPE_EXPERIENCE_GAIN = "XP gain"
local NOTIFICATIONTYPE_REP_LOSS = "Rep loss"
local NOTIFICATIONTYPE_REP_GAIN = "Rep gain"
local NOTIFICATIONTYPE_POWER_LOSS = "Power loss"
local NOTIFICATIONTYPE_POWER_GAIN = "Power gain"
local NOTIFICATIONTYPE_SKILL_GAIN = "Skill gain"
local NOTIFICATIONTYPE_EXTRA_ATTACK = "Extra attack"
local NOTIFICATIONTYPE_KILLING_BLOW = "Killing blow"
local NOTIFICATIONTYPE_SOUL_SHARD_CREATED = "Soul Shard created"
local MISSTYPE_MISS = "Miss"
local MISSTYPE_PARRY = "Parry"
local MISSTYPE_DODGE = "Dodge"
local MISSTYPE_REFLECT = "Reflect"
local MISSTYPE_RESIST = "Resist"
local MISSTYPE_ABSORB = "Absorb"
local MISSTYPE_BLOCK = "Block"
local MISSTYPE_EVADE = "Evade"
local MISSTYPE_IMMUNE = "Immune"
local HAZARDTYPE_DROWNING = "Drowning"
local HAZARDTYPE_FALLING = "Falling"
local HAZARDTYPE_FATIGUE = "Fatigue"
local HAZARDTYPE_FIRE = "Fire"
local HAZARDTYPE_LAVA = "Lava"
local HAZARDTYPE_SLIME = "Slime"

local L
local function ConfigureAceLocale22()
	ConfigureAceLocale22 = nil
	L = AceLibrary("AceLocale-2.2"):new("Parser-3.0")
	L:RegisterTranslations("enUS", function() return {
		[EVENTTYPE_DAMAGE] = DAMAGE,
		[EVENTTYPE_NOTIFICATION] = "Notification",
		[EVENTTYPE_HEAL] = "Heal",
		[EVENTTYPE_ENVIRONMENTAL] = "Environmental",
		[EVENTTYPE_MISS] = MISS,
		[DAMAGETYPE_PHYSICAL] = SPELL_SCHOOL0_CAP,
		[DAMAGETYPE_HOLY] = SPELL_SCHOOL1_CAP,
		[DAMAGETYPE_FIRE] = SPELL_SCHOOL2_CAP,
		[DAMAGETYPE_NATURE] = SPELL_SCHOOL3_CAP,
		[DAMAGETYPE_FROST] = SPELL_SCHOOL4_CAP,
		[DAMAGETYPE_SHADOW] = SPELL_SCHOOL5_CAP,
		[DAMAGETYPE_ARCANE] = SPELL_SCHOOL6_CAP,
		[DAMAGETYPE_UNKNOWN] = UNKNOWN,
		[NOTIFICATIONTYPE_HONOR_GAIN] = CHAT_MSG_COMBAT_HONOR_GAIN,
		[NOTIFICATIONTYPE_EXPERIENCE_GAIN] = CHAT_MSG_COMBAT_XP_GAIN,
		[NOTIFICATIONTYPE_REP_LOSS] = "Rep Loss",
		[NOTIFICATIONTYPE_REP_GAIN] = "Rep Gain",
		[NOTIFICATIONTYPE_POWER_LOSS] = "Power Loss",
		[NOTIFICATIONTYPE_POWER_GAIN] = "Power Gain",
		[NOTIFICATIONTYPE_SKILL_GAIN] = "Skill Gain",
		[NOTIFICATIONTYPE_EXTRA_ATTACK] = "Extra attack",
		[NOTIFICATIONTYPE_KILLING_BLOW] = "Killing blow",
		[NOTIFICATIONTYPE_SOUL_SHARD_CREATED] = "Soul Shard created",
		[MISSTYPE_MISS] = MISS,
		[MISSTYPE_PARRY] = PARRY,
		[MISSTYPE_DODGE] = DODGE,
		[MISSTYPE_REFLECT] = REFLECT,
		[MISSTYPE_RESIST] = RESIST,
		[MISSTYPE_ABSORB] = ABSORB,
		[MISSTYPE_BLOCK] = BLOCK,
		[MISSTYPE_EVADE] = EVADE,
		[MISSTYPE_IMMUNE] = IMMUNE,
		[HAZARDTYPE_DROWNING] = "Drowning",
		[HAZARDTYPE_DROWNING] = "Falling",
		[HAZARDTYPE_DROWNING] = "Fatigue",
		[HAZARDTYPE_DROWNING] = "Fire",
		[HAZARDTYPE_DROWNING] = "Lava",
		[HAZARDTYPE_DROWNING] = "Slime",
	} end)

	L:RegisterTranslations("koKR", function() return {
--		[EVENTTYPE_DAMAGE] = DAMAGE,
		[EVENTTYPE_NOTIFICATION] = "알림",
		[EVENTTYPE_HEAL] = "치유",
		[EVENTTYPE_ENVIRONMENTAL] = "환경",
--		[EVENTTYPE_MISS] = MISS,
--		[DAMAGETYPE_PHYSICAL] = SPELL_SCHOOL0_CAP,
--		[DAMAGETYPE_HOLY] = SPELL_SCHOOL1_CAP,
--		[DAMAGETYPE_FIRE] = SPELL_SCHOOL2_CAP,
--		[DAMAGETYPE_NATURE] = SPELL_SCHOOL3_CAP,
--		[DAMAGETYPE_FROST] = SPELL_SCHOOL4_CAP,
--		[DAMAGETYPE_SHADOW] = SPELL_SCHOOL5_CAP,
--		[DAMAGETYPE_ARCANE] = SPELL_SCHOOL6_CAP,
--		[DAMAGETYPE_UNKNOWN] = UNKNOWN,
--		[NOTIFICATIONTYPE_HONOR_GAIN] = CHAT_MSG_COMBAT_HONOR_GAIN,
--		[NOTIFICATIONTYPE_EXPERIENCE_GAIN] = CHAT_MSG_COMBAT_XP_GAIN,
		[NOTIFICATIONTYPE_REP_LOSS] = "평판 감소",
		[NOTIFICATIONTYPE_REP_GAIN] = "평판 획득",
		[NOTIFICATIONTYPE_POWER_LOSS] = "파워 감소",
		[NOTIFICATIONTYPE_POWER_GAIN] = "파워 획득",
		[NOTIFICATIONTYPE_SKILL_GAIN] = "기술 획득",
		[NOTIFICATIONTYPE_EXTRA_ATTACK] = "추가 공격",
		[NOTIFICATIONTYPE_KILLING_BLOW] = "강타",
		[NOTIFICATIONTYPE_SOUL_SHARD_CREATED] = "영혼의 조각 창조",
--		[MISSTYPE_MISS] = MISS,
--		[MISSTYPE_PARRY] = PARRY,
--		[MISSTYPE_DODGE] = DODGE,
--		[MISSTYPE_REFLECT] = REFLECT,
--		[MISSTYPE_RESIST] = RESIST,
--		[MISSTYPE_ABSORB] = ABSORB,
--		[MISSTYPE_BLOCK] = BLOCK,
--		[MISSTYPE_EVADE] = EVADE,
--		[MISSTYPE_IMMUNE] = IMMUNE,
		[HAZARDTYPE_DROWNING] = "호흡",
		[HAZARDTYPE_DROWNING] = "낙하",
		[HAZARDTYPE_DROWNING] = "피로",
		[HAZARDTYPE_DROWNING] = "화염",
		[HAZARDTYPE_DROWNING] = "용암",
		[HAZARDTYPE_DROWNING] = "독성",
	} end)
	-- keep this at the end
	local freeze = L[EVENTTYPE_DAMAGE]; freeze = nil
	
	AceLibrary:Register(L, MAJOR_LOCALE_VERSION, MINOR_VERSION)
end

local Parser = AceLibrary("AceOO-2.0").Mixin { "RegisterParserEvent", "UnregisterParserEvent", "UnregisterAllParserEvents", "IsParserEventRegistered" }

local playerName = UnitName("player")
local eventSearchMap = {}
local globalStringInfo = {}
local messages = {}
local toUnitID = {}
local recentPvP = {}
local string_find = string.find
local filterFuncs = setmetatable({}, {__index = function(self, filter)
	local t = {}
	for k,v in pairs(filter) do
		if k ~= 'eventType' then
			local k,v = k,v
			local compare = '=='
			if k:find("_lt$") then
				compare = '<'
				k = k:sub(1, -4)
			elseif k:find("_le$") then
				compare = '<='
				k = k:sub(1, -4)
			elseif k:find("_gt$") then
				compare = '>'
				k = k:sub(1, -4)
			elseif k:find("_ge$") then
				compare = '>='
				k = k:sub(1, -4)
			elseif k:find("_ne$") then
				compare = '~='
				k = k:sub(1, -4)
			elseif k:find("_start$") then
				compare = 'start'
				k = k:sub(1, -7)
			elseif k:find("_end$") then
				compare = 'end'
				k = k:sub(1, -5)
			elseif k:find("_match$") then
				compare = 'match'
				k = k:sub(1, -7)
			elseif k:find("_in$") then
				compare = 'in'
				k = k:sub(1, -4)
			elseif k:find("_notin$") then
				compare = 'notin'
				k = k:sub(1, -4)
			end
			if compare == "~=" then
				t[#t+1] = function(t)
					return t[k] ~= v
				end
			elseif compare == "==" then
				t[#t+1] = function(t)
					return t[k] == v
				end
			elseif compare == "<" then
				t[#t+1] = function(t)
					return t[k] < v
				end
			elseif compare == "<=" then
				t[#t+1] = function(t)
					return t[k] <= v
				end
			elseif compare == ">" then
				t[#t+1] = function(t)
					return t[k] > v
				end
			elseif compare == ">=" then
				t[#t+1] = function(t)
					return t[k] >= v
				end
			elseif compare == "start" then
				v = "^" .. v:gsub("([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1")
				t[#t+1] = function(t)
					return v and string_find(t[k], v)
				end
			elseif compare == "end" then
				v = v:gsub("([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1") .. "$"
				t[#t+1] = function(t)
					return v and string_find(t[k], v)
				end
			elseif compare == "match" then
				t[#t+1] = function(t)
					return v and string_find(t[k], v)
				end
			elseif compare == "in" then
				t[#t+1] = function(t)
					return v[t[k]]
				end
			elseif compare == "notin" then
				t[#t+1] = function(t)
					return not v[t[k]]
				end
			end
		end
	end
	local n = #t
	local func = function(parserEvent)
		local i = 1
		while i <= n do
			if not t[i](parserEvent) then
				return false
			end
			i = i + 1
		end
		return true
	end
	self[filter] = func
	return func
end, __mode = 'k' })
local registry
local ownerRegistry

local parserEvent = {}

local _G = getfenv(0)
local table_insert = table.insert
local table_remove = table.remove
local table_sort = table.sort
local select = select
local tonumber = tonumber
local GetTime = GetTime
local pcall = pcall
local pairs = pairs
local ipairs = ipairs
local next = next
local GetNumPartyMembers = GetNumPartyMembers
local GetNumRaidMembers = GetNumRaidMembers
local UnitName = UnitName
local UnitExists = UnitExists
local UnitIsPlayer = UnitIsPlayer
local UnitIsFriend = UnitIsFriend

local playerName = UnitName('player')

function Parser:GetAceLocale22Instance()
	if ConfigureAceLocale22 then
		AceLibrary:HasInstance("AceLocale-2.2")
		if ConfigureAceLocale22 then
			Parser:error("Must have AceLocale-2.2 loaded to access `GetAceLocale22Instance'.")
		end
	end
	return L
end

local function GlobalStringCompareFunc(alpha, bravo)
	-- Get the global string for the passed names.
	alpha = _G[alpha]
	bravo = _G[bravo]

	local alpha_stripped = alpha:gsub("%%[sd]", ""):gsub("%%%d%$[sd]", "")
	local bravo_stripped = bravo:gsub("%%[sd]", ""):gsub("%%%d%$[sd]", "")

	-- Check if the stripped global strings are the same length.
	if alpha_stripped:len() == bravo_stripped:len() then
		-- Count the number of captures in each string.
		local alpha_num = 0
		for _ in alpha:gmatch("%%%d%$[sd]") do
			alpha_num = alpha_num + 1
		end
		if alpha_num == 0 then
			for _ in alpha:gmatch("%%[sd]") do
				alpha_num = alpha_num + 1
			end
		end

		local bravo_num = 0
		for _ in bravo:gmatch("%%%d%$[sd]") do
			bravo_num = bravo_num + 1
		end
		if bravo_num == 0 then
			for _ in bravo:gmatch("%%[sd]") do
				bravo_num = bravo_num + 1
			end
		end
		
		-- Return the global string with the least captures.
		return alpha_num < bravo_num
	else
		-- Return the longest global string.
		return alpha_stripped:len() > bravo_stripped:len()
	end
end

local function CreateEventSearchMap()
	-- Incoming Melee Hits/Crits
	local incomingMeleeHits = {
		-- Physical Crit/Hit, Elemental Crit/Hit
		"COMBATHITCRITOTHERSELF", "COMBATHITOTHERSELF", "COMBATHITCRITSCHOOLOTHERSELF", "COMBATHITSCHOOLOTHERSELF"
	}
	if hasPet then
		-- Pet gets hit
		incomingMeleeHits[#incomingMeleeHits+1] = "COMBATHITCRITOTHEROTHER"
		incomingMeleeHits[#incomingMeleeHits+1] = "COMBATHITOTHEROTHER"
		incomingMeleeHits[#incomingMeleeHits+1] = "COMBATHITCRITSCHOOLOTHEROTHER"
		incomingMeleeHits[#incomingMeleeHits+1] = "COMBATHITSCHOOLOTHEROTHER"
	end
	eventSearchMap["CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS"] = incomingMeleeHits
	eventSearchMap["CHAT_MSG_COMBAT_HOSTILEPLAYER_HITS"] = incomingMeleeHits

	-- Incoming Melee Misses, Dodges, Parries, Blocks, Absorbs, Immunes
	local incomingMeleeMisses = {
		-- Miss, Dodge, Parry, Block, Absorb, Immune
		"MISSEDOTHERSELF", "VSDODGEOTHERSELF", "VSPARRYOTHERSELF", "VSBLOCKOTHERSELF", "VSABSORBOTHERSELF",
		"VSIMMUNEOTHERSELF"
	}
	eventSearchMap["CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES"] = incomingMeleeMisses
	eventSearchMap["CHAT_MSG_COMBAT_HOSTILEPLAYER_MISSES"] = incomingMeleeMisses
	eventSearchMap["CHAT_MSG_COMBAT_PARTY_MISSES"] = incomingMeleeMisses


	-- Incoming Spell/Ability Damage, Misses, Dodges, Parries, Blocks, Absorbs, Resists, Immunes, Reflects, Power Losses
	local incomingSpellHitsAndMisses = {
		-- Physical Crit/Hit, Ability Crit/Hit, Spell Crit/Hit, Miss, Dodge, Parry, Block, Spell Resist, Absorb, Immune, Reflect, Leech, Drain
		"COMBATHITCRITOTHERSELF", "COMBATHITOTHERSELF", "SPELLLOGCRITOTHERSELF", "SPELLLOGOTHERSELF",
		"SPELLLOGCRITSCHOOLOTHERSELF", "SPELLLOGSCHOOLOTHERSELF", "SPELLMISSOTHERSELF", "SPELLDODGEDOTHERSELF",
		"SPELLDODGEDOTHERSELF", "SPELLPARRIEDOTHERSELF", "SPELLBLOCKEDOTHERSELF", "SPELLRESISTOTHERSELF",
		"SPELLLOGABSORBOTHERSELF", "SPELLIMMUNEOTHERSELF", "SPELLREFLECTOTHERSELF", "SPELLPOWERLEECHOTHERSELF",
		"SPELLPOWERDRAINOTHERSELF"
	}
	eventSearchMap["CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE"] = incomingSpellHitsAndMisses
	eventSearchMap["CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE"] = incomingSpellHitsAndMisses

	-- Incoming damage from shields
	eventSearchMap["CHAT_MSG_SPELL_DAMAGESHIELDS_ON_OTHERS"] = {
		"DAMAGESHIELDOTHEROTHER","DAMAGESHIELDOTHERSELF","SPELLRESISTOTHEROTHER","SPELLRESISTOTHERSELF"
	}

	-- Incoming Heals, Power Gains
	local incomingHealsAndPowerGains = {
		-- Crit Heal from others, Heal from others, HoT from others, HoT from self, Power Gain from other,
		-- Power Gain from self, Power Leech
		"HEALEDCRITOTHERSELF", "HEALEDOTHERSELF", "PERIODICAURAHEALOTHERSELF", "POWERGAINOTHERSELF",
		"SPELLPOWERLEECHSELFOTHER"
	}
	eventSearchMap["CHAT_MSG_SPELL_HOSTILEPLAYER_BUFF"] = incomingHealsAndPowerGains
	eventSearchMap["CHAT_MSG_SPELL_CREATURE_VS_SELF_BUFF"] = incomingHealsAndPowerGains


	-- Incoming DoTs, Power Gains
	eventSearchMap["CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE"] = {
		-- DoT, Absorbed DoT, Power Leech
		"PERIODICAURADAMAGEOTHERSELF", "SPELLLOGABSORBOTHERSELF", "SPELLPOWERLEECHSELFOTHER"
	}


	-- Incoming HoTs, Power Gains
	eventSearchMap["CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS"] = {
		-- Crit Heal from others, Heal from others, HoT from others, HoT from self, Power Gain from self, HoT to others.
		"HEALEDCRITOTHERSELF", "HEALEDOTHERSELF", "PERIODICAURAHEALOTHERSELF", "PERIODICAURAHEALSELFSELF", 
		"POWERGAINSELFSELF", "PERIODICAURAHEALSELFOTHER"
	}

	-- Outgoing Melee Hits/Crits, Environmental Damage
	eventSearchMap["CHAT_MSG_COMBAT_SELF_HITS"] = {
		-- Physical Hit/Crit, Environmental.
		"COMBATHITSELFOTHER", "COMBATHITCRITSELFOTHER", "SPELLLOGCRITSELFOTHER", "SPELLLOGSELFOTHER",
		"SPELLLOGCRITSCHOOLSELFSELF", "SPELLLOGSCHOOLSELFSELF", "SPELLLOGCRITSCHOOLSELFOTHER", "SPELLLOGSCHOOLSELFOTHER",
		"VSENVIRONMENTALDAMAGE_DROWNING_SELF", "VSENVIRONMENTALDAMAGE_FALLING_SELF", "VSENVIRONMENTALDAMAGE_FATIGUE_SELF",
		"VSENVIRONMENTALDAMAGE_FIRE_SELF", "VSENVIRONMENTALDAMAGE_LAVA_SELF", "VSENVIRONMENTALDAMAGE_SLIME_SELF"
	}


	-- Outgoing Melee Misses, Dodges, Parries, Blocks, Absorbs, Immunes, Evades
	eventSearchMap["CHAT_MSG_COMBAT_SELF_MISSES"] = {
		-- Miss, Dodge, Parry, Block, Absorb, Immune, Evade.
		"MISSEDSELFOTHER", "VSDODGESELFOTHER", "VSPARRYSELFOTHER", "VSBLOCKSELFOTHER",
		"VSABSORBSELFOTHER", "VSIMMUNESELFOTHER", "VSEVADESELFOTHER"
	}


	-- Outgoing Spell/Ability Damage, Misses, Dodges, Parries, Blocks, Absorbs, Resists, Immunes, Evades, Extra Attacks
	eventSearchMap["CHAT_MSG_SPELL_SELF_DAMAGE"] = {
		-- Ability Crit/Hit, Self Spell Crit/Hit, Spell Crit/Hit, Miss, Dodge, Parry, Block, Spell Resist, Absorb, Immune,
		-- Reflect, Evade, Extra Attack(s), 
		"SPELLLOGCRITSELFOTHER", "SPELLLOGSELFOTHER", "SPELLLOGCRITSCHOOLSELFSELF", "SPELLLOGSCHOOLSELFSELF",
		"SPELLLOGCRITSCHOOLSELFOTHER", "SPELLLOGSCHOOLSELFOTHER", "SPELLMISSSELFOTHER", "SPELLDODGEDSELFOTHER",
		"SPELLPARRIEDSELFOTHER", "SPELLBLOCKEDSELFOTHER", "SPELLRESISTSELFOTHER", "SPELLLOGABSORBSELFOTHER",
		"SPELLIMMUNESELFOTHER", "SPELLREFLECTSELFOTHER", "SPELLEVADEDSELFOTHER", "SPELLEXTRAATTACKSSELF",
		"SPELLEXTRAATTACKSSELF_SINGULAR"
	}


	-- Outgoing damage from shields
	eventSearchMap["CHAT_MSG_SPELL_DAMAGESHIELDS_ON_SELF"] = {
		"DAMAGESHIELDSELFOTHER"
	}


	-- Outgoing Heals, Power Gains, Dispel/Purge Resists, Extra Attacks
	eventSearchMap["CHAT_MSG_SPELL_SELF_BUFF"] = {
		-- (Crit) Heal to self, (Crit) Heal to others, Power Gain from self, Power Leech, Dispel/Purge Resist, Extra Attack(s)
		"HEALEDCRITSELFSELF", "HEALEDSELFSELF", "HEALEDCRITSELFOTHER", "HEALEDSELFOTHER",
		"POWERGAINSELFSELF", "SPELLPOWERLEECHSELFOTHER", "SPELLRESISTSELFOTHER", "SPELLEXTRAATTACKSSELF",
		"SPELLEXTRAATTACKSSELF_SINGULAR"
	}


	-- Outgoing HoTs.
	local outgoingHoTs = {
		-- HoT to others.
		"PERIODICAURAHEALSELFOTHER"
	}
	eventSearchMap["CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS"] = outgoingHoTs
	eventSearchMap["CHAT_MSG_SPELL_PERIODIC_PARTY_BUFFS"] = outgoingHoTs


	-- Outgoing DoTs, Power Losses
	local outgoingDoTsAndPowerLosses = {
		-- DoT to others, Absorbed DoT, Leech, Drain
		"PERIODICAURADAMAGESELFOTHER", "SPELLLOGABSORBSELFOTHER", "SPELLPOWERLEECHOTHERSELF", "SPELLPOWERDRAINOTHERSELF","PERIODICAURADAMAGEOTHEROTHER"
	}
	eventSearchMap["CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE"] = outgoingDoTsAndPowerLosses
	eventSearchMap["CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE"] = outgoingDoTsAndPowerLosses



	-- Outgoing Pet Melee Hits/Crits
	eventSearchMap["CHAT_MSG_COMBAT_PET_HITS"] = {
		-- Physical Crit/Hit
		"COMBATHITCRITOTHEROTHER", "COMBATHITOTHEROTHER"
	}


	-- Outgoing Pet Melee Misses
	eventSearchMap["CHAT_MSG_COMBAT_PET_MISSES"] = {
		-- Miss, Dodge, Parry, Block, Absorb, Immune, Evade.
		"MISSEDOTHEROTHER", "VSDODGEOTHEROTHER", "VSPARRYOTHEROTHER", "VSBLOCKOTHEROTHER",
		"VSABSORBOTHEROTHER", "VSIMMUNEOTHEROTHER", "VSEVADEOTHEROTHER"
	}

	-- Outgoing Pet Spell/Ability Damage, Misses, Dodges, Parries, Blocks, Absorbs, Resists, Immunes, Evades
	eventSearchMap["CHAT_MSG_SPELL_PET_DAMAGE"] = {
		-- Ability Crit/Hit, Spell Crit/Hit, Miss, Dodge, Parry, Block, Spell Resist, Absorb, Immune, Evade 
		"SPELLLOGCRITOTHEROTHER", "SPELLLOGOTHEROTHER", "SPELLLOGCRITSCHOOLOTHEROTHER", "SPELLLOGSCHOOLOTHEROTHER",
		"SPELLMISSOTHEROTHER", "SPELLDODGEDOTHEROTHER", "SPELLPARRIEDOTHEROTHER", "SPELLBLOCKEDOTHEROTHER",
		"SPELLRESISTOTHEROTHER", "SPELLLOGABSORBOTHEROTHER", "SPELLIMMUNEOTHEROTHER", "SPELLEVADEDOTHEROTHER"
	}
	
	-- Outgoing Party/Raid Melee Hits/Crits, Environmental Damage
	eventSearchMap["CHAT_MSG_COMBAT_PARTY_HITS"] = {
		"COMBATHITOTHEROTHER","COMBATHITCRITOTHEROTHER","COMBATHITCRITSCHOOLOTHEROTHER","COMBATHITOTHEROTHER",
		"COMBATHITOTHERSELF","COMBATHITSCHOOLOTHEROTHER","SPELLLOGCRITOTHEROTHER","SPELLLOGCRITSCHOOLOTHEROTHER",
		"SPELLLOGOTHEROTHER","SPELLLOGSCHOOLOTHEROTHER","VSENVIRONMENTALDAMAGE_FIRE_OTHER","VSENVIRONMENTALDAMAGE_LAVA_OTHER"
	}
	
	-- Outgoing Party/Raid Spell/Ability Damage
	eventSearchMap["CHAT_MSG_SPELL_PARTY_DAMAGE"] = {		
		"SPELLLOGCRITOTHEROTHER", "SPELLLOGOTHEROTHER", "SPELLLOGCRITSCHOOLOTHEROTHER", "SPELLLOGSCHOOLOTHEROTHER",
		"SPELLMISSOTHEROTHER", "SPELLDODGEDOTHEROTHER", "SPELLPARRIEDOTHEROTHER", "SPELLBLOCKEDOTHEROTHER",
		"SPELLRESISTOTHEROTHER", "SPELLLOGABSORBOTHEROTHER", "SPELLIMMUNEOTHEROTHER", "SPELLEVADEDOTHEROTHER"
	}
	
	-- Honor Gains
	eventSearchMap["CHAT_MSG_COMBAT_HONOR_GAIN"] = {
		"COMBATLOG_HONORGAIN", "COMBATLOG_HONORAWARD"
	}


	-- Reputation Gains/Losses
	eventSearchMap["CHAT_MSG_COMBAT_FACTION_CHANGE"] = {
		-- Rep Gain, Rep Loss
		"FACTION_STANDING_INCREASED", "FACTION_STANDING_DECREASED"
	}


	-- Skill Gains
	eventSearchMap["CHAT_MSG_SKILL"] = {
		"SKILL_RANK_UP"
	}


	-- Experience Gains
	eventSearchMap["CHAT_MSG_COMBAT_XP_GAIN"] = {
		"COMBATLOG_XPGAIN_FIRSTPERSON", "COMBATLOG_XPGAIN_FIRSTPERSON_UNNAMED"
	}


	-- Killing Blows
	eventSearchMap["CHAT_MSG_COMBAT_HOSTILE_DEATH"] = {
		"SELFKILLOTHER"
	}


	-- Created Items (Soul Shards)
	eventSearchMap["CHAT_MSG_LOOT"] = {
		"LOOT_ITEM_CREATED_SELF", "LOOT_ITEM_PUSHED_SELF"
	}


	-- Loop through each of the events.
	for event, t in pairs(eventSearchMap) do
		-- Remove invalid global strings.
		local num = #t
		for i = num, 1, -1 do
			if not _G[t[i]] then
				table_remove(t, i)
			end
		end

		-- Sort the global strings from most to least specific.
		table_sort(t, GlobalStringCompareFunc)
	end
end

local damageTypeNumber = {
	[SPELL_SCHOOL0_CAP] = DAMAGETYPE_PHYSICAL,
	[SPELL_SCHOOL1_CAP] = DAMAGETYPE_HOLY,
	[SPELL_SCHOOL2_CAP] = DAMAGETYPE_FIRE,
	[SPELL_SCHOOL3_CAP] = DAMAGETYPE_NATURE,
	[SPELL_SCHOOL4_CAP] = DAMAGETYPE_FROST,
	[SPELL_SCHOOL5_CAP] = DAMAGETYPE_SHADOW,
	[SPELL_SCHOOL6_CAP] = DAMAGETYPE_ARCANE,
	[DAMAGETYPE_PHYSICAL] = DAMAGETYPE_PHYSICAL,
	[DAMAGETYPE_HOLY] = DAMAGETYPE_HOLY,
	[DAMAGETYPE_FIRE] = DAMAGETYPE_FIRE,
	[DAMAGETYPE_NATURE] = DAMAGETYPE_NATURE,
	[DAMAGETYPE_FROST] = DAMAGETYPE_FROST,
	[DAMAGETYPE_SHADOW] = DAMAGETYPE_SHADOW,
	[DAMAGETYPE_ARCANE] = DAMAGETYPE_ARCANE,
}

local function PopulateDamageEvent(captureMap, capturedData, msg)
	-- Attempt to get the damage type and convert it to a number if there is one.
	local damageType = capturedData[captureMap[1]] or captureMap[1]

	parserEvent.eventType = EVENTTYPE_DAMAGE
	parserEvent.damageType = damageTypeNumber[damageType] or DAMAGETYPE_UNKNOWN
	local sourceName = capturedData[captureMap[2]] or captureMap[2]
	if sourceName == "player" then
		parserEvent.sourceName = playerName
		parserEvent.sourceID = "player"
		parserEvent.sourcePvP = false
	else
		parserEvent.sourceName = sourceName
		parserEvent.sourceID = toUnitID[sourceName] or false
		parserEvent.sourcePvP = not not recentPvP[sourceName]
	end
	local recipientName = capturedData[captureMap[3]] or captureMap[3]
	if recipientName == "player" then
		parserEvent.recipientName = playerName
		parserEvent.recipientID = "player"
		parserEvent.recipientPvP = false
	else
		parserEvent.recipientName = recipientName
		parserEvent.recipientID = toUnitID[recipientName] or false
		parserEvent.recipientPvP = not not recentPvP[recipientName]
	end
	parserEvent.amount = tonumber(capturedData[captureMap[4]] or captureMap[4]) or 0
	parserEvent.abilityName = capturedData[captureMap[5]] or captureMap[5] or false
	parserEvent.isCrit = captureMap[6] or false
	parserEvent.isDoT = captureMap[7] or false
end

local function ValidityCheckDamageEvent(captureMap, filter)
	local damageType = captureMap[1]
	if type(damageType) == "string" then
		damageType = damageTypeNumber[damageType] or DAMAGETYPE_UNKNOWN
	end
	damageType = damageType or false
	local sourceName = captureMap[2] or false
	local recipientName = captureMap[3] or false
	local abilityName = captureMap[5] or false
	local isCrit = captureMap[6] or false
	local isDoT = captureMap[7] or false
	
	if filter.damageType ~= nil and damageType ~= filter.damageType and type(damageType) == "string" then
		return false
	end
	if filter.damageType_ne ~= nil and damageType == filter.damageType_ne and type(damageType) == "string" then
		return false
	end

	if filter.sourceID ~= nil then
		if filter.sourceID == "player" then
			if sourceName ~= "player" then
				return false
			end
		else
			if sourceName == "player" then
				return false
			end
		end
	end
	if filter.sourceID_ne ~= nil and filter.sourceID_ne == "player" and sourceName == "player" then
		return false
	end

	if filter.recipientID ~= nil then
		if filter.recipientID == "player" then
			if recipientName ~= "player" then
				return false
			end
		else
			if recipientName == "player" then
				return false
			end
		end
	end
	if filter.recipientID_ne ~= nil and filter.recipientID_ne == "player" and recipientName == "player" then
		return false
	end
	
	if filter.abilityName ~= nil then
		if filter.abilityName then
			if not abilityName then
				return false
			end
		else
			if abilityName then
				return false
			end
		end
	end
	if filter.abilityName_ne and not filter.abilityName_ne and not abilityName then
		return false
	end
	
	if filter.isCrit ~= nil then
		if filter.isCrit then
			if not isCrit then
				return false
			end
		else
			if isCrit then
				return false
			end
		end
	end
	if filter.isCrit_ne ~= nil then
		if filter.isCrit_ne then
			if isCrit then
				return false
			end
		else
			if not isCrit then
				return false
			end
		end
	end
	
	if filter.isDoT ~= nil then
		if filter.isDoT then
			if not isDoT then
				return false
			end
		else
			if isDoT then
				return false
			end
		end
	end
	if filter.isDoT_ne ~= nil then
		if filter.isDoT_ne then
			if isCrit then
				return false
			end
		else
			if not isCrit then
				return false
			end
		end
	end
	
	return true
end

local function PopulateHealEvent(captureMap, capturedData)
	parserEvent.eventType = EVENTTYPE_HEAL
	local sourceName = capturedData[captureMap[1]] or captureMap[1]
	if sourceName == "player" then
		parserEvent.sourceName = playerName
		parserEvent.sourceID = "player"
	else
		parserEvent.sourceName = sourceName
		parserEvent.sourceID = toUnitID[sourceName] or false
	end
	local recipientName = capturedData[captureMap[2]] or captureMap[2]
	local recipientID
	if recipientName == "player" then
		parserEvent.recipientName = playerName
		recipientID = "player"
		parserEvent.recipientID = recipientID
	else
		parserEvent.recipientName = recipientName
		recipientID = toUnitID[recipientName] or false
		parserEvent.recipientID = recipientID
	end
	parserEvent.amount = tonumber(capturedData[captureMap[3]] or captureMap[3]) or 0
	parserEvent.abilityName = capturedData[captureMap[4]] or captureMap[4] or false
	parserEvent.isCrit = captureMap[5] or false
	parserEvent.isHoT = captureMap[6] or false

	-- Get the unit ID for the healed unit.

	-- Make sure it's a valid unit id.
	if recipientID then
		local healthMissing = UnitHealthMax(recipientID) - UnitHealth(recipientID)
		local overhealAmount = parserEvent.amount - healthMissing

		-- Check if any overhealing occured.
		if overhealAmount > 0 then
			parserEvent.overhealAmount = overhealAmount
		else
			parserEvent.overhealAmount = 0
		end
	else
		parserEvent.overhealAmount = 0
	end
end

local function ValidityCheckHealEvent(captureMap, filter)
	local sourceName = captureMap[1] or false
	local recipientName = captureMap[2] or false
	local isCrit = captureMap[5] or false
	local isHoT = captureMap[6] or false
	
	if filter.sourceID ~= nil then
		if filter.sourceID == "player" then
			if sourceName ~= "player" then
				return false
			end
		else
			if sourceName == "player" then
				return false
			end
		end
	end
	if filter.sourceID_ne ~= nil and filter.sourceID_ne == "player" and sourceName == "player" then
		return false
	end

	if filter.recipientID ~= nil then
		if filter.recipientID == "player" then
			if recipientName ~= "player" then
				return false
			end
		else
			if recipientName == "player" then
				return false
			end
		end
	end
	if filter.recipientID_ne ~= nil and filter.recipientID_ne == "player" and recipientName == "player" then
		return false
	end
	
	if filter.isCrit ~= nil then
		if filter.isCrit then
			if not isCrit then
				return false
			end
		else
			if isCrit then
				return false
			end
		end
	end
	if filter.isCrit_ne ~= nil then
		if filter.isCrit_ne then
			if isCrit then
				return false
			end
		else
			if not isCrit then
				return false
			end
		end
	end
	
	if filter.isHoT ~= nil then
		if filter.isHoT then
			if not isHoT then
				return false
			end
		else
			if isHoT then
				return false
			end
		end
	end
	if filter.isHoT_ne ~= nil then
		if filter.isHoT_ne then
			if isHoT then
				return false
			end
		else
			if not isHoT then
				return false
			end
		end
	end
	
	return true
end

local function PopulateNotificationEvent(captureMap, capturedData)
	parserEvent.eventType = EVENTTYPE_NOTIFICATION
	parserEvent.notificationType = captureMap[1] or false
	parserEvent.effectName = capturedData[captureMap[2]] or captureMap[2] or false
	parserEvent.amount = tonumber(capturedData[captureMap[3]] or captureMap[3]) or 0
	parserEvent.abilityName = capturedData[captureMap[4]] or captureMap[4] or false
	local sourceName = capturedData[captureMap[5]] or captureMap[5]
	if sourceName == "player" then
		parserEvent.sourceName = playerName
		parserEvent.sourceID = "player"
	else
		parserEvent.sourceName = sourceName
		parserEvent.sourceID = toUnitID[sourceName] or false
	end
end

local function ValidityCheckNotificationEvent(captureMap, filter)
	local notificationType = captureMap[1] or false
	if filter.notificationType ~= nil and filter.notificationType ~= notificationType then
		return false
	end
	if filter.notificationType_ne ~= nil and filter.notificationType == notificationType then
		return false
	end
	return true
end

local function PopulateEnvironmentalEvent(captureMap, capturedData)
	parserEvent.eventType = EVENTTYPE_ENVIRONMENTAL
	parserEvent.hazardType = captureMap[1] or false
	parserEvent.damageType = captureMap[2] or false
	parserEvent.amount = tonumber(capturedData[captureMap[4]] or captureMap[4]) or 0
	
	local recipientName = capturedData[captureMap[3]] or captureMap[3]
	local recipientID
	if recipientName == "player" then
		parserEvent.recipientName = playerName
		recipientID = "player"
		parserEvent.recipientID = recipientID
	else
		parserEvent.recipientName = recipientName
		recipientID = toUnitID[recipientName] or false
		parserEvent.recipientID = recipientID
	end
end

local function ValidityCheckEnvironmentalEvent(captureMap, filter)
	local hazardType = captureMap[1] or false
	local damageType = captureMap[2] or false
	if filter.hazardType ~= nil and filter.hazardType ~= hazardType then
		return false
	end
	if filter.hazardType_ne ~= nil and filter.hazardType_ne == hazardType then
		return false
	end
	if filter.damageType ~= nil and filter.damageType ~= damageType then
		return false
	end
	if filter.damageType_ne ~= nil and filter.damageType_ne == damageType then
		return false
	end
	return true
end

local function PopulateKillingBlowEvent(captureMap, capturedData)
	-- Get the name of the slain enemy.
	local enemyName = capturedData[captureMap[1]] or captureMap[1]
	
	parserEvent.eventType = EVENTTYPE_NOTIFICATION
	parserEvent.notificationType = NOTIFICATIONTYPE_KILLING_BLOW
	parserEvent.effectName = enemyName or false
	parserEvent.abilityName = false
	parserEvent.sourceID = false
	parserEvent.sourceName = false
end

local function ValidityCheckKillingBlowEvent(captureMap, filter)
	if filter.notificationType ~= nil and filter.notificationType ~= NOTIFICATIONTYPE_KILLING_BLOW then
		return false
	end
	if filter.notificationType_ne ~= nil and filter.notificationType_ne == NOTIFICATIONTYPE_KILLING_BLOW then
		return false
	end
	return true
end

local function PopulateMissEvent(captureMap, capturedData)
	parserEvent.eventType = EVENTTYPE_MISS
	parserEvent.missType = captureMap[1] or false
	local sourceName = capturedData[captureMap[2]] or captureMap[2]
	if sourceName == "player" then
		parserEvent.sourceName = playerName
		parserEvent.sourceID = "player"
		parserEvent.sourcePvP = false
	else
		parserEvent.sourceName = sourceName
		parserEvent.sourceID = toUnitID[sourceName] or false
		parserEvent.sourcePvP = not not recentPvP[sourceName]
	end
	local recipientName = capturedData[captureMap[3]] or captureMap[3]
	if recipientName == "player" then
		parserEvent.recipientName = playerName
		parserEvent.recipientID = "player"
		parserEvent.recipientPvP = false
	else
		parserEvent.recipientName = recipientName
		parserEvent.recipientID = toUnitID[recipientName] or false
		parserEvent.recipientPvP = not not recentPvP[recipientName]
	end
	parserEvent.abilityName = capturedData[captureMap[4]] or captureMap[4] or false
end

local function ValidityCheckMissEvent(captureMap, filter)
	local missType = captureMap[1] or false
	if filter.missType ~= nil and filter.missType ~= captureMap[1] then
		return false
	end
	if filter.missType_ne ~= nil and filter.missType_ne == captureMap[1] then
		return false
	end
	
	local sourceName = captureMap[2] or false
	local recipientName = captureMap[3] or false
	
	if filter.sourceID ~= nil then
		if filter.sourceID == "player" then
			if sourceName ~= "player" then
				return false
			end
		else
			if sourceName == "player" then
				return false
			end
		end
	end
	if filter.sourceID_ne ~= nil and filter.sourceID_ne == "player" and sourceName == "player" then
		return false
	end

	if filter.recipientID ~= nil then
		if filter.recipientID == "player" then
			if recipientName ~= "player" then
				return false
			end
		else
			if recipientName == "player" then
				return false
			end
		end
	end
	if filter.recipientID_ne ~= nil and filter.recipientID_ne == "player" and recipientName == "player" then
		return false
	end
	
	return true
end

local function PopulateItemCreationEvent(captureMap, capturedData)
	-- Get the created item link.
	local itemLink = capturedData[(captureOrder and captureOrder[captureMap[1]]) or captureMap[1]] or captureMap[1];

	-- Check if the item created is a Soul Shard.
	local itemID, itemName = itemLink:match("item:(%d+):[%d:]+|h%[(.+)%]");
	if tonumber(itemID) == 6265 then
		parserEvent.eventType = EVENTTYPE_NOTIFICATION
		parserEvent.notificationType = NOTIFICATIONTYPE_SOUL_SHARD_CREATED
		parserEvent.effectName = itemName
		parserEvent.abilityName = false
		parserEvent.sourceID = false
		parserEvent.sourceName = false
	end
end

local function ValidityCheckItemCreationEvent(captureMap, filter)
	if filter.notificationType ~= nil and filter.notificationType ~= NOTIFICATIONTYPE_SOUL_SHARD_CREATED then
		return false
	end
	if filter.notificationType_ne ~= nil and filter.notificationType_ne == NOTIFICATIONTYPE_SOUL_SHARD_CREATED then
		return false
	end
	return true
end

local function CreateGlobalStringCaptureMaps()
	-- Create the capture map for each supported global string.
	globalStringInfo["COMBATHITCRITOTHERSELF"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {DAMAGETYPE_PHYSICAL, 1, "player", 2, nil, true, false}}
	globalStringInfo["COMBATHITCRITSCHOOLOTHERSELF"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {3, 1, "player", 2, nil, true, false}}
	globalStringInfo["COMBATHITCRITSELFOTHER"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {DAMAGETYPE_PHYSICAL, "player", 1, 2, nil, true, false}}
	globalStringInfo["COMBATHITCRITOTHEROTHER"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {DAMAGETYPE_PHYSICAL, 1, 2, 3, nil, true, false}}
	globalStringInfo["COMBATHITOTHEROTHER"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {DAMAGETYPE_PHYSICAL, 1, 2, 3, nil, false, false}}
	globalStringInfo["COMBATHITOTHERSELF"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {DAMAGETYPE_PHYSICAL, 1, "player", 2, nil, false, false}}
	globalStringInfo["COMBATHITSCHOOLOTHERSELF"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {3, 1, "player", 2, nil, false, false}}
	globalStringInfo["COMBATHITSELFOTHER"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {DAMAGETYPE_PHYSICAL, "player", 1, 2, nil, false, false}}
	globalStringInfo["COMBATLOG_HONORAWARD"] = {eventType = EVENTTYPE_NOTIFICATION, captureMap = {NOTIFICATIONTYPE_HONOR_GAIN, nil, 1}}
	globalStringInfo["COMBATLOG_HONORGAIN"] = {eventType = EVENTTYPE_NOTIFICATION, captureMap = {NOTIFICATIONTYPE_HONOR_GAIN, nil, 3}}
	globalStringInfo["COMBATLOG_XPGAIN_FIRSTPERSON"] = {eventType = EVENTTYPE_NOTIFICATION, captureMap = {NOTIFICATIONTYPE_EXPERIENCE_GAIN, nil, 2}}
	globalStringInfo["COMBATLOG_XPGAIN_FIRSTPERSON_UNNAMED"] = {eventType = EVENTTYPE_NOTIFICATION, captureMap = {NOTIFICATIONTYPE_EXPERIENCE_GAIN, nil, 1}}
	globalStringInfo["DAMAGESHIELDOTHERSELF"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {3, 1, 4, 2, nil, false, false}}
	globalStringInfo["DAMAGESHIELDOTHEROTHER"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {3, 1, "player", 2, nil, false, false}}
	globalStringInfo["DAMAGESHIELDSELFOTHER"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {2, "player", 3, 1, nil, false, false}}
	globalStringInfo["FACTION_STANDING_DECREASED"] = {eventType = EVENTTYPE_NOTIFICATION, captureMap = {NOTIFICATIONTYPE_REP_LOSS, 1, 2}}
	globalStringInfo["FACTION_STANDING_INCREASED"] =  {eventType = EVENTTYPE_NOTIFICATION, captureMap = {NOTIFICATIONTYPE_REP_GAIN, 1, 2}}
	globalStringInfo["HEALEDCRITOTHERSELF"] = {eventType = EVENTTYPE_HEAL, captureMap = {1, "player", 3, 2, true, false}}
	globalStringInfo["HEALEDCRITSELFOTHER"] = {eventType = EVENTTYPE_HEAL, captureMap = {"player", 2, 3, 1, true, false}}
	globalStringInfo["HEALEDCRITSELFSELF"] = {eventType = EVENTTYPE_HEAL, captureMap = {"player", "player", 2, 1, true, false}}
	globalStringInfo["HEALEDOTHERSELF"] = {eventType = EVENTTYPE_HEAL, captureMap = {1, "player", 3, 2, false, false}}
	globalStringInfo["HEALEDSELFOTHER"] = {eventType = EVENTTYPE_HEAL, captureMap = {"player", 2, 3, 1, false, false}}
	globalStringInfo["HEALEDSELFSELF"] = {eventType = EVENTTYPE_HEAL, captureMap = {"player", "player", 2, 1, false, false}}
	globalStringInfo["LOOT_ITEM_CREATED_SELF"] = {eventType = EVENTTYPE_NOTIFICATION, populateEventFunc = PopulateItemCreationEvent, validFunc = ValidityCheckItemCreationEvent, captureMap = {1}}
	globalStringInfo["LOOT_ITEM_PUSHED_SELF"] = {eventType = EVENTTYPE_NOTIFICATION, populateEventFunc = PopulateItemCreationEvent, validFunc = ValidityCheckItemCreationEvent, captureMap = {1}}
	globalStringInfo["MISSEDOTHEROTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_MISS, 1, 2, nil}}
	globalStringInfo["MISSEDOTHERSELF"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_MISS, 1, "player", nil}}
	globalStringInfo["MISSEDSELFOTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_MISS, "player", 1, nil}}
	globalStringInfo["PERIODICAURADAMAGEOTHERSELF"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {2, 3, "player", 1, 4, false, true}}
	globalStringInfo["PERIODICAURADAMAGESELFOTHER"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {3, "player", 1, 2, 4, false, true}}
	globalStringInfo["PERIODICAURADAMAGEOTHEROTHER"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {3, 4, 1, 2, 5, false, true}}
	globalStringInfo["PERIODICAURAHEALOTHERSELF"] = {eventType = EVENTTYPE_HEAL, captureMap = {2, "player", 1, 3, false, true}}
	globalStringInfo["PERIODICAURAHEALSELFOTHER"] = {eventType = EVENTTYPE_HEAL, captureMap = {"player", 1, 2, 3, false, true}}
	globalStringInfo["PERIODICAURAHEALSELFSELF"] = {eventType = EVENTTYPE_HEAL, captureMap = {"player", "player", 1, 2, false, true}}
	globalStringInfo["POWERGAINOTHERSELF"] = {eventType = EVENTTYPE_NOTIFICATION, captureMap = {NOTIFICATIONTYPE_POWER_GAIN, 2, 1, 4, 3}}
	globalStringInfo["POWERGAINSELFSELF"] = {eventType = EVENTTYPE_NOTIFICATION, captureMap = {NOTIFICATIONTYPE_POWER_GAIN, 2, 1, 3, "player"}}
	globalStringInfo["SELFKILLOTHER"] = {eventType = EVENTTYPE_NOTIFICATION, populateEventFunc = PopulateKillingBlowEvent, validFunc = ValidityCheckKillingBlowEvent, captureMap = {1}}
	globalStringInfo["SKILL_RANK_UP"] = {eventType = EVENTTYPE_NOTIFICATION, captureMap = {NOTIFICATIONTYPE_SKILL_GAIN, 1, 2}}
	globalStringInfo["SPELLBLOCKEDOTHEROTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_BLOCK, 1, 3, 2}}
	globalStringInfo["SPELLBLOCKEDOTHERSELF"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_BLOCK, 1, "player", 2}}
	globalStringInfo["SPELLBLOCKEDSELFOTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_BLOCK, "player", 2, 1}}
	globalStringInfo["SPELLDODGEDOTHEROTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_DODGE, 1, 3, 2}}
	globalStringInfo["SPELLDODGEDOTHERSELF"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_DODGE, 1, "player", 2}}
	globalStringInfo["SPELLDODGEDSELFOTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_DODGE, "player", 2, 1}}
	globalStringInfo["SPELLEVADEDOTHEROTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_EVADE, 1, 3, 2}}
	globalStringInfo["SPELLEVADEDSELFOTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_EVADE, "player", 2, 1}}
	globalStringInfo["SPELLEXTRAATTACKSSELF"] = {eventType = EVENTTYPE_NOTIFICATION, captureMap = {NOTIFICATIONTYPE_EXTRA_ATTACK, 2, 1}}
	globalStringInfo["SPELLEXTRAATTACKSSELF_SINGULAR"] = {eventType = EVENTTYPE_NOTIFICATION, captureMap = {NOTIFICATIONTYPE_EXTRA_ATTACK, 2, 1}}
	globalStringInfo["SPELLIMMUNEOTHEROTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_IMMUNE, 1, 3, 2}}
	globalStringInfo["SPELLIMMUNEOTHERSELF"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_IMMUNE, 1, "player", 2}}
	globalStringInfo["SPELLIMMUNESELFOTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_IMMUNE, "player", 2, 1}}
	globalStringInfo["SPELLLOGABSORBOTHEROTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_ABSORB, 1, 3, 2}}
	globalStringInfo["SPELLLOGABSORBOTHERSELF"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_ABSORB, 1, "player", 2}}
	globalStringInfo["SPELLLOGABSORBSELFOTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_ABSORB, "player", 2, 1}}
	globalStringInfo["SPELLLOGCRITOTHEROTHER"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {DAMAGETYPE_PHYSICAL, 1, 3, 4, 2, true, false}}
	globalStringInfo["SPELLLOGCRITOTHERSELF"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {DAMAGETYPE_PHYSICAL, 1, "player", 3, 2, true, false}}
	globalStringInfo["SPELLLOGCRITSCHOOLOTHEROTHER"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {5, 1, 3, 4, 2, true, false}}
	globalStringInfo["SPELLLOGCRITSCHOOLOTHERSELF"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {4, 1, "player", 3, 2, true, false}}
	globalStringInfo["SPELLLOGCRITSCHOOLSELFOTHER"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {4, "player", 2, 3, 1, true, false}}
	globalStringInfo["SPELLLOGCRITSCHOOLSELFSELF"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {3, "player", "player", 2, 1, true, false}}
	globalStringInfo["SPELLLOGCRITSELFOTHER"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {DAMAGETYPE_PHYSICAL, "player", 2, 3, 1, true, false}}
	globalStringInfo["SPELLLOGOTHEROTHER"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {DAMAGETYPE_PHYSICAL, 1, 3, 4, 2, false, false}}
	globalStringInfo["SPELLLOGOTHERSELF"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {DAMAGETYPE_PHYSICAL, 1, "player", 3, 2, false, false}}
	globalStringInfo["SPELLLOGSCHOOLOTHEROTHER"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {5, 1, 3, 4, 2, false, false}}
	globalStringInfo["SPELLLOGSCHOOLOTHERSELF"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {4, 1, "player", 3, 2, false, false}}
	globalStringInfo["SPELLLOGSCHOOLSELFOTHER"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {4, "player", 2, 3, 1, false, false}}
	globalStringInfo["SPELLLOGSCHOOLSELFSELF"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {3, "player", "player", 2, 1, false, false}}
	globalStringInfo["SPELLLOGSELFOTHER"] = {eventType = EVENTTYPE_DAMAGE, captureMap = {DAMAGETYPE_PHYSICAL, "player", 2, 3, 1, false, false}}
	globalStringInfo["SPELLMISSOTHEROTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_MISS, 1, 3, 2}}
	globalStringInfo["SPELLMISSOTHERSELF"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_MISS, 1, "player", 2}}
	globalStringInfo["SPELLMISSSELFOTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_MISS, "player", 2, 1}}
	globalStringInfo["SPELLPARRIEDOTHEROTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_PARRY, 1, 3, 2}}
	globalStringInfo["SPELLPARRIEDOTHERSELF"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_PARRY, 1, "player", 2}}
	globalStringInfo["SPELLPARRIEDSELFOTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_PARRY, "player", 2, 1}}
	globalStringInfo["SPELLPOWERDRAINOTHERSELF"] = {eventType = EVENTTYPE_NOTIFICATION, captureMap = {NOTIFICATIONTYPE_POWER_LOSS, 4, 3}}
	globalStringInfo["SPELLPOWERLEECHOTHERSELF"] = {eventType = EVENTTYPE_NOTIFICATION, captureMap = {NOTIFICATIONTYPE_POWER_LOSS, 4, 3}}
	globalStringInfo["SPELLPOWERLEECHSELFOTHER"] = {eventType = EVENTTYPE_NOTIFICATION, captureMap = {NOTIFICATIONTYPE_POWER_GAIN, 3, 2}}
	globalStringInfo["SPELLREFLECTOTHERSELF"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_REFLECT, 1, "player", 2}}
	globalStringInfo["SPELLREFLECTSELFOTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_REFLECT, "player", 2, 1}}
	globalStringInfo["SPELLRESISTOTHEROTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_RESIST, 1, 3, 2}}
	globalStringInfo["SPELLRESISTOTHERSELF"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_RESIST, 1, "player", 2}}
	globalStringInfo["SPELLRESISTSELFOTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_RESIST, "player", 2, 1}}
	globalStringInfo["VSABSORBOTHEROTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_ABSORB, 1, 2, nil}}
	globalStringInfo["VSABSORBOTHERSELF"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_ABSORB, 1, "player", nil}}
	globalStringInfo["VSABSORBSELFOTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_ABSORB, "player", 1, nil}}
	globalStringInfo["VSBLOCKOTHEROTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_BLOCK, 1, 2, nil}}
	globalStringInfo["VSBLOCKOTHERSELF"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_BLOCK, 1, "player", nil}}
	globalStringInfo["VSBLOCKSELFOTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_BLOCK, "player", 1, nil}}
	globalStringInfo["VSDODGEOTHEROTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_DODGE, 1, 2, nil}}
	globalStringInfo["VSDODGEOTHERSELF"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_DODGE, 1, "player", nil}}
	globalStringInfo["VSDODGESELFOTHER"] ={eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_DODGE, "player", 1, nil}}
	globalStringInfo["VSENVIRONMENTALDAMAGE_DROWNING_SELF"] = {eventType = EVENTTYPE_ENVIRONMENTAL, captureMap = {HAZARDTYPE_DROWNING, DAMAGETYPE_NATURE, "player", 1}}
	globalStringInfo["VSENVIRONMENTALDAMAGE_FALLING_SELF"] = {eventType = EVENTTYPE_ENVIRONMENTAL, captureMap = {HAZARDTYPE_FALLING, DAMAGETYPE_PHYSICAL, "player", 1}}
	globalStringInfo["VSENVIRONMENTALDAMAGE_FATIGUE_SELF"] = {eventType = EVENTTYPE_ENVIRONMENTAL, captureMap = {HAZARDTYPE_FATIGUE, DAMAGETYPE_PHYSICAL, "player", 1}}
	globalStringInfo["VSENVIRONMENTALDAMAGE_FIRE_SELF"] = {eventType = EVENTTYPE_ENVIRONMENTAL, captureMap = {HAZARDTYPE_FIRE, DAMAGETYPE_FIRE, "player", 1}}
	globalStringInfo["VSENVIRONMENTALDAMAGE_LAVA_SELF"] = {eventType = EVENTTYPE_ENVIRONMENTAL, captureMap = {HAZARDTYPE_LAVA, DAMAGETYPE_FIRE, "player", 1}}
	globalStringInfo["VSENVIRONMENTALDAMAGE_SLIME_SELF"] = {eventType = EVENTTYPE_ENVIRONMENTAL, captureMap = {HAZARDTYPE_SLIME, DAMAGETYPE_NATURE, "player", 1}}
	globalStringInfo["VSENVIRONMENTALDAMAGE_FIRE_OTHER"] = {eventType = EVENTTYPE_ENVIRONMENTAL, captureMap = {HAZARDTYPE_SLIME, DAMAGETYPE_NATURE, 1, 2}}
	globalStringInfo["VSENVIRONMENTALDAMAGE_LAVA_OTHER"] = {eventType = EVENTTYPE_ENVIRONMENTAL, captureMap = {HAZARDTYPE_SLIME, DAMAGETYPE_NATURE, 1, 2}}
	globalStringInfo["VSEVADEOTHEROTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_EVADE, 1, 2, nil}}
	globalStringInfo["VSEVADESELFOTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_EVADE, "player", 1, nil}}
	globalStringInfo["VSIMMUNEOTHEROTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_IMMUNE, 1, 2, nil}}
	globalStringInfo["VSIMMUNEOTHERSELF"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_IMMUNE, 1, "player", nil}}
	globalStringInfo["VSIMMUNESELFOTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_IMMUNE, "player", 1, nil}}
	globalStringInfo["VSPARRYOTHEROTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_PARRY, 1, 2, nil}}
	globalStringInfo["VSPARRYOTHERSELF"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_PARRY, 1, "player", nil}}
	globalStringInfo["VSPARRYSELFOTHER"] = {eventType = EVENTTYPE_MISS, captureMap = {MISSTYPE_PARRY, "player", 1, nil}}


	-- Print an error message for each global string that isn't found and remove it from the info map.
	for name, v in pairs(globalStringInfo) do
		if not _G[name] then
			DEFAULT_CHAT_FRAME:AddMessage("Parser-3.0: Unable to find global string: " .. name)
			globalStringInfo[name] = nil
		end
		if not v.populateEventFunc then
			local eventType = v.eventType
			if eventType == EVENTTYPE_DAMAGE then
				v.populateEventFunc = PopulateDamageEvent
				v.validFunc = ValidityCheckDamageEvent
			elseif eventType == EVENTTYPE_HEAL then
				v.populateEventFunc = PopulateHealEvent
				v.validFunc = ValidityCheckHealEvent
			elseif eventType == EVENTTYPE_NOTIFICATION then
				v.populateEventFunc = PopulateNotificationEvent
				v.validFunc = ValidityCheckNotificationEvent
			elseif eventType == EVENTTYPE_ENVIRONMENTAL then
				v.populateEventFunc = PopulateEnvironmentalEvent
				v.validFunc = ValidityCheckEnvironmentalEvent
			elseif eventType == EVENTTYPE_MISS then
				v.populateEventFunc = PopulateMissEvent
				v.validFunc = ValidityCheckMissEvent
			end
		end
	end
end

local function FindGlobalStringRareWords()
	-- Hold the number of times each word appears in all the global strings.
	local wordCounts = {}
	
	local tmp = {}
	-- Loop through all of the supported global strings.
	for name in pairs(globalStringInfo) do
		local str = _G[name]
		local pattern = str:find("%%1%$[sd]") and "%%%d%$[sd]" or "%%[sd]"
		local start = 0
		local fin = 0
		while true do
			local lastFin = fin
			start, fin = str:find(pattern, start + 1)
			if not start then
				local s = str:sub(lastFin+1)
				if s:len() > 0 then
					tmp[#tmp+1] = s
				end
				break
			end
			if lastFin+1 <= start-1 then
				tmp[#tmp+1] = str:sub(lastFin+1, start-1)
			end
		end
		-- Count how many times each word appears in the global string.
		for i, word in ipairs(tmp) do
			wordCounts[word] = (wordCounts[word] or 0) + 1
			tmp[i] = nil
		end
	end


	-- Loop through all of the supported global strings.
	for name in pairs(globalStringInfo) do
		local leastSeen, rarestWord

		local str = _G[name]
		local pattern = str:find("%%1%$[sd]") and "%%%d%$[sd]" or "%%[sd]"
		local start = 0
		local fin = 0
		while true do
			local lastFin = fin
			start, fin = str:find(pattern, start + 1)
			if not start then
				local s = str:sub(lastFin+1)
				if s:len() > 0 then
					tmp[#tmp+1] = s
				end
				break
			end
			if lastFin+1 <= start-1 then
				tmp[#tmp+1] = str:sub(lastFin+1, start-1)
			end
		end

		-- Find the rarest word in the global string.
		for i, word in ipairs(tmp) do
			if not leastSeen or wordCounts[word] < leastSeen or (wordCounts[word] == leastSeen and word:len() > rarestWord:len()) then
				leastSeen = wordCounts[word]
				rarestWord = word
			end
			tmp[i] = nil
		end

		-- Set the rarest word.
		globalStringInfo[name].rarestWord = rarestWord
	end
end

function ConvertGlobalString(name, first)
	-- Check if the passed global string does not exist.
	local str = _G[name]
	if not str then
		return
	end

	-- Return the cached conversion if it has already been converted.
	if globalStringInfo[name] and globalStringInfo[name].searchPattern then
		return
	end

	-- Hold the capture order.
	local captureOrder
	local numCaptures = 0

	-- Escape lua magic chars.
	local pattern = str:gsub("([%(%)%.%*%+%-%[%]%?%^%$%%])", "%%%1")
	
	if pattern:find("%%%%1%%%$") then
		captureOrder = {}
		-- Loop through each capture and setup the capture order.
		for index in pattern:gmatch("%%%%(%d)%%%$[sd]") do
			numCaptures = numCaptures + 1
			captureOrder[tonumber(index)] = numCaptures
		end
		-- Convert %1$s to (.+) and %1$d to (%d+).
		pattern = pattern:gsub("%%%%%d%%%$s", "(.+)")
		pattern = pattern:gsub("%%%%%d%%%$d", "(%%d+)")
	else
		-- Convert %s to (.+) and %d to (%d+).
		pattern = pattern:gsub("%%%%s", "(.+)")
		pattern = pattern:gsub("%%%%d", "(%%d+)")
	end
	
	-- If no entry exists for the global string name, create one.
	if not globalStringInfo[name] then
		globalStringInfo[name] = {}
	end
	
	if first then
		pattern = "^" .. pattern
	end

	-- Cache the converted pattern and capture order.
	globalStringInfo[name].searchPattern = pattern
	globalStringInfo[name].captureOrder = captureOrder
end

local function ConvertGlobalStrings()
	-- Loop through all of the global strings.
	for name in pairs(globalStringInfo) do
		-- Get the global string converted to a lua search pattern.
		ConvertGlobalString(name, true)
	end

	-- Convert and cache all of the trailer global strings.
	ConvertGlobalString("ABSORB_TRAILER")
	ConvertGlobalString("BLOCK_TRAILER")
	ConvertGlobalString("RESIST_TRAILER")
	ConvertGlobalString("VULNERABLE_TRAILER")
	ConvertGlobalString("CRUSHING_TRAILER")
	ConvertGlobalString("GLANCING_TRAILER")
end

local function ParsePartialEffectInfo(message, start)
	-- Check for a glancing blow.
	if message:find(globalStringInfo["GLANCING_TRAILER"].searchPattern, start) then
		parserEvent.isGlancing = true
	else
		parserEvent.isGlancing = false
	end
	
	-- Check for a crushing blow.
	if message:find(globalStringInfo["CRUSHING_TRAILER"].searchPattern, start) then
		parserEvent.isCrushing = true
	else
		parserEvent.isCrushing = false
	end

	-- Check for a partial absorb.
	local value = message:match(globalStringInfo["ABSORB_TRAILER"].searchPattern, start)
	parserEvent.absorbAmount = value and tonumber(value) or 0

	-- Check for a partial block.
	value = message:match(globalStringInfo["BLOCK_TRAILER"].searchPattern, start)
	parserEvent.blockAmount = value and tonumber(value) or 0

	-- Check for a partial resist.
	value = message:match(globalStringInfo["RESIST_TRAILER"].searchPattern, start)
	parserEvent.resistAmount = value and tonumber(value) or 0

	-- Check for vulnerability damage.
	value = message:match(globalStringInfo["VULNERABLE_TRAILER"].searchPattern, start)
	parserEvent.vulnerableAmount = value and tonumber(value) or 0
end

local function RefreshEvents()
	for msg, info in pairs(globalStringInfo) do
		messages[msg] = false
	end
	for eventType, t in pairs(registry) do
		for filter, func in pairs(t) do
			for msg, info in pairs(globalStringInfo) do
				if info.eventType == eventType then
					if info.validFunc(info.captureMap, filter) then
						messages[msg] = true
					end
				end
			end
		end
	end
	for event, v in pairs(eventSearchMap) do
		Parser.frame:UnregisterEvent(event)
		for _, msg in ipairs(v) do
			if messages[msg] then
				Parser.frame:RegisterEvent(event)
				break
			end
		end
	end
end

function Parser:RegisterParserEvent(data, func, ...)
	Parser:argCheck(data, 2, 'table')
	if self == Parser then
		Parser:argCheck(func, 3, 'function')
	else
		Parser:argCheck(func, 3, 'function', 'string')
		if type(func) == "string" and type(self[func]) ~= "function" then
			Parser:error("Handler %q nonexistent.", func)
		end
	end
	local eventType = data.eventType
	if eventType ~= EVENTTYPE_DAMAGE and eventType ~= EVENTTYPE_NOTIFICATION and eventType ~= EVENTTYPE_HEAL and eventType ~= EVENTTYPE_ENVIRONMENTAL and eventType ~= EVENTTYPE_MISS then
		Parser:error("Bad argument #2 to `RegisterParserEvent'. eventType must be either %q, %q, %q, %q, or %q (currently %q)", EVENTTYPE_DAMAGE, EVENTTYPE_NOTIFICATION, EVENTTYPE_HEAL, EVENTTYPE_ENVIRONMENTAL, EVENTTYPE_MISS, eventType)
	end
	if not registry[eventType] then
		registry[eventType] = {}
	end
	local f = func
	if type(func) == "string" then
		if select('#', ...) > 0 then
			local t = { ... }
			local n = select('#', ...) + 1
			f = function(val)
				t[n] = val
				self[func](self, unpack(t, 1, n))
				t[n] = nil
			end
		else
			f = function(val)
				self[func](self, val)
			end
		end
	else
		if select('#', ...) > 0 then
			local t = { ... }
			local n = select('#', ...) + 1
			f = function(val)
				t[n] = val
				func(unpack(t, 1, n))
				t[n] = nil
			end
		end
	end
	registry[eventType][data] = f
	ownerRegistry[data] = self
	RefreshEvents()
end

function Parser:UnregisterParserEvent(data)
	Parser:argCheck(data, 2, 'table')
	local eventType = data.eventType
	if eventType ~= EVENTTYPE_DAMAGE and eventType ~= EVENTTYPE_NOTIFICATION and eventType ~= EVENTTYPE_HEAL and eventType ~= EVENTTYPE_ENVIRONMENTAL and eventType ~= EVENTTYPE_MISS then
		Parser:error("Bad argument #2 to `UnregisterParserEvent'. eventType must be either %q, %q, %q, %q, or %q (currently %q)", EVENTTYPE_DAMAGE, EVENTTYPE_NOTIFICATION, EVENTTYPE_HEAL, EVENTTYPE_ENVIRONMENTAL, EVENTTYPE_MISS, eventType)
	end
	if registry[eventType] then
		registry[eventType][data] = nil
	end
	ownerRegistry[data] = nil
	RefreshEvents()
end

function Parser:IsParserEventRegistered(data)
	Parser:argCheck(data, 2, 'table')
	local eventType = data.eventType
	if eventType ~= EVENTTYPE_DAMAGE and eventType ~= EVENTTYPE_NOTIFICATION and eventType ~= EVENTTYPE_HEAL and eventType ~= EVENTTYPE_ENVIRONMENTAL and eventType ~= EVENTTYPE_MISS then
		Parser:error("Bad argument #2 to `UnregisterParserEvent'. eventType must be either %q, %q, %q, %q, or %q (currently %q)", EVENTTYPE_DAMAGE, EVENTTYPE_NOTIFICATION, EVENTTYPE_HEAL, EVENTTYPE_ENVIRONMENTAL, EVENTTYPE_MISS, eventType)
	end
	if registry[eventType] and registry[eventType][data] then
		return true
	end
	return false
end

function Parser:UnregisterAllParserEvents()
	Parser:argCheck(self, 1, 'table')
	if self == Parser then
		Parser:error("Must call `UnregisterAllParserEvents' from a mixin instance.")
	end
	for data, owner in pairs(ownerRegistry) do
		if owner == self then
			Parser.UnregisterParserEvent(self, data)
		end
	end
end

function Parser:OnEmbedDisable(target)
	self.UnregisterAllParserEvents(target)
end

if tonumber(date("%Y%m%d")) < 20070224 then
	Parser.Register = Parser.RegisterParserEvent
	Parser.Unregister = Parser.UnregisterParserEvent
elseif tonumber(date("%Y%m%d")) < 20070301 then
	function Parser:Register(...)
		geterrorhandler()(":Register(...) is deprecated, use :RegisterParserEvent(...) instead. This will stop working on March 1st, 2007.")
		self:RegisterParserEvent(...)
	end
	function Parser:Unregister(...)
		geterrorhandler()(":Unregister(...) is deprecated, use :UnregisterParserEvent(...) instead. This will stop working on March 1st, 2007.")
		self:UnregisterParserEvent(...)
	end
end

local function SendParserEvent()
	local r = registry[parserEvent.eventType]
	if r then
		for filter, func in pairs(r) do
			if filterFuncs[filter](parserEvent) then
				local success, ret = pcall(func, parserEvent)
				if not success then
					geterrorhandler()(ret)
				end
			end
		end
	end
end

local tmpCapture
do
	local t = {}
	function tmpCapture(_, finish, ...)
		if not finish then
			return nil
		end
		local num = select('#', ...)
		local t_num = #t
		if t_num > num then
			for i = t_num, num+1, -1 do
				t[i] = nil
			end
		end
		for i = 1, num do
			t[i] = select(i, ...)
		end
		return finish, t
	end
end	
	
local function fixOrder(t, order)
	t[1], t[2], t[3], t[4], t[5], t[6], t[7], t[8], t[9] = t[order[1]], t[order[2]], t[order[3]], t[order[4]], t[order[5]], t[order[6]], t[order[7]], t[order[8]], t[order[9]]
end

local timeToNext = 0
local function OnUpdate(this, elapsed)
	timeToNext = timeToNext + elapsed
	if timeToNext < 0.2 then
		return
	end
	timeToNext = 0
	for k in pairs(toUnitID) do
		toUnitID[k] = nil
	end
	local numParty = GetNumPartyMembers()
	local numRaid = GetNumRaidMembers()
	if numRaid == 0 then
		for i = 1, numParty do
			local unitID = "party" .. i
			local name = UnitName(unitID)
			toUnitID[name] = unitID
			unitID = "partypet" .. i
			if UnitExists(unitID) then
				local name = UnitName(unitID)
				if not toUnitID[name] then
					toUnitID[name] = unitID
				end
			end
		end
	else
		for i = 1, numRaid do
			local unitID = "raid" .. i
			local name = UnitName(unitID)
			toUnitID[name] = unitID
			unitID = "raidpet" .. i
			if UnitExists(unitID) then
				local name = UnitName(unitID)
				if not toUnitID[name] then
					toUnitID[name] = unitID
				end
			end
		end
	end
	if hasPet then
		local unitID = "pet"
		if UnitExists(unitID) then
			local name = UnitName(unitID)
			toUnitID[name] = unitID
		end
	end
	this:Hide()
end

local nextCleanup = 0
local function OnEvent(this, event, message)
	if event == "RAID_ROSTER_UPDATE" or event == "PARTY_MEMBERS_CHANGED" or event == "UNIT_PET" or event == "PLAYER_LOGIN" then
		this:Show()
		return
	end
	if event == "PLAYER_TARGET_CHANGED" then
		if UnitExists("target") and UnitIsPlayer("target") and not UnitIsFriend("player", "target") then
			local now = GetTime()
			if nextCleanup <= now then
				for k, v in pairs(recentPvP) do
					if v < now then
						recentPvP[k] = nil
					end
				end
				nextCleanup = now + 60
			end
			recentPvP[UnitName("target")] = now + 60
		end
		return
	end
	
	-- Leave if there is no map of global strings to search for the event.
	if not eventSearchMap[event] then
		return
	end
	
	-- Loop through all of the global strings to search for the event.
	for _,name in ipairs(eventSearchMap[event]) do
		-- Get the info for the global string and ensure it's valid.
		local info = globalStringInfo[name]
		if info then
			-- First, check if the rarest word in the global string is in the combat message since
			-- a plain text search is faster than doing a full regular expression search.
			if message:find(info.rarestWord, 1, true) then
				-- Get capture data.
				if not messages[name] then
					if message:find(info.searchPattern) then
						return
					end
				else
					local start, capturedData = tmpCapture(message:find(info.searchPattern))
					if capturedData then
						-- Erase the parser event table and call the appropriate function to populate it.
						for k in pairs(parserEvent) do
							parserEvent[k] = nil
						end
						if info.captureOrder then
							fixOrder(capturedData, info.captureOrder)
						end
						info.populateEventFunc(info.captureMap, capturedData, message)
					
						-- Check if the event type is damage or environmental.
					    if parserEvent.eventType == EVENTTYPE_DAMAGE or parserEvent.eventType == EVENTTYPE_ENVIRONMENTAL then
							-- Parse partial effect trailer information.
							if message:len() > start then
								ParsePartialEffectInfo(message, start+1)
							else
								parserEvent.isGlancing = false
								parserEvent.isCrushing = false
								parserEvent.absorbAmount = 0
								parserEvent.blockAmount = 0
								parserEvent.resistAmount = 0
								parserEvent.vulnerableAmount = 0
							end
					    end
					
						-- Make sure there is an event to send.
				    	if parserEvent.eventType then
							-- Send the event.
							SendParserEvent()
						end
						return
					end
				end
			end
		end
	end
end

local function external(self, major, instance)
	if major == "AceLocale-2.2" then
		ConfigureAceLocale22()
	end
end

local function activate(self, oldLib, oldDeactivate)
	Parser = self
	
	self.frame = oldLib and oldLib.frame or CreateFrame("Frame", "Parser30Frame")
	self.frame:SetScript("OnEvent", OnEvent)
	self.frame:SetScript("OnUpdate", OnUpdate)
	self.frame:RegisterEvent("RAID_ROSTER_UPDATE")
	self.frame:RegisterEvent("PARTY_MEMBERS_CHANGED")
	self.frame:RegisterEvent("PLAYER_LOGIN")
	self.frame:RegisterEvent("UNIT_PET")
	self.frame:RegisterEvent("PLAYER_TARGET_CHANGED")
	
	self.registry = oldLib and oldLib.registry or {}
	registry = self.registry
	self.ownerRegistry = oldLib and oldLib.ownerRegistry or {}
	ownerRegistry = self.ownerRegistry
	
	-- Create the event search map.
	CreateEventSearchMap()
	
	-- Create the global string capture map.
	CreateGlobalStringCaptureMaps()
	
	-- Find the rarest word for each supported global string.
	FindGlobalStringRareWords()
	
	-- Convert the supported global strings into lua search patterns.
	ConvertGlobalStrings()
	
	RefreshEvents()
end

AceLibrary:Register(Parser, MAJOR_VERSION, MINOR_VERSION, activate, nil, external)