-------------------------------------------------------------------------------------
-- Title: MSBT Parser
-- Author: Mik
-------------------------------------------------------------------------------------

-- Create module namespace.
MSBTParser = {};

-------------------------------------------------------------------------------------
-- Public constants.
-------------------------------------------------------------------------------------

-- Event types.
MSBTParser.EVENTTYPE_DAMAGE		= 1001;
MSBTParser.EVENTTYPE_MISS		= 1002;
MSBTParser.EVENTTYPE_HEAL		= 1003;
MSBTParser.EVENTTYPE_NOTIFICATION	= 1004;
MSBTParser.EVENTTYPE_ENVIRONMENTAL	= 1005;

-- Damage types.
MSBTParser.DAMAGETYPE_PHYSICAL	= 1001;
MSBTParser.DAMAGETYPE_HOLY		= 1002;
MSBTParser.DAMAGETYPE_FIRE		= 1003;
MSBTParser.DAMAGETYPE_NATURE		= 1004;
MSBTParser.DAMAGETYPE_FROST		= 1005;
MSBTParser.DAMAGETYPE_SHADOW		= 1006;
MSBTParser.DAMAGETYPE_ARCANE		= 1007;
MSBTParser.DAMAGETYPE_UNKNOWN		= 9999;

-- Miss types.
MSBTParser.MISSTYPE_MISS		= 1001;
MSBTParser.MISSTYPE_DODGE		= 1002;
MSBTParser.MISSTYPE_PARRY		= 1003;
MSBTParser.MISSTYPE_BLOCK		= 1004;
MSBTParser.MISSTYPE_RESIST		= 1005;
MSBTParser.MISSTYPE_ABSORB		= 1006;
MSBTParser.MISSTYPE_IMMUNE		= 1007;
MSBTParser.MISSTYPE_EVADE		= 1008;
MSBTParser.MISSTYPE_REFLECT		= 1009;
MSBTParser.MISSTYPE_INTERRUPT		= 1010;

-- Notification types.
MSBTParser.NOTIFICATIONTYPE_DEBUFF			= 1001;
MSBTParser.NOTIFICATIONTYPE_BUFF			= 1002;
MSBTParser.NOTIFICATIONTYPE_ITEM_BUFF		= 1003;
MSBTParser.NOTIFICATIONTYPE_DEBUFF_FADE		= 1004;
MSBTParser.NOTIFICATIONTYPE_BUFF_FADE		= 1005;
MSBTParser.NOTIFICATIONTYPE_ITEM_BUFF_FADE	= 1006;
MSBTParser.NOTIFICATIONTYPE_COMBAT_ENTER		= 1007;
MSBTParser.NOTIFICATIONTYPE_COMBAT_LEAVE		= 1008;
MSBTParser.NOTIFICATIONTYPE_POWER_GAIN		= 1009;
MSBTParser.NOTIFICATIONTYPE_POWER_LOSS		= 1010;
MSBTParser.NOTIFICATIONTYPE_CP_GAIN			= 1011;
MSBTParser.NOTIFICATIONTYPE_HONOR_GAIN		= 1012;
MSBTParser.NOTIFICATIONTYPE_REP_GAIN		= 1013;
MSBTParser.NOTIFICATIONTYPE_REP_LOSS		= 1014;
MSBTParser.NOTIFICATIONTYPE_SKILL_GAIN		= 1015;
MSBTParser.NOTIFICATIONTYPE_EXPERIENCE_GAIN	= 1016;
MSBTParser.NOTIFICATIONTYPE_PC_KILLING_BLOW	= 1017;
MSBTParser.NOTIFICATIONTYPE_NPC_KILLING_BLOW	= 1018;
MSBTParser.NOTIFICATIONTYPE_SOUL_SHARD_CREATED	= 1019;
MSBTParser.NOTIFICATIONTYPE_EXTRA_ATTACK		= 1020;

-- Hazard types.
MSBTParser.HAZARDTYPE_DROWNING	= 1001;
MSBTParser.HAZARDTYPE_FALLING		= 1002;
MSBTParser.HAZARDTYPE_FATIGUE		= 1003;
MSBTParser.HAZARDTYPE_FIRE		= 1004;
MSBTParser.HAZARDTYPE_LAVA		= 1005;
MSBTParser.HAZARDTYPE_SLIME		= 1006;


-------------------------------------------------------------------------------------
-- Private constants.
-------------------------------------------------------------------------------------

-- Amount of time to hold a recently selected player in cache.
local RECENTLY_SELECTED_PLAYERS_HOLD_TIME = 60;

-- Amount of time to wait between updates.
local UPDATE_INTERVAL = 1;

-- The maximum number of buffs or debuffs that can be on a player.
local MAX_PLAYER_BUFFS = 16;


-------------------------------------------------------------------------------------
-- Public variables.
-------------------------------------------------------------------------------------

MSBTParser.CurrentAuras = nil;

-------------------------------------------------------------------------------------
-- Private variables.
-------------------------------------------------------------------------------------

-- Holds dynamically created frames for receiving events and tooltip info.
local eventFrame;
local tooltipFrame;

-- Holds the events the parser is interested in receiving.
local listenEvents = {};

-- Holds the name of the player.
local playerName;

-- Accumulates time between updates.
local timeSinceLastUpdate = 0;
local targetCleanupTime = 0;

-- Holds a list of recently selected hostile players.
local recentlySelectedPlayers = {};

-- Holds saved and current buffs and debuffs.
local currentAuras;
local savedAuras;

-- Holds recent item buffs.
local recentItemBuffs = {};

-- Holds which global strings to search for each event and information about them.
local eventSearchMap;
local globalStringInfo;

-- Table to hold the parser event data.
local parserEvent = {};

-- Table to hold unordered captures.
local unorderedCaptures = {};


-------------------------------------------------------------------------------------
-- Core event handlers.
-------------------------------------------------------------------------------------

-- **********************************************************************************
-- Registers all of the events the parser is interested in.
-- **********************************************************************************
function MSBTParser.RegisterEvents()
 -- Register the events we are interested in receiving.
 for k, v in ipairs(listenEvents) do
  eventFrame:RegisterEvent(v);
 end 
end


-- **********************************************************************************
-- Unregisters all of the event the parser registered for.
-- **********************************************************************************
function MSBTParser.UnregisterEvents()
 -- Unregister the events that were registered for.
 for k, v in ipairs(listenEvents) do
  eventFrame:UnregisterEvent(v);
 end 
end


-- **********************************************************************************
-- Called when the parser is loaded.
-- **********************************************************************************
function MSBTParser.OnLoad()
 -- Create a frame to receive events.
 eventFrame = CreateFrame("FRAME");
 eventFrame:Hide();
 eventFrame:SetScript("OnEvent", MSBTParser.OnEvent);
 eventFrame:SetScript("OnUpdate", MSBTParser.OnUpdate);

 -- Create a tooltip frame to get buff/debuff names.
 tooltipFrame = CreateFrame("GameTooltip", "MSBTParserTooltip", UIParent, "GameTooltipTemplate");
  

 -- Register for the ADDON_LOADED event.
 eventFrame:RegisterEvent("ADDON_LOADED");
end


-- **********************************************************************************
-- Called when the events the parser registered for occur.
-- **********************************************************************************
function MSBTParser.OnEvent()
 -- When an addon is loaded.
 if (event == "ADDON_LOADED") then
  -- Make sure it's the right addon.
  if (arg1 == MikSBT.MOD_NAME) then

   -- Don't get notification for other addons being loaded.
   this:UnregisterEvent("ADDON_LOADED");

   -- Initialize the parser.
   MSBTParser.Init();
  end

 -- Leave Combat
 elseif (event == "PLAYER_REGEN_ENABLED") then
  MSBTParser.EraseParserEventTable();
  parserEvent.EventType = MSBTParser.EVENTTYPE_NOTIFICATION;
  parserEvent.NotificationType = MSBTParser.NOTIFICATIONTYPE_COMBAT_LEAVE;
  parserEvent.RecipientName = playerName;
  MSBTParser.SendParserEvent();

 -- Enter Combat
 elseif (event == "PLAYER_REGEN_DISABLED") then
  MSBTParser.EraseParserEventTable();
  parserEvent.EventType = MSBTParser.EVENTTYPE_NOTIFICATION;
  parserEvent.NotificationType = MSBTParser.NOTIFICATIONTYPE_COMBAT_ENTER;
  parserEvent.RecipientName = playerName;
  MSBTParser.SendParserEvent();

 -- Combo Point Gains
 elseif (event == "PLAYER_COMBO_POINTS") then
  local numCP = GetComboPoints();

  -- Make sure there are combo points.
  if (numCP ~= 0) then
   MSBTParser.EraseParserEventTable();
   parserEvent.EventType = MSBTParser.EVENTTYPE_NOTIFICATION;
   parserEvent.NotificationType = MSBTParser.NOTIFICATIONTYPE_CP_GAIN;
   parserEvent.RecipientName = playerName;
   parserEvent.Amount = numCP;
   MSBTParser.SendParserEvent();
  end

 -- Target changes
 elseif (event == "PLAYER_TARGET_CHANGED") then
  -- Make sure a unit is selected, is a player, and is hostile.
  if (UnitExists("target") and UnitIsPlayer("target") and not UnitIsFriend("player", "target")) then
   -- Get the current time and check if it's time to clean up targets.
   local now = GetTime();
   if (now >= targetCleanupTime) then
    -- Loop through all of the recently selected players.
    for name, cleanupTime in pairs(recentlySelectedPlayers) do
     if (now >= cleanupTime) then
      recentlySelectedPlayers[name] = nil;
     end
    end

    -- Update the target cleanup time.
    targetCleanupTime = now + RECENTLY_SELECTED_PLAYERS_HOLD_TIME;
   end

   -- Get the unit's name and make sure it's valid before adding it to the recently selected player's list.
   local targetName = UnitName("target");
   if (targetName) then
    recentlySelectedPlayers[targetName] = now + RECENTLY_SELECTED_PLAYERS_HOLD_TIME;
   end
  end

 -- Buff/Debuff Gains/Fades.
 elseif (event == "PLAYER_AURAS_CHANGED") then
  -- Initialize the saved auras if the haven't been created yet.
  if (not savedAuras) then
   MSBTParser.InitAuras();
   return;
  end

  -- Populate the current buffs and debuffs.
  MSBTParser.PopulateCurrentBuffsAndDebuffs();

  -- Send events for buff and debuff changes.
  MSBTParser.DoAuraChange("Buffs", MSBTParser.NOTIFICATIONTYPE_BUFF, MSBTParser.NOTIFICATIONTYPE_BUFF_FADE);
  MSBTParser.DoAuraChange("Debuffs", MSBTParser.NOTIFICATIONTYPE_DEBUFF, MSBTParser.NOTIFICATIONTYPE_DEBUFF_FADE);

 -- Chat message combat events.
 else
  MSBTParser.ParseCombatEvents(event, arg1);

 end
end


-- **********************************************************************************
-- Called when the onupdate event occurs.
-- **********************************************************************************
function MSBTParser.OnUpdate()
 -- Increment the amount of time passed since the last update.
 timeSinceLastUpdate = timeSinceLastUpdate + arg1;

 -- Check if it's time for an update.
 if (timeSinceLastUpdate >= UPDATE_INTERVAL) then
  -- Check for item buff changes.
  if (savedAuras) then
   -- Populate the current item buffs.
   MSBTParser.PopulateCurrentItemBuffs();

   -- Send events for item buff changes.
   MSBTParser.DoAuraChange("ItemBuffs", MSBTParser.NOTIFICATIONTYPE_ITEM_BUFF, MSBTParser.NOTIFICATIONTYPE_ITEM_BUFF_FADE);
  end

  -- Reset the time since last update.
  timeSinceLastUpdate = 0;
 end
end



-- **********************************************************************************
-- Called when the mod is fully loaded (variables loaded).
-- **********************************************************************************
function MSBTParser.Init()
 -- Get the name of the player.
 playerName = UnitName("player");


 -- Create the event search map.
 MSBTParser.CreateEventSearchMap();

 -- Create the global string capture map.
 MSBTParser.CreateGlobalStringCaptureMaps();

 -- Find the rarest word for each supported global string.
 MSBTParser.FindGlobalStringRareWords();

 -- Convert the supported global strings into lua search patterns.
 MSBTParser.ConvertGlobalStrings();

 -- Load up the listen events table with the events the parser is interested in.
 table.insert(listenEvents, "CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS");		-- Incoming Melee Hits/Crits
 table.insert(listenEvents, "CHAT_MSG_COMBAT_HOSTILEPLAYER_HITS");		-- Incoming Melee Hits/Crits
 table.insert(listenEvents, "CHAT_MSG_COMBAT_PARTY_HITS");				-- Incoming Melee Hits/Crits 
 table.insert(listenEvents, "CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES"); 	-- Incoming Melee Misses, Dodges, Parries, Blocks, Absorbs, Immunes
 table.insert(listenEvents, "CHAT_MSG_COMBAT_HOSTILEPLAYER_MISSES");		-- Incoming Melee Misses, Dodges, Parries, Blocks, Absorbs, Immunes
 table.insert(listenEvents, "CHAT_MSG_COMBAT_PARTY_MISSES");			-- Incoming Melee Misses, Dodges, Parries, Blocks, Absorbs, Immunes
 table.insert(listenEvents, "CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE");		-- Incoming Spell/Ability Damage, Misses, Dodges, Parries, Blocks, Absorbs, Resists, Immunes, Reflects, Power Losses
 table.insert(listenEvents, "CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE");		-- Incoming Spell/Ability Damage, Misses, Dodges, Parries, Blocks, Absorbs, Resists, Immunes, Reflects, Power Losses
 table.insert(listenEvents, "CHAT_MSG_SPELL_PARTY_DAMAGE");				-- Incoming Spell/Ability Damage, Misses, Dodges, Parries, Blocks, Absorbs, Resists, Immunes, Reflects, Power Losses
 table.insert(listenEvents, "CHAT_MSG_SPELL_DAMAGESHIELDS_ON_OTHERS");		-- Incoming damage from shields
 table.insert(listenEvents, "CHAT_MSG_SPELL_HOSTILEPLAYER_BUFF");			-- Incoming Heals, Power Gains
 table.insert(listenEvents, "CHAT_MSG_SPELL_CREATURE_VS_SELF_BUFF");		-- Incoming Heals, Power Gains
 table.insert(listenEvents, "CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE");		-- Incoming DoTs, Power Gains
 table.insert(listenEvents, "CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS");		-- Incoming HoTs, Power Gains
 table.insert(listenEvents, "CHAT_MSG_SPELL_PET_BUFF");				-- Incoming Power Gains from Pet.

 table.insert(listenEvents, "CHAT_MSG_COMBAT_SELF_HITS");				-- Outgoing Melee Hits/Crits, Environmental Damage
 table.insert(listenEvents, "CHAT_MSG_COMBAT_SELF_MISSES");  			-- Outgoing Melee Misses, Dodges, Parries, Blocks, Absorbs, Immunes, Evades
 table.insert(listenEvents, "CHAT_MSG_SPELL_SELF_DAMAGE");				-- Outgoing Spell/Ability Damage, Misses, Dodges, Parries, Blocks, Absorbs, Resists, Immunes, Evades, Extra Attacks
 table.insert(listenEvents, "CHAT_MSG_SPELL_DAMAGESHIELDS_ON_SELF");		-- Outgoing damage from shields
 table.insert(listenEvents, "CHAT_MSG_SPELL_SELF_BUFF");				-- Outgoing Heals, Power Gains, Dispel/Purge Resists, Extra Attacks
 table.insert(listenEvents, "CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS");	-- Outgoing HoTs, Other's Buffs
 table.insert(listenEvents, "CHAT_MSG_SPELL_PERIODIC_PARTY_BUFFS");		-- Outgoing HoTs, Other's Buffs
 table.insert(listenEvents, "CHAT_MSG_SPELL_PERIODIC_CREATURE_BUFFS");		-- Other's Buffs
 table.insert(listenEvents, "CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_BUFFS");	-- Other's Buffs
 table.insert(listenEvents, "CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE");		-- Outgoing DoTs, Power Losses, Other's Debuffs
 table.insert(listenEvents, "CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE");	-- Outgoing DoTs, Power Losses, Other's Debuffs
 table.insert(listenEvents, "CHAT_MSG_COMBAT_PET_HITS");				-- Outgoing Pet Melee Hits/Crits
 table.insert(listenEvents, "CHAT_MSG_COMBAT_PET_MISSES");				-- Outgoing Pet Melee Misses
 table.insert(listenEvents, "CHAT_MSG_SPELL_PET_DAMAGE");				-- Outgoing Pet Spell/Ability Damage, Misses, Dodges, Parries, Blocks, Absorbs, Resists, Immunes, Evades

 table.insert(listenEvents, "CHAT_MSG_COMBAT_HONOR_GAIN");				-- Honor Gains
 table.insert(listenEvents, "CHAT_MSG_COMBAT_FACTION_CHANGE");			-- Reputation Gains/Losses
 table.insert(listenEvents, "CHAT_MSG_SKILL");						-- Skill Gains
 table.insert(listenEvents, "CHAT_MSG_COMBAT_XP_GAIN");				-- Experience Gains
 table.insert(listenEvents, "CHAT_MSG_COMBAT_HOSTILE_DEATH");			-- Killing Blows
 table.insert(listenEvents, "CHAT_MSG_LOOT");						-- Created Items (Soul Shards)

 table.insert(listenEvents, "PLAYER_REGEN_ENABLED");					-- Leave Combat
 table.insert(listenEvents, "PLAYER_REGEN_DISABLED");					-- Enter Combat
 table.insert(listenEvents, "PLAYER_COMBO_POINTS");					-- Combo Point Gains
 table.insert(listenEvents, "PLAYER_AURAS_CHANGED");					-- Self Buff/Debuff Gains/Fades

 table.insert(listenEvents, "PLAYER_TARGET_CHANGED");					-- Target changes.
end


-- **********************************************************************************
-- Parses the chat message combat events.
-- **********************************************************************************
function MSBTParser.ParseCombatEvents(event, combatMessage)
 -- 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 _, globalStringName in pairs(eventSearchMap[event]) do
  -- Get the info for the global string and ensure it's valid.
  local gsInfo = globalStringInfo[globalStringName];
  if (gsInfo) 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 (string.find(combatMessage, gsInfo.RarestWord, 1, true)) then
   local checkedRarestWord = GetLocale() ~= "koKR" and string.find(combatMessage, gsInfo.RarestWord, 1, true) or true
   if checkedRarestWord then
    -- Get capture data.
    local matchEnd, capturedData = MSBTParser.GetUnorderedCaptures(string.find(combatMessage, gsInfo.SearchPattern));

    -- Check if a match was found. 
    if (capturedData) then
     -- Erase the parser event table and call the appropriate function to populate it.
     MSBTParser.EraseParserEventTable();
     gsInfo.PopulateEventFunc(gsInfo.CaptureMap, capturedData, gsInfo.CaptureOrder);

     -- Check if the event type is damage or environmental.
     if (parserEvent.EventType == MSBTParser.EVENTTYPE_DAMAGE or
         parserEvent.EventType == MSBTParser.EVENTTYPE_ENVIRONMENTAL) then
      -- Check if there are any trailers.
      if (string.len(combatMessage) > matchEnd) then
       -- Parse partial effect trailer information.
       MSBTParser.ParsePartialEffectInfo(combatMessage, matchEnd+1);
      end
     end

     -- Make sure there is an event to send.
     if (parserEvent.EventType) then
      -- Send the event.
      MSBTParser.SendParserEvent();
     end
     return;
    end -- Match found.
   end -- Fast plain search.
  end -- Global string info is valid.
 end -- Loop through global strings to search.
end



-- **********************************************************************************
-- Parses the passed combat message for partial effects and populates the parser
-- event data table with any found data.
-- **********************************************************************************
function MSBTParser.ParsePartialEffectInfo(combatMessage, startPos)
 local matchStart, capturedAmount;

 -- Check for a glancing blow.
 if (string.find(combatMessage, globalStringInfo["GLANCING_TRAILER"].SearchPattern, startPos)) then
  parserEvent.IsGlancing = true;
 end

 -- Check for a crushing blow.
 if (string.find(combatMessage, globalStringInfo["CRUSHING_TRAILER"].SearchPattern, startPos)) then
  parserEvent.IsCrushing = true;
 end

 -- Check for a partial absorb.
 matchStart, _, capturedAmount = string.find(combatMessage, globalStringInfo["ABSORB_TRAILER"].SearchPattern, startPos)
 if (matchStart) then
  parserEvent.AbsorbAmount = capturedAmount;
 end

 -- Check for a partial block.
 matchStart, _, capturedAmount = string.find(combatMessage, globalStringInfo["BLOCK_TRAILER"].SearchPattern, startPos)
 if (matchStart) then
  parserEvent.BlockAmount = capturedAmount;
 end

 -- Check for a partial resist.
 matchStart, _, capturedAmount = string.find(combatMessage, globalStringInfo["RESIST_TRAILER"].SearchPattern, startPos)
 if (matchStart) then
  parserEvent.ResistAmount = capturedAmount;
 end

 -- Check for vulnerability damage.
 matchStart, _, capturedAmount = string.find(combatMessage, globalStringInfo["VULNERABLE_TRAILER"].SearchPattern, startPos)
 if (matchStart) then
  parserEvent.VulnerabilityAmount = capturedAmount;
 end
end


-- **********************************************************************************
-- Sends the parser event to the MSBT's parser events handler.
-- **********************************************************************************
function MSBTParser.SendParserEvent()
 MikSBT.ParserEventsHandler(parserEvent);
 MSBTTriggers.ParserEventsHandler(parserEvent);
end



-------------------------------------------------------------------------------------
-- Aura functions.
-------------------------------------------------------------------------------------

-- **********************************************************************************
-- Gets the buff name for the passed index.
-- **********************************************************************************
function MSBTParser.GetBuffName(index)
 -- Set the tooltip to the buff index.
 tooltipFrame:SetOwner(UIParent, "ANCHOR_NONE");
 tooltipFrame:SetPlayerBuff(index);

 -- Return the buff's name.
 return getglobal(tooltipFrame:GetName() .. "TextLeft1"):GetText();
end


-- **********************************************************************************
-- Gets the item buff name for the passed slot.
-- **********************************************************************************
function MSBTParser.GetItemBuffName(slotNumber)
 -- Set the tooltip to the buff index.
 tooltipFrame:SetOwner(UIParent, "ANCHOR_NONE");
 tooltipFrame:SetInventoryItem("player", slotNumber);

 local text, buffName;
 for i = 1, tooltipFrame:NumLines() do
  -- Get the text for the line number and attempt to extract a temporary item buff from it.
  text = getglobal(tooltipFrame:GetName() .. "TextLeft" .. i):GetText();
  _, _, buffName = string.find(text, "^([^%(]+) %(%d+ [^%)]+%)");

  -- Check if an item buff was found and return it.
  if (buffName) then
   return buffName
  end
 end
end


-- **********************************************************************************
-- Populates the current auras table with the current buff and debuff information.
-- **********************************************************************************
function MSBTParser.PopulateCurrentBuffsAndDebuffs()
 -- Erase the old current aura buffs.
 for buffName in pairs(currentAuras.Buffs) do
  currentAuras.Buffs[buffName] = nil;
 end

 -- Erase the old current aura debuffs.
 for buffName in pairs(currentAuras.Debuffs) do
  currentAuras.Debuffs[buffName] = nil;
 end

 -- Loop through all of the buffs and add the name to the buffs table.
 local buffIndex, buffName;
 for buffId = 1, MAX_PLAYER_BUFFS do
  buffIndex = GetPlayerBuff(buffId, "HELPFUL");
  if (buffIndex == 0) then break; end
  buffName = MSBTParser.GetBuffName(buffIndex);
  if (buffName) then
   currentAuras.Buffs[buffName] = true;
  end
 end

 -- Loop through all of the debuffs and add the name to the buffs table.
 for buffId = 1, MAX_PLAYER_BUFFS do
  buffIndex = GetPlayerBuff(buffId, "HARMFUL");
  if (buffIndex == 0) then break; end
  buffName = MSBTParser.GetBuffName(buffIndex);
  if (buffName) then
   currentAuras.Debuffs[buffName] = true;
  end
 end
end


-- **********************************************************************************
-- Populates the current auras table with the current item buff information.
-- **********************************************************************************
function MSBTParser.PopulateCurrentItemBuffs()
 -- Erase the old current aura item buffs.
 for buffName in pairs(currentAuras.ItemBuffs) do
  currentAuras.ItemBuffs[buffName] = nil;
 end

 -- Add main hand item buff info to the item buffs table.
 local hasMainHandBuff, _, _, hasOffHandBuff = GetWeaponEnchantInfo();
 if (hasMainHandBuff) then
  buffName = MSBTParser.GetItemBuffName(GetInventorySlotInfo("MainHandSlot"));
  if (buffName) then
   currentAuras.ItemBuffs[buffName] = true;
  end
 end

 -- Add off hand item buff info to the item buffs table.
 if (hasOffHandBuff) then
  buffName = MSBTParser.GetItemBuffName(GetInventorySlotInfo("SecondaryHandSlot"));
  if (buffName) then
   currentAuras.ItemBuffs[buffName] = true;
  end
 end
end



-- **********************************************************************************
-- Initializes the current auras.
-- **********************************************************************************
function MSBTParser.InitAuras()
 -- Create tables to hold the current buffs, debuffs, and item buffs.
 currentAuras = {
  Buffs = {},
  Debuffs = {},
  ItemBuffs = {}
 };

 -- Set the public current auras to the local variable.
 MSBTParser.CurrentAuras = currentAuras;

 -- Populate the current buffs, debuffs, and item buffs.
 MSBTParser.PopulateCurrentBuffsAndDebuffs();
 MSBTParser.PopulateCurrentItemBuffs();

 -- Save the current auras.
 savedAuras = MSBTTableRecycler.CopyTable(currentAuras);
end


-- **********************************************************************************
-- Populates the parser event data table for an aura change or the passed type.
-- **********************************************************************************
function MSBTParser.DoAuraChange(tableName, gainNotificationType, fadeNotificationType)
 -- Loop through all of the saved auras for the passed buff table to check for those that have faded.
 for buffName in pairs(savedAuras[tableName]) do
  if (not currentAuras[tableName][buffName]) then
   savedAuras[tableName][buffName] = nil;
   MSBTParser.EraseParserEventTable();
   parserEvent.EventType = MSBTParser.EVENTTYPE_NOTIFICATION;
   parserEvent.NotificationType = fadeNotificationType;
   parserEvent.RecipientName = playerName;
   parserEvent.EffectName = buffName;
   MSBTParser.SendParserEvent();
  end
 end

 -- Loop through all of the current auras for the passed buff table to check for those that have been gained.
 for buffName in pairs(currentAuras[tableName]) do
  if (not savedAuras[tableName][buffName]) then
   savedAuras[tableName][buffName] = true;
   MSBTParser.EraseParserEventTable();
   parserEvent.EventType = MSBTParser.EVENTTYPE_NOTIFICATION;
   parserEvent.NotificationType = gainNotificationType;
   parserEvent.RecipientName = playerName;
   parserEvent.EffectName = buffName;
   MSBTParser.SendParserEvent();
  end
 end
end


-------------------------------------------------------------------------------------
-- Utility functions.
-------------------------------------------------------------------------------------

-- **********************************************************************************
-- Erases the parser event table.
-- **********************************************************************************
function MSBTParser.EraseParserEventTable() 
 -- Erase the parser event table.
 for key in pairs(parserEvent) do
  parserEvent[key] = nil;
 end
end


-- **********************************************************************************
-- Compares two global strings so the most specific one comes first.  This prevents
-- incorrectly capturing information for certain events.
-- **********************************************************************************
function MSBTParser.GlobalStringCompareFunc(globalStringNameOne, globalStringNameTwo)
 -- Get the global string for the passed names.
 local globalStringOne = _G[globalStringNameOne];
 local globalStringTwo = _G[globalStringNameTwo];

 local gsOneStripped = string.gsub(globalStringOne, "%%%d?%$?[sd]", "");
 local gsTwoStripped = string.gsub(globalStringTwo, "%%%d?%$?[sd]", "");

 -- Check if the stripped global strings are the same length.
 if (string.len(gsOneStripped) == string.len(gsTwoStripped)) then
  -- Count the number of captures in each string.
  local numCapturesOne = 0;
  for _ in string.gmatch(globalStringOne, "%%%d?%$?[sd]") do
   numCapturesOne = numCapturesOne + 1;
  end

  local numCapturesTwo = 0;
  for _ in string.gmatch(globalStringTwo, "%%%d?%$?[sd]") do
   numCapturesTwo = numCapturesTwo + 1;
  end
  
  -- Return the global string with the least captures.
  return numCapturesOne < numCapturesTwo;

 else
  -- Return the longest global string.
  return string.len(gsOneStripped) > string.len(gsTwoStripped);
 end
end


-- **********************************************************************************
-- Creates a map of global strings to search for each event.
-- **********************************************************************************
function MSBTParser.CreateEventSearchMap()
 -- Create a table to hold the map.
 eventSearchMap = {};


 -- Incoming Melee Hits/Crits
 local incomingMeleeHits = {
  -- Elemental Crit/Hit, Physical Crit/Hit.
  "COMBATHITCRITSCHOOLOTHERSELF", "COMBATHITSCHOOLOTHERSELF", "COMBATHITCRITOTHERSELF", "COMBATHITOTHERSELF"
 };
 eventSearchMap["CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS"] = incomingMeleeHits;
 eventSearchMap["CHAT_MSG_COMBAT_HOSTILEPLAYER_HITS"] = incomingMeleeHits;
 eventSearchMap["CHAT_MSG_COMBAT_PARTY_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, Interrupts, Power Losses
 local incomingSpellHitsAndMisses = {
  -- Physical Crit/Hit, Ability Crit/Hit, Spell Crit/Hit, Split Damage, Miss, Dodge, Parry,
  -- Block, Spell Resist, Absorb, Immune, Reflect, Interrupt, Leech, Drain
  "COMBATHITCRITOTHERSELF", "COMBATHITOTHERSELF", "SPELLLOGCRITOTHERSELF", "SPELLLOGOTHERSELF",
  "SPELLLOGCRITSCHOOLOTHERSELF", "SPELLLOGSCHOOLOTHERSELF", "SPELLSPLITDAMAGEOTHERSELF", "SPELLMISSOTHERSELF",
  "SPELLDODGEDOTHERSELF", "SPELLDODGEDOTHERSELF", "SPELLPARRIEDOTHERSELF", "SPELLBLOCKEDOTHERSELF",
  "SPELLRESISTOTHERSELF", "SPELLLOGABSORBOTHERSELF", "SPELLIMMUNEOTHERSELF", "SPELLINTERRUPTOTHERSELF",
  "SPELLREFLECTOTHERSELF", "SPELLPOWERLEECHOTHERSELF", "SPELLPOWERDRAINOTHERSELF"
 };
 eventSearchMap["CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE"] = incomingSpellHitsAndMisses;
 eventSearchMap["CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE"] = incomingSpellHitsAndMisses;
 eventSearchMap["CHAT_MSG_SPELL_PARTY_DAMAGE"] = incomingSpellHitsAndMisses;


 -- Incoming damage from shields
 eventSearchMap["CHAT_MSG_SPELL_DAMAGESHIELDS_ON_OTHERS"] = {
  "DAMAGESHIELDOTHERSELF"
 };


 -- 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", "PERIODICAURADAMAGESELF", "SPELLLOGABSORBOTHERSELF", "SPELLLOGABSORBSELF",
  "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"
 };


 -- Incoming Power Gains from Pet.
 eventSearchMap["CHAT_MSG_SPELL_PET_BUFF"] = {
  -- Power gains from other.
  "POWERGAINOTHERSELF"
 };


 -- 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, Interrupt, Extra Attack(s), 
  "SPELLLOGCRITSELFOTHER", "SPELLLOGSELFOTHER", "SPELLLOGCRITSCHOOLSELFSELF", "SPELLLOGSCHOOLSELFSELF",
  "SPELLLOGCRITSCHOOLSELFOTHER", "SPELLLOGSCHOOLSELFOTHER", "SPELLMISSSELFOTHER", "SPELLDODGEDSELFOTHER",
  "SPELLPARRIEDSELFOTHER", "SPELLBLOCKEDSELFOTHER", "SPELLRESISTSELFOTHER", "SPELLLOGABSORBSELFOTHER",
  "SPELLIMMUNESELFOTHER", "SPELLREFLECTSELFOTHER", "SPELLEVADEDSELFOTHER", "SPELLINTERRUPTSELFOTHER",
  "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"
 };


 -- Other's Buffs.
 local othersBuffs = {
  "AURAADDEDOTHERHELPFUL", "AURAAPPLICATIONADDEDOTHERHELPFUL"
 };
 eventSearchMap["CHAT_MSG_SPELL_PERIODIC_CREATURE_BUFFS"] = othersBuffs;
 eventSearchMap["CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_BUFFS"] = othersBuffs;

 -- Outgoing HoTs, Other's Buffs.
 local outgoingHoTsAndOthersBuffs = {
  -- HoT to others.
  "PERIODICAURAHEALSELFOTHER", "AURAADDEDOTHERHELPFUL", "AURAAPPLICATIONADDEDOTHERHELPFUL"
 };
 eventSearchMap["CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS"] = outgoingHoTsAndOthersBuffs;
 eventSearchMap["CHAT_MSG_SPELL_PERIODIC_PARTY_BUFFS"] = outgoingHoTsAndOthersBuffs;


 -- Outgoing DoTs, Power Losses, Other's Debuffs
 local outgoingDoTsAndPowerLosses = {
  -- DoT to others, Absorbed DoT, Leech, Drain, Enemy Debuffs
  "PERIODICAURADAMAGESELFOTHER", "SPELLLOGABSORBSELFOTHER", "SPELLPOWERLEECHOTHERSELF", "SPELLPOWERDRAINOTHERSELF",
  "AURAADDEDOTHERHARMFUL", "AURAAPPLICATIONADDEDOTHERHARMFUL"
 };
 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"] = {
  -- Elemental Crit/Hit, Physical Crit/Hit
  "COMBATHITCRITSCHOOLOTHEROTHER", "COMBATHITSCHOOLOTHEROTHER", "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"
 };


 -- 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, map in pairs(eventSearchMap) do
  -- Remove invalid global strings.
  local numGlobalStrings = #map;
  for i = numGlobalStrings, 1, -1 do
   if (not _G[map[i]]) then
    table.remove(map, i);
   end
  end

  -- Sort the global strings from most to least specific.
  table.sort(map, MSBTParser.GlobalStringCompareFunc);
 end
end


-- **********************************************************************************
-- Creates a map of data to capture for each global string.
-- **********************************************************************************
function MSBTParser.CreateGlobalStringCaptureMaps()
 -- Create a table for global string info.
 globalStringInfo = {};

 -- Create the capture map for each supported global string.

 -- Damage Event (DamageType, SourceName, RecipientName, Amount, AbilityName, IsCrit, IsDoT).
 globalStringInfo["COMBATHITCRITOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {MSBTParser.DAMAGETYPE_PHYSICAL, 1, playerName, 2, nil, true, false}};
 globalStringInfo["COMBATHITCRITSCHOOLOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {3, 1, playerName, 2, nil, true, false}};
 globalStringInfo["COMBATHITCRITSCHOOLOTHEROTHER"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {4, 1, 2, 3, nil, true, false}};
 globalStringInfo["COMBATHITCRITSELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {MSBTParser.DAMAGETYPE_PHYSICAL, playerName, 1, 2, nil, true, false}};
 globalStringInfo["COMBATHITCRITOTHEROTHER"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {MSBTParser.DAMAGETYPE_PHYSICAL, 1, 2, 3, nil, true, false}};
 globalStringInfo["COMBATHITOTHEROTHER"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {MSBTParser.DAMAGETYPE_PHYSICAL, 1, 2, 3, nil, false, false}};
 globalStringInfo["COMBATHITOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {MSBTParser.DAMAGETYPE_PHYSICAL, 1, playerName, 2, nil, false, false}};
 globalStringInfo["COMBATHITSCHOOLOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {3, 1, playerName, 2, nil, false, false}};
 globalStringInfo["COMBATHITSCHOOLOTHEROTHER"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {4, 1, 2, 3, nil, false, false}};
 globalStringInfo["COMBATHITSELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {MSBTParser.DAMAGETYPE_PHYSICAL, playerName, 1, 2, nil, false, false}};
 globalStringInfo["DAMAGESHIELDOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {3, 1, playerName, 2, nil, false, false}};
 globalStringInfo["DAMAGESHIELDSELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {2, playerName, 3, 1, nil, false, false}};
 globalStringInfo["PERIODICAURADAMAGEOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {2, 3, playerName, 1, 4, false, true}};
 globalStringInfo["PERIODICAURADAMAGESELF"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {2, 3, playerName, 1, 3, false, true}};
 globalStringInfo["PERIODICAURADAMAGESELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {3, playerName, 1, 2, 4, false, true}};
 globalStringInfo["SPELLLOGCRITOTHEROTHER"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {MSBTParser.DAMAGETYPE_PHYSICAL, 1, 3, 4, 2, true, false}};
 globalStringInfo["SPELLLOGCRITOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {MSBTParser.DAMAGETYPE_PHYSICAL, 1, playerName, 3, 2, true, false}};
 globalStringInfo["SPELLLOGCRITSCHOOLOTHEROTHER"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {5, 1, 3, 4, 2, true, false}};
 globalStringInfo["SPELLLOGCRITSCHOOLOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {4, 1, playerName, 3, 2, true, false}};
 globalStringInfo["SPELLLOGCRITSCHOOLSELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {4, playerName, 2, 3, 1, true, false}};
 globalStringInfo["SPELLLOGCRITSCHOOLSELFSELF"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {3, playerName, playerName, 2, 1, true, false}};
 globalStringInfo["SPELLLOGCRITSELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {MSBTParser.DAMAGETYPE_PHYSICAL, playerName, 2, 3, 1, true, false}};
 globalStringInfo["SPELLLOGOTHEROTHER"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {MSBTParser.DAMAGETYPE_PHYSICAL, 1, 3, 4, 2, false, false}};
 globalStringInfo["SPELLLOGOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {MSBTParser.DAMAGETYPE_PHYSICAL, 1, playerName, 3, 2, false, false}};
 globalStringInfo["SPELLLOGSCHOOLOTHEROTHER"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {5, 1, 3, 4, 2, false, false}};
 globalStringInfo["SPELLLOGSCHOOLOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {4, 1, playerName, 3, 2, false, false}};
 globalStringInfo["SPELLLOGSCHOOLSELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {4, playerName, 2, 3, 1, false, false}};
 globalStringInfo["SPELLLOGSCHOOLSELFSELF"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {3, playerName, playerName, 2, 1, false, false}};
 globalStringInfo["SPELLLOGSELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {MSBTParser.DAMAGETYPE_PHYSICAL, playerName, 2, 3, 1, false, false}};
 globalStringInfo["SPELLSPLITDAMAGEOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateDamageEvent, CaptureMap = {MSBTParser.DAMAGETYPE_PHYSICAL, 1, playerName, 3, 2, false, false}};

 -- Miss Event (MissType, RecipientName, SourceName, AbilityName).
 globalStringInfo["MISSEDOTHEROTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_MISS, 1, 2, nil}};
 globalStringInfo["MISSEDOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_MISS, 1, playerName, nil}};
 globalStringInfo["MISSEDSELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_MISS, playerName, 1, nil}};
 globalStringInfo["SPELLBLOCKEDOTHEROTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_BLOCK, 1, 3, 2}};
 globalStringInfo["SPELLBLOCKEDOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_BLOCK, 1, playerName, 2}};
 globalStringInfo["SPELLBLOCKEDSELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_BLOCK, playerName, 2, 1}};
 globalStringInfo["SPELLDODGEDOTHEROTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_DODGE, 1, 3, 2}};
 globalStringInfo["SPELLDODGEDOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_DODGE, 1, playerName, 2}};
 globalStringInfo["SPELLDODGEDSELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_DODGE, playerName, 2, 1}};
 globalStringInfo["SPELLEVADEDOTHEROTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_EVADE, 1, 3, 2}};
 globalStringInfo["SPELLEVADEDSELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_EVADE, playerName, 2, 1}};
 globalStringInfo["SPELLIMMUNEOTHEROTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_IMMUNE, 1, 3, 2}};
 globalStringInfo["SPELLIMMUNEOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_IMMUNE, 1, playerName, 2}};
 globalStringInfo["SPELLIMMUNESELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_IMMUNE, playerName, 2, 1}};
 globalStringInfo["SPELLINTERRUPTOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_INTERRUPT, 1, playerName, 2}};
 globalStringInfo["SPELLINTERRUPTSELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_INTERRUPT, playerName, 1, 2}};
 globalStringInfo["SPELLLOGABSORBOTHEROTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_ABSORB, 1, 3, 2}};
 globalStringInfo["SPELLLOGABSORBOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_ABSORB, 1, playerName, 2}};
 globalStringInfo["SPELLLOGABSORBSELF"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_ABSORB, 1, playerName, 1}};
 globalStringInfo["SPELLLOGABSORBSELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_ABSORB, playerName, 2, 1}};
 globalStringInfo["SPELLMISSOTHEROTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_MISS, 1, 3, 2}};
 globalStringInfo["SPELLMISSOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_MISS, 1, playerName, 2}};
 globalStringInfo["SPELLMISSSELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_MISS, playerName, 2, 1}};
 globalStringInfo["SPELLPARRIEDOTHEROTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_PARRY, 1, 3, 2}};
 globalStringInfo["SPELLPARRIEDOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_PARRY, 1, playerName, 2}};
 globalStringInfo["SPELLPARRIEDSELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_PARRY, playerName, 2, 1}};
 globalStringInfo["SPELLREFLECTOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_REFLECT, 1, playerName, 2}};
 globalStringInfo["SPELLREFLECTSELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_REFLECT, playerName, 2, 1}};
 globalStringInfo["SPELLRESISTOTHEROTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_RESIST, 1, 3, 2}};
 globalStringInfo["SPELLRESISTOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_RESIST, 1, playerName, 2}};
 globalStringInfo["SPELLRESISTSELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_RESIST, playerName, 2, 1}};
 globalStringInfo["VSABSORBOTHEROTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_ABSORB, 1, 2, nil}};
 globalStringInfo["VSABSORBOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_ABSORB, 1, playerName, nil}};
 globalStringInfo["VSABSORBSELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_ABSORB, playerName, 1, nil}};
 globalStringInfo["VSBLOCKOTHEROTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_BLOCK, 1, 2, nil}};
 globalStringInfo["VSBLOCKOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_BLOCK, 1, playerName, nil}};
 globalStringInfo["VSBLOCKSELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_BLOCK, playerName, 1, nil}};
 globalStringInfo["VSDODGEOTHEROTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_DODGE, 1, 2, nil}};
 globalStringInfo["VSDODGEOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_DODGE, 1, playerName, nil}};
 globalStringInfo["VSDODGESELFOTHER"] ={PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_DODGE, playerName, 1, nil}};
 globalStringInfo["VSEVADEOTHEROTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_EVADE, 1, 2, nil}};
 globalStringInfo["VSEVADESELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_EVADE, playerName, 1, nil}};
 globalStringInfo["VSIMMUNEOTHEROTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_IMMUNE, 1, 2, nil}};
 globalStringInfo["VSIMMUNEOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_IMMUNE, 1, playerName, nil}};
 globalStringInfo["VSIMMUNESELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_IMMUNE, playerName, 1, nil}};
 globalStringInfo["VSPARRYOTHEROTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_PARRY, 1, 2, nil}};
 globalStringInfo["VSPARRYOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_PARRY, 1, playerName, nil}};
 globalStringInfo["VSPARRYSELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateMissEvent, CaptureMap = {MSBTParser.MISSTYPE_PARRY, playerName, 1, nil}};

 -- Heal Event (SourceName, RecipientName, Amount, AbilityName, IsCrit, IsHoT).
 globalStringInfo["HEALEDCRITOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateHealEvent, CaptureMap = {1, playerName, 3, 2, true, false}};
 globalStringInfo["HEALEDCRITSELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateHealEvent, CaptureMap = {playerName, 2, 3, 1, true, false}};
 globalStringInfo["HEALEDCRITSELFSELF"] = {PopulateEventFunc = MSBTParser.PopulateHealEvent, CaptureMap = {playerName, playerName, 2, 1, true, false}};
 globalStringInfo["HEALEDOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateHealEvent, CaptureMap = {1, playerName, 3, 2, false, false}};
 globalStringInfo["HEALEDSELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateHealEvent, CaptureMap = {playerName, 2, 3, 1, false, false}};
 globalStringInfo["HEALEDSELFSELF"] = {PopulateEventFunc = MSBTParser.PopulateHealEvent, CaptureMap = {playerName, playerName, 2, 1, false, false}};
 globalStringInfo["PERIODICAURAHEALOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateHealEvent, CaptureMap = {2, playerName, 1, 3, false, true}};
 globalStringInfo["PERIODICAURAHEALSELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateHealEvent, CaptureMap = {playerName, 1, 2, 3, false, true}};
 globalStringInfo["PERIODICAURAHEALSELFSELF"] = {PopulateEventFunc = MSBTParser.PopulateHealEvent, CaptureMap = {playerName, playerName, 1, 2, false, true}};

 -- Notification Event (NotificationType, RecipientName, EffectName, Amount).
 globalStringInfo["AURAADDEDOTHERHARMFUL"] = {PopulateEventFunc = MSBTParser.PopulateNotificationEvent, CaptureMap = {MSBTParser.NOTIFICATIONTYPE_DEBUFF, 1, 2, nil}};
 globalStringInfo["AURAAPPLICATIONADDEDOTHERHARMFUL"] = {PopulateEventFunc = MSBTParser.PopulateNotificationEvent, CaptureMap = {MSBTParser.NOTIFICATIONTYPE_DEBUFF, 1, 2, 3}};
 globalStringInfo["AURAADDEDOTHERHELPFUL"] = {PopulateEventFunc = MSBTParser.PopulateNotificationEvent, CaptureMap = {MSBTParser.NOTIFICATIONTYPE_BUFF, 1, 2, nil}};
 globalStringInfo["AURAAPPLICATIONADDEDOTHERHELPFUL"] = {PopulateEventFunc = MSBTParser.PopulateNotificationEvent, CaptureMap = {MSBTParser.NOTIFICATIONTYPE_BUFF, 1, 2, 3}}; 
 globalStringInfo["COMBATLOG_HONORAWARD"] = {PopulateEventFunc = MSBTParser.PopulateNotificationEvent, CaptureMap = {MSBTParser.NOTIFICATIONTYPE_HONOR_GAIN, playerName, nil, 1}};
 globalStringInfo["COMBATLOG_HONORGAIN"] = {PopulateEventFunc = MSBTParser.PopulateNotificationEvent, CaptureMap = {MSBTParser.NOTIFICATIONTYPE_HONOR_GAIN, playerName, nil, 3}};
 globalStringInfo["COMBATLOG_XPGAIN_FIRSTPERSON"] = {PopulateEventFunc = MSBTParser.PopulateNotificationEvent, CaptureMap = {MSBTParser.NOTIFICATIONTYPE_EXPERIENCE_GAIN, playerName, nil, 2}};
 globalStringInfo["COMBATLOG_XPGAIN_FIRSTPERSON_UNNAMED"] = {PopulateEventFunc = MSBTParser.PopulateNotificationEvent, CaptureMap = {MSBTParser.NOTIFICATIONTYPE_EXPERIENCE_GAIN, playerName, nil, 1}};
 globalStringInfo["FACTION_STANDING_DECREASED"] = {PopulateEventFunc = MSBTParser.PopulateNotificationEvent, CaptureMap = {MSBTParser.NOTIFICATIONTYPE_REP_LOSS, playerName, 1, 2}};
 globalStringInfo["FACTION_STANDING_INCREASED"] =  {PopulateEventFunc = MSBTParser.PopulateNotificationEvent, CaptureMap = {MSBTParser.NOTIFICATIONTYPE_REP_GAIN, playerName, 1, 2}};
 globalStringInfo["LOOT_ITEM_CREATED_SELF"] = {PopulateEventFunc = MSBTParser.PopulateItemCreationEvent, CaptureMap = {1}};
 globalStringInfo["LOOT_ITEM_PUSHED_SELF"] = {PopulateEventFunc = MSBTParser.PopulateItemCreationEvent, CaptureMap = {1}};
 globalStringInfo["POWERGAINOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateNotificationEvent, CaptureMap = {MSBTParser.NOTIFICATIONTYPE_POWER_GAIN, playerName, 2, 1}};
 globalStringInfo["POWERGAINSELFSELF"] = {PopulateEventFunc = MSBTParser.PopulateNotificationEvent, CaptureMap = {MSBTParser.NOTIFICATIONTYPE_POWER_GAIN, playerName, 2, 1}};
 globalStringInfo["SELFKILLOTHER"] = {PopulateEventFunc = MSBTParser.PopulateKillingBlowEvent, CaptureMap = {1}};
 globalStringInfo["SKILL_RANK_UP"] = {PopulateEventFunc = MSBTParser.PopulateNotificationEvent, CaptureMap = {MSBTParser.NOTIFICATIONTYPE_SKILL_GAIN, playerName, 1, 2}};
 globalStringInfo["SPELLEXTRAATTACKSSELF"] = {PopulateEventFunc = MSBTParser.PopulateNotificationEvent, CaptureMap = {MSBTParser.NOTIFICATIONTYPE_EXTRA_ATTACK, playerName, 2, 1}};
 globalStringInfo["SPELLEXTRAATTACKSSELF_SINGULAR"] = {PopulateEventFunc = MSBTParser.PopulateNotificationEvent, CaptureMap = {MSBTParser.NOTIFICATIONTYPE_EXTRA_ATTACK, playerName, 2, 1}};
 globalStringInfo["SPELLPOWERDRAINOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateNotificationEvent, CaptureMap = {MSBTParser.NOTIFICATIONTYPE_POWER_LOSS, playerName, 4, 3}};
 globalStringInfo["SPELLPOWERLEECHOTHERSELF"] = {PopulateEventFunc = MSBTParser.PopulateNotificationEvent, CaptureMap = {MSBTParser.NOTIFICATIONTYPE_POWER_LOSS, playerName, 4, 3}};
 globalStringInfo["SPELLPOWERLEECHSELFOTHER"] = {PopulateEventFunc = MSBTParser.PopulateNotificationEvent, CaptureMap = {MSBTParser.NOTIFICATIONTYPE_POWER_GAIN, playerName, 3, 2}};

 -- Environmental Event (HazardType, DamageType, Amount).
 globalStringInfo["VSENVIRONMENTALDAMAGE_DROWNING_SELF"] = {PopulateEventFunc = MSBTParser.PopulateEnvironmentalEvent, CaptureMap = {MSBTParser.HAZARDTYPE_DROWNING, MSBTParser.DAMAGETYPE_NATURE, 1}};
 globalStringInfo["VSENVIRONMENTALDAMAGE_FALLING_SELF"] = {PopulateEventFunc = MSBTParser.PopulateEnvironmentalEvent, CaptureMap = {MSBTParser.HAZARDTYPE_FALLING, MSBTParser.DAMAGETYPE_PHYSICAL, 1}};
 globalStringInfo["VSENVIRONMENTALDAMAGE_FATIGUE_SELF"] = {PopulateEventFunc = MSBTParser.PopulateEnvironmentalEvent, CaptureMap = {MSBTParser.HAZARDTYPE_FATIGUE, MSBTParser.DAMAGETYPE_PHYSICAL, 1}};
 globalStringInfo["VSENVIRONMENTALDAMAGE_FIRE_SELF"] = {PopulateEventFunc = MSBTParser.PopulateEnvironmentalEvent, CaptureMap = {MSBTParser.HAZARDTYPE_FIRE, MSBTParser.DAMAGETYPE_FIRE, 1}};
 globalStringInfo["VSENVIRONMENTALDAMAGE_LAVA_SELF"] = {PopulateEventFunc = MSBTParser.PopulateEnvironmentalEvent, CaptureMap = {MSBTParser.HAZARDTYPE_LAVA, MSBTParser.DAMAGETYPE_FIRE, 1}};
 globalStringInfo["VSENVIRONMENTALDAMAGE_SLIME_SELF"] = {PopulateEventFunc = MSBTParser.PopulateEnvironmentalEvent, CaptureMap = {MSBTParser.HAZARDTYPE_SLIME, MSBTParser.DAMAGETYPE_NATURE, 1}};


 -- Print an error message for each global string that isn't found and remove it from the info map.
 for globalStringName in pairs(globalStringInfo) do
  if (not _G[globalStringName]) then
   MikSBT.Print("Unable to find global string: " .. globalStringName, 1, 0, 0);
   globalStringInfo[globalStringName] = nil;
  end
 end
end


-- **********************************************************************************
-- Finds the rarest word for each global string.
-- **********************************************************************************
function MSBTParser.FindGlobalStringRareWords()
 -- Hold the number of times each word appears in all the global strings.
 local wordCounts = {};

 -- Loop through all of the supported global strings.
 for globalStringName in pairs(globalStringInfo) do
  -- Strip out all of the formatting codes.
  local strippedGS = string.gsub(_G[globalStringName], "%%%d?%$?[sd]", "");

  -- Count how many times each word appears in the global string.
  for word in string.gmatch(strippedGS, "%w+") do
   wordCounts[word] = (wordCounts[word] or 0) + 1;
  end
 end


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

  -- Strip out all of the formatting codes.
  local strippedGS = string.gsub(_G[globalStringName], "%%%d?%$?[sd]", "");

  -- Find the rarest word in the global string.
  for word in string.gmatch(strippedGS, "%w+") do
   if (not leastSeen or wordCounts[word] < leastSeen) then
    leastSeen = wordCounts[word];
    rarestWord = word;
   end
  end

  -- Set the rarest word.
  globalStringInfo[globalStringName].RarestWord = rarestWord;
 end
end


-- **********************************************************************************
-- Converts the passed global string into a lua search pattern with a capture order
-- table and stores the results so any requests to convert the same global string
-- will just return the cached one.
-- **********************************************************************************
function MSBTParser.ConvertGlobalString(globalStringName)
 -- Check if the passed global string does not exist.
 local globalString = getglobal(globalStringName);
 if (globalString == nil) then
  return;
 end

 -- Return the cached conversion if it has already been converted.
 if (globalStringInfo[globalStringName] and globalStringInfo[globalStringName].SearchPattern) then
  return globalStringInfo[globalStringName].SearchPattern, globalStringInfo[globalStringName].CaptureOrder;
 end

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

 -- Escape lua magic chars.
 local searchPattern = string.gsub(globalString, "([%^%(%)%.%[%]%*%+%-%?])", "%%%1");

 -- Loop through each capture and setup the capture order.
 for captureIndex in string.gmatch(searchPattern, "%%(%d)%$[sd]") do
  if (not captureOrder) then captureOrder = {}; end
  numCaptures = numCaptures + 1;
  captureOrder[tonumber(captureIndex)] = numCaptures;
 end
 
 -- Convert %1$s / %s to (.+) and %1$d / %d to (%d+).
 searchPattern = string.gsub(searchPattern, "%%%d?%$?s", "(.-)"); -- Do these AFTER escaping the magic characters.
 searchPattern = string.gsub(searchPattern, "%%%d?%$?d", "(%%d+)");

 -- Escape any remaining $ chars.
 searchPattern = string.gsub(searchPattern, "%$", "%%$");

 -- If the pattern ends with (.-), replace it with (.+), or the capsule will be lost.
 if searchPattern:sub(-4) == "(.-)" then
  searchPattern = searchPattern:sub(0, -5) .. "(.+)"
 end
	
 -- If no entry exists for the global string name, create one.
 if (not globalStringInfo[globalStringName]) then globalStringInfo[globalStringName] = {}; end

 -- Cache the converted pattern and capture order.
 globalStringInfo[globalStringName].SearchPattern = searchPattern;
 globalStringInfo[globalStringName].CaptureOrder = captureOrder;

 -- Return the converted global string.
 return searchPattern, captureOrder;
end


-- **********************************************************************************
-- Converts all of the supported global strings.
-- **********************************************************************************
function MSBTParser.ConvertGlobalStrings()
 -- Loop through all of the global strings.
 for globalStringName in pairs(globalStringInfo) do
  -- Get the global string converted to a lua search pattern.
  local searchPattern = MSBTParser.ConvertGlobalString(globalStringName);

  -- Prepend an anchor to speed up searching.
  globalStringInfo[globalStringName].SearchPattern = "^" .. searchPattern;
 end

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


-- **********************************************************************************
-- Fills in the unordered capture table with the captured data.
-- **********************************************************************************
function MSBTParser.GetUnorderedCaptures(matchStart, matchEnd, c1, c2, c3, c4, c5, c6, c7, c8, c9)
 -- Check if a match was found.
 if (matchStart) then
  unorderedCaptures[1] = c1;
  unorderedCaptures[2] = c2;
  unorderedCaptures[3] = c3;
  unorderedCaptures[4] = c4;
  unorderedCaptures[5] = c5;
  unorderedCaptures[6] = c6;
  unorderedCaptures[7] = c7;
  unorderedCaptures[8] = c8;
  unorderedCaptures[9] = c9;

  -- Return the populated unordered captures table.
  return matchEnd, unorderedCaptures;
 end

 -- Don't return anything since no match was found.
 return nil;
end


-- **********************************************************************************
-- Gets the damage type number for the given string.
-- **********************************************************************************
function MSBTParser.GetDamageTypeNumber(damageTypeString)
 -- Return the correct damage type number for the passed string.
 if (damageTypeString == SPELL_SCHOOL0_CAP) then
  return MSBTParser.DAMAGETYPE_PHYSICAL;
 elseif (damageTypeString == SPELL_SCHOOL1_CAP) then
  return MSBTParser.DAMAGETYPE_HOLY;
 elseif (damageTypeString == SPELL_SCHOOL2_CAP) then
  return MSBTParser.DAMAGETYPE_FIRE;
 elseif (damageTypeString == SPELL_SCHOOL3_CAP) then
  return MSBTParser.DAMAGETYPE_NATURE;
 elseif (damageTypeString == SPELL_SCHOOL4_CAP) then
  return MSBTParser.DAMAGETYPE_FROST;
 elseif (damageTypeString == SPELL_SCHOOL5_CAP) then
  return MSBTParser.DAMAGETYPE_SHADOW;
 elseif (damageTypeString == SPELL_SCHOOL6_CAP) then
  return MSBTParser.DAMAGETYPE_ARCANE;
 end

 -- Return the unknown damage type.
 return MSBTParser.DAMAGETYPE_UNKNOWN;
end


-- **********************************************************************************
-- Get a string for the passed damage type.
-- **********************************************************************************
function MSBTParser.GetDamageTypeString(damageType)
 -- Return the correct damage type string for the passed number.
 if (damageType == MSBTParser.DAMAGETYPE_PHYSICAL) then
  return SPELL_SCHOOL0_CAP;
 elseif (damageType == MSBTParser.DAMAGETYPE_HOLY) then
  return SPELL_SCHOOL1_CAP;
 elseif (damageType == MSBTParser.DAMAGETYPE_FIRE) then
  return SPELL_SCHOOL2_CAP;
 elseif (damageType == MSBTParser.DAMAGETYPE_NATURE) then
  return SPELL_SCHOOL3_CAP;
 elseif (damageType == MSBTParser.DAMAGETYPE_FROST) then
  return SPELL_SCHOOL4_CAP;
 elseif (damageType == MSBTParser.DAMAGETYPE_SHADOW) then
  return SPELL_SCHOOL5_CAP;
 elseif (damageType == MSBTParser.DAMAGETYPE_ARCANE) then
  return SPELL_SCHOOL6_CAP;
 end

 -- Return the unknown damage type string.
 return UNKNOWN;
end


-- **********************************************************************************
-- Populates the parser event data table for a damage event with the passed info.
-- **********************************************************************************
function MSBTParser.PopulateDamageEvent(captureMap, capturedData, captureOrder)
 -- Attempt to get the damage type and convert it to a number if there is one.
 local damageType = capturedData[(captureOrder and captureOrder[captureMap[1]]) or captureMap[1]] or captureMap[1];
 if (type(damageType) == "string") then
  damageType = MSBTParser.GetDamageTypeNumber(damageType);
 end

 parserEvent.EventType = MSBTParser.EVENTTYPE_DAMAGE;
 parserEvent.DamageType = damageType;
 parserEvent.SourceName = capturedData[(captureOrder and captureOrder[captureMap[2]]) or captureMap[2]] or captureMap[2];
 parserEvent.RecipientName = capturedData[(captureOrder and captureOrder[captureMap[3]]) or captureMap[3]] or captureMap[3];
 parserEvent.Amount = tonumber(capturedData[(captureOrder and captureOrder[captureMap[4]]) or captureMap[4]] or captureMap[4]);
 parserEvent.AbilityName = capturedData[(captureOrder and captureOrder[captureMap[5]]) or captureMap[5]] or captureMap[5];
 parserEvent.IsCrit = captureMap[6];
 parserEvent.IsDoT = captureMap[7];
end


-- **********************************************************************************
-- Populates the parser event data table for a miss event with the passed info.
-- **********************************************************************************
function MSBTParser.PopulateMissEvent(captureMap, capturedData, captureOrder)
 parserEvent.EventType = MSBTParser.EVENTTYPE_MISS;
 parserEvent.MissType = captureMap[1];
 parserEvent.SourceName = capturedData[(captureOrder and captureOrder[captureMap[2]]) or captureMap[2]] or captureMap[2];
 parserEvent.RecipientName = capturedData[(captureOrder and captureOrder[captureMap[3]]) or captureMap[3]] or captureMap[3];
 parserEvent.AbilityName = capturedData[(captureOrder and captureOrder[captureMap[4]]) or captureMap[4]] or captureMap[4];
end


-- **********************************************************************************
-- Populates the parser event data table for a miss event with the passed info.
-- **********************************************************************************
function MSBTParser.PopulateHealEvent(captureMap, capturedData, captureOrder)
 parserEvent.EventType = MSBTParser.EVENTTYPE_HEAL;
 parserEvent.SourceName = capturedData[(captureOrder and captureOrder[captureMap[1]]) or captureMap[1]] or captureMap[1];
 parserEvent.RecipientName = capturedData[(captureOrder and captureOrder[captureMap[2]]) or captureMap[2]] or captureMap[2];
 parserEvent.Amount = tonumber(capturedData[(captureOrder and captureOrder[captureMap[3]]) or captureMap[3]] or captureMap[3]);
 parserEvent.AbilityName = capturedData[(captureOrder and captureOrder[captureMap[4]]) or captureMap[4]] or captureMap[4];
 parserEvent.IsCrit = captureMap[5];
 parserEvent.IsHoT = captureMap[6];


 -- Get the unit ID for the healed unit.
 local unitID = MSBTParser.GetUnitIDFromName(parserEvent.RecipientName);

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

  -- Check if any overhealing occured.
  if (overhealAmount > 0) then
   parserEvent.OverhealAmount = overhealAmount;
  end
 end
end


-- **********************************************************************************
-- Populates the parser event data table for a notification  event with the passed
-- info.
-- **********************************************************************************
function MSBTParser.PopulateNotificationEvent(captureMap, capturedData, captureOrder)
 parserEvent.EventType = MSBTParser.EVENTTYPE_NOTIFICATION;
 parserEvent.NotificationType = captureMap[1];
 parserEvent.RecipientName = capturedData[(captureOrder and captureOrder[captureMap[2]]) or captureMap[2]] or captureMap[2];
 parserEvent.EffectName = capturedData[(captureOrder and captureOrder[captureMap[3]]) or captureMap[3]] or captureMap[3];
 parserEvent.Amount = tonumber(capturedData[(captureOrder and captureOrder[captureMap[4]]) or captureMap[4]] or captureMap[4]);
end


-- **********************************************************************************
-- Populates the parser event data table for a PC or NPC killing blow.
-- **********************************************************************************
function MSBTParser.PopulateKillingBlowEvent(captureMap, capturedData, captureOrder)
 -- Get the name of the slain enemy.
 local enemyName = capturedData[(captureOrder and captureOrder[captureMap[1]]) or captureMap[1]] or captureMap[1];

 parserEvent.EventType = MSBTParser.EVENTTYPE_NOTIFICATION;
 parserEvent.NotificationType = MSBTParser.NOTIFICATIONTYPE_NPC_KILLING_BLOW;
 parserEvent.RecipientName = playerName;
 parserEvent.EffectName = enemyName;

 -- Check if the current target is the slain enemy and is a player, or the slain target is on the recently
 -- selected players list.
 if ((UnitExists("target") and (UnitName("target") == enemyName) and UnitIsPlayer("target")) or
     (recentlySelectedPlayers[enemyName] ~= nil)) then
  -- Change the notification type to a PC killing blow and clear the slain player from the list.
  parserEvent.NotificationType = MSBTParser.NOTIFICATIONTYPE_PC_KILLING_BLOW;
  recentlySelectedPlayers[enemyName] = nil;
 end
end


-- **********************************************************************************
-- Populates the parser event data table for specific item creation events.
-- **********************************************************************************
function MSBTParser.PopulateItemCreationEvent(captureMap, capturedData, captureOrder)
 -- 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 matchStart, _, itemID, itemName = string.find(itemLink, "item:(%d+):[%d:]+|h%[(.+)%]");
 if (matchStart and (tonumber(itemID) == 6265)) then
  parserEvent.EventType = MSBTParser.EVENTTYPE_NOTIFICATION;
  parserEvent.NotificationType = MSBTParser.NOTIFICATIONTYPE_SOUL_SHARD_CREATED;
  parserEvent.RecipientName = playerName;
  parserEvent.EffectName = itemName;
 end
end


-- **********************************************************************************
-- Populates the parser event data table for an environmental event with the passed
-- info.
-- **********************************************************************************
function MSBTParser.PopulateEnvironmentalEvent(captureMap, capturedData, captureOrder)
 parserEvent.EventType = MSBTParser.EVENTTYPE_ENVIRONMENTAL;
 parserEvent.HazardType = captureMap[1];
 parserEvent.DamageType = captureMap[2];
 parserEvent.Amount = tonumber(capturedData[(captureOrder and captureOrder[captureMap[3]]) or captureMap[3]] or captureMap[3]);
end


-- **********************************************************************************
-- Gets a unit id for the name.
-- **********************************************************************************
function MSBTParser.GetUnitIDFromName(uName)
 local unitID;

 -- Check if the name is the player.
 if (uName == playerName) then
  unitID = "player";

 -- Check if the name is one of the player's raid or party members.
 else
  -- Loop through all of the raid members.
  local numRaidMembers = GetNumRaidMembers();
  for i = 1, numRaidMembers do
   if (uName == UnitName("raid" .. i)) then
    unitID = "raid" .. i;
   end
  end

  -- Check if the unit ID was not already found.
  if (not unitID) then
   -- Loop through all of the party members.
   local numPartyMembers = GetNumPartyMembers();
   for i = 1, numPartyMembers do
    if (uName == UnitName("party" .. i)) then
     unitID = "party" .. i;
    end
   end
  end -- unit ID not already found.
 end

 -- Return the unit id.
 return unitID;
end


-- **********************************************************************************
-- Disables the parsing.
-- **********************************************************************************
function MSBTParser.Disable()
 -- Unregister all of the events.
 MSBTParser.UnregisterEvents();

 -- Stop receiving updates.
 eventFrame:Hide();

 -- Clear the recently selected players list.
 MSBTTableRecycler.EraseTable(recentlySelectedPlayers);
end


-- **********************************************************************************
-- Enables the parsing.
-- **********************************************************************************
function MSBTParser.Enable()
 MSBTParser.RegisterEvents();
 
 -- Start receiving updates.
 eventFrame:Show();
end


-------------------------------------------------------------------------------------
-- Load.
-------------------------------------------------------------------------------------

MSBTParser.OnLoad();