-------------------------------------------------------------------------------------
-- Title: MSBT Triggers
-- Author: Mik
-------------------------------------------------------------------------------------

-- Create module namespace.
MSBTTriggers = {};


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

-- Main Trigger Conditions.
MSBTTriggers.MAINCONDITIONTYPE_SELF_HEALTH		= "SelfHealth";
MSBTTriggers.MAINCONDITIONTYPE_SELF_MANA			= "SelfMana";
MSBTTriggers.MAINCONDITIONTYPE_PET_HEALTH			= "PetHealth";
MSBTTriggers.MAINCONDITIONTYPE_ENEMY_HEALTH		= "EnemyHealth";
MSBTTriggers.MAINCONDITIONTYPE_FRIENDLY_HEALTH		= "FriendlyHealth";
MSBTTriggers.MAINCONDITIONTYPE_INCOMING_CRIT		= "IncomingCrit";
MSBTTriggers.MAINCONDITIONTYPE_INCOMING_BLOCK		= "IncomingBlock";
MSBTTriggers.MAINCONDITIONTYPE_INCOMING_DODGE		= "IncomingDodge";
MSBTTriggers.MAINCONDITIONTYPE_INCOMING_PARRY		= "IncomingParry";
MSBTTriggers.MAINCONDITIONTYPE_OUTGOING_CRIT		= "OutgoingCrit";
MSBTTriggers.MAINCONDITIONTYPE_OUTGOING_BLOCK		= "OutgoingBlock";
MSBTTriggers.MAINCONDITIONTYPE_OUTGOING_DODGE		= "OutgoingDodge";
MSBTTriggers.MAINCONDITIONTYPE_OUTGOING_PARRY		= "OutgoingParry";
MSBTTriggers.MAINCONDITIONTYPE_SELF_BUFF_GAIN		= "SelfBuff";
MSBTTriggers.MAINCONDITIONTYPE_SELF_DEBUFF_GAIN		= "SelfDebuff";
MSBTTriggers.MAINCONDITIONTYPE_TARGET_BUFF_GAIN		= "TargetBuff";
MSBTTriggers.MAINCONDITIONTYPE_TARGET_DEBUFF_GAIN	= "TargetDebuff";
MSBTTriggers.MAINCONDITIONTYPE_TARGET_DEBUFF_APP	= "TargetDebuffApplication";
MSBTTriggers.MAINCONDITIONTYPE_SEARCH_PATTERN		= "SearchPattern";

-- Secondary Trigger Conditions.
MSBTTriggers.SECONDARYCONDITIONTYPE_SPELL_READY		= "SpellReady";
MSBTTriggers.SECONDARYCONDITIONTYPE_BUFF_INACTIVE	= "BuffInactive";
MSBTTriggers.SECONDARYCONDITIONTYPE_MINIMUM_POWER	= "MinPower";
MSBTTriggers.SECONDARYCONDITIONTYPE_WARRIOR_STANCE	= "WarriorStance";
MSBTTriggers.SECONDARYCONDITIONTYPE_TRIGGER_COOLDOWN	= "TriggerCooldown";


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

-- Holds dynamically created frame for receiving events.
local eventFrame;

-- Holds all of the supported trigger search pattern events.
local supportedSearchPatternEvents;

-- Holds the search pattern for event searching mode.
local searchModePattern;

-- Holds the player's name and class.
local playerName, playerClass;

-- Table to cache spell ID's that are checked for readiness. 
local spellIDCache = {};

-- Table to hold the last time triggers with a cooldown condition were fired.
local cooldownTimes = {};

-- Table to hold triggers in a format optimized for searching.
local categorizedTriggers = {};

-- Holds previous health and mana values.
local lastSelfHealthPercentage = 0;
local lastSelfManaPercentage = 0;
local lastPetHealthPercentage = 0;
local lastEnemyHealthPercentage = 0;
local lastFriendlyHealthPercentage = 0;

-- Table to hold the trigger event data.
local triggerEventData = {};

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


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

-- **********************************************************************************
-- Called when the helper is loaded.
-- **********************************************************************************
function MSBTTriggers.OnLoad()
 -- Get the player's name and class.
 playerName = UnitName("player");
 _, playerClass = UnitClass("player");

 -- Create a frame to receive events.
 eventFrame = CreateFrame("FRAME");
 eventFrame:SetScript("OnEvent", MSBTTriggers.OnEvent);
end


-- **********************************************************************************
-- Called when the events the helper registered for occur.
-- **********************************************************************************
function MSBTTriggers.OnEvent()
 -- Health changes.
 if (event == "UNIT_HEALTH") then
  if (arg1 == "player") then
   lastSelfHealthPercentage = MSBTTriggers.FireHealthTriggers("player", categorizedTriggers[MSBTTriggers.MAINCONDITIONTYPE_SELF_HEALTH], lastSelfHealthPercentage);
  elseif (arg1 == "target") then
   -- Check if the target is an enemy.
   if (not UnitIsFriend("player", "target")) then
    lastEnemyHealthPercentage = MSBTTriggers.FireHealthTriggers("target", categorizedTriggers[MSBTTriggers.MAINCONDITIONTYPE_ENEMY_HEALTH], lastEnemyHealthPercentage);
   -- Target is not an enemy.
   else
    lastFriendlyHealthPercentage = MSBTTriggers.FireHealthTriggers("target", categorizedTriggers[MSBTTriggers.MAINCONDITIONTYPE_FRIENDLY_HEALTH], lastFriendlyHealthPercentage);
   end
  elseif (arg1 == "pet") then
    lastPetHealthPercentage = MSBTTriggers.FireHealthTriggers("pet", categorizedTriggers[MSBTTriggers.MAINCONDITIONTYPE_PET_HEALTH], lastPetHealthPercentage);
  end

 -- Mana changes.
 elseif (event == "UNIT_MANA") then
  if (arg1 == "player") then
   lastSelfManaPercentage = MSBTTriggers.FireManaTriggers("player", categorizedTriggers[MSBTTriggers.MAINCONDITIONTYPE_SELF_MANA], lastSelfManaPercentage);
  end

 -- Chat message combat events.
 else
  MSBTTriggers.ParseSearchPatternTriggers(event, arg1);

 end
end


-------------------------------------------------------------------------------------
-- Trigger utility functions.
-------------------------------------------------------------------------------------

-- **********************************************************************************
-- Gets up a list of all of the supported trigger search pattern events.
-- **********************************************************************************
function MSBTTriggers.GetSupportedSearchPatternEvents()
 -- Check if the supported search pattern events table hasn't been created yet.
 if (not supportedSearchPatternEvents) then
  supportedSearchPatternEvents = {
   "CHAT_MSG_COMBAT_CREATURE_VS_PARTY_HITS",
   "CHAT_MSG_COMBAT_CREATURE_VS_PARTY_MISSES",
   "CHAT_MSG_COMBAT_CREATURE_VS_SELF_HITS",
   "CHAT_MSG_COMBAT_CREATURE_VS_SELF_MISSES",
   "CHAT_MSG_COMBAT_FACTION_CHANGE",
   "CHAT_MSG_COMBAT_FRIENDLY_DEATH",
   "CHAT_MSG_COMBAT_FRIENDLYPLAYER_HITS",
   "CHAT_MSG_COMBAT_FRIENDLYPLAYER_MISSES",
   "CHAT_MSG_COMBAT_HONOR_GAIN",
   "CHAT_MSG_COMBAT_HOSTILE_DEATH",
   "CHAT_MSG_COMBAT_HOSTILEPLAYER_HITS",
   "CHAT_MSG_COMBAT_HOSTILEPLAYER_MISSES",
   "CHAT_MSG_COMBAT_PARTY_HITS",
   "CHAT_MSG_COMBAT_PARTY_MISSES",
   "CHAT_MSG_COMBAT_PET_HITS",
   "CHAT_MSG_COMBAT_PET_MISSES",
   "CHAT_MSG_COMBAT_SELF_HITS",
   "CHAT_MSG_COMBAT_SELF_MISSES",
   "CHAT_MSG_COMBAT_XP_GAIN",
   "CHAT_MSG_SKILL",
   "CHAT_MSG_SPELL_AURA_GONE_PARTY",
   "CHAT_MSG_SPELL_AURA_GONE_OTHER",
   "CHAT_MSG_SPELL_AURA_GONE_SELF",
   "CHAT_MSG_SPELL_CREATURE_VS_PARTY_DAMAGE",
   "CHAT_MSG_SPELL_CREATURE_VS_PARTY_BUFF",
   "CHAT_MSG_SPELL_CREATURE_VS_SELF_BUFF",
   "CHAT_MSG_SPELL_CREATURE_VS_SELF_DAMAGE",
   "CHAT_MSG_SPELL_DAMAGESHIELDS_ON_OTHERS",
   "CHAT_MSG_SPELL_DAMAGESHIELDS_ON_SELF",
   "CHAT_MSG_SPELL_FRIENDLYPLAYER_BUFF",
   "CHAT_MSG_SPELL_FRIENDLYPLAYER_DAMAGE",
   "CHAT_MSG_SPELL_HOSTILEPLAYER_BUFF",
   "CHAT_MSG_SPELL_HOSTILEPLAYER_DAMAGE",
   "CHAT_MSG_SPELL_ITEM_ENCHANTMENTS",
   "CHAT_MSG_SPELL_PARTY_BUFF",
   "CHAT_MSG_SPELL_PARTY_DAMAGE",
   "CHAT_MSG_SPELL_PERIODIC_CREATURE_BUFFS",
   "CHAT_MSG_SPELL_PERIODIC_CREATURE_DAMAGE",
   "CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS",
   "CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_DAMAGE",
   "CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_BUFFS",
   "CHAT_MSG_SPELL_PERIODIC_HOSTILEPLAYER_DAMAGE",
   "CHAT_MSG_SPELL_PERIODIC_PARTY_BUFFS",
   "CHAT_MSG_SPELL_PERIODIC_PARTY_DAMAGE",
   "CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS",
   "CHAT_MSG_SPELL_PERIODIC_SELF_DAMAGE",
   "CHAT_MSG_SPELL_SELF_BUFF",
   "CHAT_MSG_SPELL_SELF_DAMAGE",
   "CHAT_MSG_SPELL_PET_DAMAGE",
   "CHAT_MSG_SPELL_PET_BUFF",
  }
 end

 -- Return the supported search pattern events.
 return supportedSearchPatternEvents;
end


-- **********************************************************************************
-- Enables event searching mode.  The passed pattern will be used to only show event
-- types where the combat message contains the pattern.
-- **********************************************************************************
function MSBTTriggers.EnableEventSearching(pattern)
 -- Set the event searching mode search pattern to the passed pattern.
 searchModePattern = pattern;

 -- Register extra events for broader searching.
 local searchPatternEvents = MSBTTriggers.GetSupportedSearchPatternEvents();
 for _, event in ipairs(searchPatternEvents) do
  eventFrame:RegisterEvent(event);
 end
end


-- **********************************************************************************
-- Disables event searching mode.
-- **********************************************************************************
function MSBTTriggers.DisableEventSearching()
 -- Clear the event searching mode search pattern.
 searchModePattern = nil;

 -- Unregister the extra events we registered for broader searching if they aren't in use by a trigger.
 local searchPatternEvents = MSBTTriggers.GetSupportedSearchPatternEvents();
 local searchPatternTriggers = categorizedTriggers[MSBTTriggers.MAINCONDITIONTYPE_SEARCH_PATTERN];
 for _, event in pairs(searchPatternEvents) do
  -- Make sure it isn't in use by a trigger.
  if (not (searchPatternTriggers and searchPatternTriggers[event])) then
   eventFrame:UnregisterEvent(event);
  end
 end
end


-- **********************************************************************************
-- Registers the passed trigger if it is not disabled and it applies to the 
-- current player's class.  Also registers the appropriate events for the trigger
-- with the events frame so that the only events that are received are those that
-- are needed by the triggers that are in use.
-- **********************************************************************************
function MSBTTriggers.RegisterTrigger(triggerKey)
 -- Get the trigger data for the key.
 local triggerData = MSBTProfiles.CurrentProfile.Triggers[triggerKey];

 -- Don't register the trigger if it is disabled. 
 if (triggerData.EventSettings.Disabled) then
  return;
 end

 -- Don't register the trigger if it is not for the current class 
 local triggerSettings = triggerData.TriggerSettings;
 if (triggerSettings.Classes and not triggerSettings.Classes[playerClass]) then
   return;
 end 

 -- Holds a table of search patterns for the trigger if there are any.
 local tempSearchPatterns;

 -- Loop through the main conditions for the trigger.
 local conditionParam;
 for conditionNum, mainConditionType in pairs(triggerSettings.MainConditions) do
  -- Get the parameter for the condition.
  conditionParam = (triggerSettings.MainConditionParams and triggerSettings.MainConditionParams[conditionNum]) or nil;

  -- Create a table to hold an array of the triggers for the main condition type if there isn't
  -- already one for it. 
  if (not categorizedTriggers[mainConditionType]) then
   categorizedTriggers[mainConditionType] = {};
  end

  -- Search Pattern
  if (mainConditionType == MSBTTriggers.MAINCONDITIONTYPE_SEARCH_PATTERN) then
   -- Make sure there is a condition
   if (conditionParam and conditionParam ~= "") then
    -- Create a table to hold the search patterns if it hasn't already been done.
    if (not tempSearchPatterns) then
     tempSearchPatterns = {};
    end

    -- Add the search pattern to the temp search patterns table.
    tempSearchPatterns[#tempSearchPatterns + 1] = conditionParam;
   end

  -- Not a search pattern main condition.
  else
   -- Categorize the triggers by their main condition types so time is not wasted checking for triggers that don't apply.
   categorizedTriggers[mainConditionType][triggerKey] = conditionParam or triggerKey;

   -- Self Health.
   if (mainConditionType == MSBTTriggers.MAINCONDITIONTYPE_SELF_HEALTH) then
    eventFrame:RegisterEvent("UNIT_HEALTH");

   -- Self Mana.
   elseif (mainConditionType == MSBTTriggers.MAINCONDITIONTYPE_SELF_MANA) then
    eventFrame:RegisterEvent("UNIT_MANA");

   -- Pet Health.
   elseif (mainConditionType == MSBTTriggers.MAINCONDITIONTYPE_PET_HEALTH) then
    eventFrame:RegisterEvent("UNIT_HEALTH");

   -- Enemy Health.
   elseif (mainConditionType == MSBTTriggers.MAINCONDITIONTYPE_ENEMY_HEALTH) then
    eventFrame:RegisterEvent("UNIT_HEALTH");

   -- Friendly Health.
   elseif (mainConditionType == MSBTTriggers.MAINCONDITIONTYPE_FRIENDLY_HEALTH) then
    eventFrame:RegisterEvent("UNIT_HEALTH");
   end
  end
 end  -- Main Conditions Loop.

 -- Categorize the search patterns by their event types and trigger key.
 if (triggerSettings.TriggerEvents and tempSearchPatterns) then
  for _, triggerEvent in pairs(triggerSettings.TriggerEvents) do
   -- Create a table to hold the triggers categorized by event type if there isn't already one for it.
   if (not categorizedTriggers[MSBTTriggers.MAINCONDITIONTYPE_SEARCH_PATTERN][triggerEvent]) then
    categorizedTriggers[MSBTTriggers.MAINCONDITIONTYPE_SEARCH_PATTERN][triggerEvent] = {};
   end

   categorizedTriggers[MSBTTriggers.MAINCONDITIONTYPE_SEARCH_PATTERN][triggerEvent][triggerKey] = tempSearchPatterns;
   eventFrame:RegisterEvent(triggerEvent);
  end
 end  
end


-- **********************************************************************************
-- Registers all of the triggers.
-- **********************************************************************************
function MSBTTriggers.RegisterAllTriggers()
 -- Unregister any triggers that are already registered.
 MSBTTriggers.UnregisterAllTriggers();

 -- Loop through all of the triggers and try to register them.
 for triggerKey in pairs(MSBTProfiles.CurrentProfile.Triggers) do
  MSBTTriggers.RegisterTrigger(triggerKey);
 end
end


-- **********************************************************************************
-- Unregisters all of the triggers.
-- **********************************************************************************
function MSBTTriggers.UnregisterAllTriggers()
 -- Unregister all of the events from the event frame.
 eventFrame:UnregisterAllEvents();

 -- Loop through all of the categorized trigger arrays and erase them.
 for conditionType in pairs(categorizedTriggers) do
  MSBTTableRecycler.EraseTable(categorizedTriggers[conditionType]);
 end
end


-- **********************************************************************************
-- Returns the spell id for the passed spell name if it is in the spell book.
-- Returns 0 is the spell wasn't found.
-- **********************************************************************************
function MSBTTriggers.GetSpellID(spellName)
 -- Get the total number of spells in the spell book.
 local _, _, offset, numSpells = GetSpellTabInfo(GetNumSpellTabs());
 local totalSpells = offset + numSpells;

 -- Loop through all of the spells looking for the passed spell name and return the spell
 -- ID if it's found.
 for i = 1, totalSpells do
  if (spellName == GetSpellName(i, BOOKTYPE_SPELL)) then
   return i;
  end
 end

 -- Return 0 if the spell wasn't found.
 return 0;
end


-- **********************************************************************************
-- Fills in the unordered capture table with the captured data.
-- **********************************************************************************
function MSBTTriggers.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 unorderedCaptures;
 end

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


-- **********************************************************************************
-- Populates the trigger event data table using the passed info
-- **********************************************************************************
function MSBTTriggers.PopulateTriggerEventData(triggerKey, ...)
 -- Erase the trigger event data table.
 MSBTTableRecycler.EraseTable(triggerEventData);

 -- Populate the trigger key.
 triggerEventData.TriggerKey = triggerKey;

 -- Get the number of arguments and populate the trigger event data with them.
 local numArgs = select("#", ...);
 if (numArgs) then
  triggerEventData.NumCaptures = numArgs;
  for i = 1, numArgs do
   triggerEventData["CapturedData" .. i] = select(i, ...);
  end
 end
end


-- **********************************************************************************
-- Sends the trigger to MSBT's trigger events handler function.
-- **********************************************************************************
function MSBTTriggers.SendTriggerEvent()
 -- Send to MSBT's trigger handler.
 MikSBT.TriggerHandler(triggerEventData);
end



-------------------------------------------------------------------------------------
-- Trigger condition functions.
-------------------------------------------------------------------------------------

-- **********************************************************************************
-- Handle parser events.
-- **********************************************************************************
function MSBTTriggers.ParserEventsHandler(parserEvent)
 -- Check if the event is a critical hit.
 if (parserEvent.EventType == MSBTParser.EVENTTYPE_DAMAGE and parserEvent.IsCrit) then
  -- Check if the event is incoming.
  if (parserEvent.RecipientName == playerName) then
   MSBTTriggers.FireTriggers(categorizedTriggers[MSBTTriggers.MAINCONDITIONTYPE_INCOMING_CRIT]);
  -- Event is outgoing.
  elseif (parserEvent.SourceName == playerName) then
   MSBTTriggers.FireTriggers(categorizedTriggers[MSBTTriggers.MAINCONDITIONTYPE_OUTGOING_CRIT]);
  end

 -- Check if the event is a miss.
 elseif (parserEvent.EventType == MSBTParser.EVENTTYPE_MISS) then
  -- Block.
  if (parserEvent.MissType == MSBTParser.MISSTYPE_BLOCK) then
   -- Check if the event is incoming.
   if (parserEvent.RecipientName == playerName) then
    MSBTTriggers.FireTriggers(categorizedTriggers[MSBTTriggers.MAINCONDITIONTYPE_INCOMING_BLOCK]);
   -- Event is outgoing.
   elseif (parserEvent.SourceName == playerName) then
    MSBTTriggers.FireTriggers(categorizedTriggers[MSBTTriggers.MAINCONDITIONTYPE_OUTGOING_BLOCK]);
   end

  -- Dodge.
  elseif (parserEvent.MissType == MSBTParser.MISSTYPE_DODGE) then
   -- Check if the event is incoming.
   if (parserEvent.RecipientName == playerName) then
    MSBTTriggers.FireTriggers(categorizedTriggers[MSBTTriggers.MAINCONDITIONTYPE_INCOMING_DODGE]);
   -- Event is outgoing.
   elseif (parserEvent.SourceName == playerName) then
    MSBTTriggers.FireTriggers(categorizedTriggers[MSBTTriggers.MAINCONDITIONTYPE_OUTGOING_DODGE]);
   end

  -- Parry.
  elseif (parserEvent.MissType == MSBTParser.MISSTYPE_PARRY) then
   -- Check if the event is incoming.
   if (parserEvent.RecipientName == playerName) then
    MSBTTriggers.FireTriggers(categorizedTriggers[MSBTTriggers.MAINCONDITIONTYPE_INCOMING_PARRY]);
   -- Event is outgoing.
   elseif (parserEvent.SourceName == playerName) then
    MSBTTriggers.FireTriggers(categorizedTriggers[MSBTTriggers.MAINCONDITIONTYPE_OUTGOING_PARRY]);
   end
  end

 -- Check if the event is a notification.
 elseif (parserEvent.EventType == MSBTParser.EVENTTYPE_NOTIFICATION) then
  -- The notification is for the player.
  if (parserEvent.RecipientName == playerName) then
   -- Debuff gain.
   if (parserEvent.NotificationType == MSBTParser.NOTIFICATIONTYPE_DEBUFF) then
    MSBTTriggers.FireTriggers(categorizedTriggers[MSBTTriggers.MAINCONDITIONTYPE_SELF_DEBUFF_GAIN], parserEvent.EffectName);
   -- Buff gain.
   elseif (parserEvent.NotificationType == MSBTParser.NOTIFICATIONTYPE_BUFF) then
    MSBTTriggers.FireTriggers(categorizedTriggers[MSBTTriggers.MAINCONDITIONTYPE_SELF_BUFF_GAIN], parserEvent.EffectName);
   end

  -- The notification is for the current target.
  elseif (parserEvent.RecipientName == UnitName("target")) then
   -- Debuff gain.
   if (parserEvent.NotificationType == MSBTParser.NOTIFICATIONTYPE_DEBUFF) then
    -- First application of the debuff.
    if (not parserEvent.Amount) then
     MSBTTriggers.FireTriggers(categorizedTriggers[MSBTTriggers.MAINCONDITIONTYPE_TARGET_DEBUFF_GAIN], parserEvent.EffectName);
     MSBTTriggers.FireTriggers(categorizedTriggers[MSBTTriggers.MAINCONDITIONTYPE_TARGET_DEBUFF_APP], parserEvent.EffectName, 1);
    -- Another application of the debuff.
    else
     MSBTTriggers.FireTriggers(categorizedTriggers[MSBTTriggers.MAINCONDITIONTYPE_TARGET_DEBUFF_APP], parserEvent.EffectName, parserEvent.Amount);
    end

   -- Buff gain.
   elseif (parserEvent.NotificationType == MSBTParser.NOTIFICATIONTYPE_BUFF) then
    MSBTTriggers.FireTriggers(categorizedTriggers[MSBTTriggers.MAINCONDITIONTYPE_TARGET_BUFF_GAIN], parserEvent.EffectName);
   end
  end

 end 
end


-- **********************************************************************************
-- Returns whether or not all of the secondary conditions for the passed trigger are
-- satisfied.
-- **********************************************************************************
function MSBTTriggers.CheckSecondaryConditions(triggerKey)
 local triggerSettings = MSBTProfiles.CurrentProfile.Triggers[triggerKey].TriggerSettings;

 -- Return true if there are no secondary conditions.
 if (not triggerSettings.SecondaryConditions) then
  return true;
 end


 -- Holds whether or not all secondary conditions are met.
 local conditionsMet = true;

 -- Holds whether or not the trigger has a cooldown condition.
 local hasCooldownCondition = false;

 -- Loop through all of the secondary conditions.
 local conditionParam;
 for conditionNum, secondaryConditionType in pairs(triggerSettings.SecondaryConditions) do
  -- Get the parameter for the condition.
  conditionParam = triggerSettings.SecondaryConditionParams[conditionNum];

  -- Buff Inactive.
  if (secondaryConditionType == MSBTTriggers.SECONDARYCONDITIONTYPE_BUFF_INACTIVE) then
   if (MSBTParser.CurrentAuras and MSBTParser.CurrentAuras.Buffs[conditionParam]) then
    conditionsMet = false;
   end
  
  -- Minimum power amount.
  elseif (secondaryConditionType == MSBTTriggers.SECONDARYCONDITIONTYPE_MINIMUM_POWER) then
   -- Check if there is less power than the minimum amount.
   if (UnitMana("player") < conditionParam) then
    conditionsMet = false;
   end

  -- Spell Ready.
  elseif (secondaryConditionType == MSBTTriggers.SECONDARYCONDITIONTYPE_SPELL_READY) then
   -- Make sure there is a spell name to check.
   if (conditionParam) then
    -- Get the spell ID for the spell name.  Search the spell book the first time, and
    -- cache the results for subsequent calls.
    if (spellIDCache[conditionParam] == nil) then
     spellIDCache[conditionParam] = MSBTTriggers.GetSpellID(conditionParam);
    end
    local spellID = spellIDCache[conditionParam];

    -- Check if the spell is not known.
    if (spellID == 0) then
     conditionsMet = false;

    -- Spell is known.
    else
     -- Check if the spell is cooling down (but ignore the global cooldown).
     local start, duration = GetSpellCooldown(spellID, BOOKTYPE_SPELL);
     if (start > 0 and duration > 1.5) then
      conditionsMet = false;
     end    
    end
   end

  -- Warrior Stance.
  elseif (secondaryConditionType == MSBTTriggers.SECONDARYCONDITIONTYPE_WARRIOR_STANCE) then
   if (playerClass == "WARRIOR" and (GetShapeshiftForm(true) ~= conditionParam)) then
    conditionsMet = false;
   end

  -- Trigger Cooldown
  elseif (secondaryConditionType == MSBTTriggers.SECONDARYCONDITIONTYPE_TRIGGER_COOLDOWN) then
   hasCooldownCondition = true;
   local lastFired = cooldownTimes[triggerKey] or 0;
   if ((GetTime() - lastFired) < conditionParam) then
    conditionsMet = false;
   end
  end
 end

 -- If all conditions are met and the trigger has a cooldown condition then set the current time
 -- as the last time the trigger was fired.
 if (conditionsMet and hasCooldownCondition) then
  cooldownTimes[triggerKey] = GetTime();
 end

 -- Return whether or not the secondary conditions are satisfied.
 return conditionsMet;
end

-- **********************************************************************************
-- Fires the passed triggers if the passed condition and the secondary conditions
-- are met. Any additional parameters will be populated as captured data into 
-- trigger event data table.
-- **********************************************************************************
function MSBTTriggers.FireTriggers(triggers, condition, ...)
 -- Don't do anything if there are no triggers to search for.
 if (not triggers) then return; end

 -- Loop through the passed triggers.
 for triggerKey, triggerValue in pairs(triggers) do
  if (triggerValue == triggerKey or triggerValue == condition) then
   -- Check if the secondary conditions are met and fire the trigger.
   if (MSBTTriggers.CheckSecondaryConditions(triggerKey)) then
    MSBTTriggers.PopulateTriggerEventData(triggerKey, ...);
    MSBTTriggers.SendTriggerEvent();
   end
  end
 end
end


-- **********************************************************************************
-- Fires the passed health triggers if the secondar conditions are met and returns
-- the updated health percentage.
-- **********************************************************************************
function MSBTTriggers.FireHealthTriggers(unit, healthTriggers, lastHealthPercentage)
 -- Don't do anything if there are no triggers to search for.
 if (not healthTriggers) then return 0; end

 local healthAmount = UnitHealth(unit);
 local healthPercentage = healthAmount / UnitHealthMax(unit);

 -- Loop through the passed health triggers.
 for triggerKey, thresholdPercentage in pairs(healthTriggers) do
  -- Check if we just crossed the trigger's threshold.
  if (healthPercentage < thresholdPercentage/100 and
      lastHealthPercentage >= thresholdPercentage/100) then

   -- Check if the secondary conditions are met and fire the trigger.
   if (MSBTTriggers.CheckSecondaryConditions(triggerKey)) then
    MSBTTriggers.PopulateTriggerEventData(triggerKey, healthAmount);
    MSBTTriggers.SendTriggerEvent();
   end

  end
 end

 -- Return the new health percentage.
 return healthPercentage;
end


-- **********************************************************************************
-- Fires the passed mana triggers if the secondary conditions are met and returns
-- the updated mana percentage.
-- **********************************************************************************
function MSBTTriggers.FireManaTriggers(unit, manaTriggers, lastManaPercentage)
 -- Don't do anything if there are no triggers to search for.
 if (not manaTriggers) then return 0; end

 -- Make sure we're dealing with mana.
 if (UnitPowerType(unit) == 0) then
  local manaAmount = UnitMana(unit);
  local manaPercentage = manaAmount / UnitManaMax(unit);

  -- Loop through the passed mana triggers.
  for triggerKey, thresholdPercentage in pairs(manaTriggers) do
   -- Check if we just crossed the trigger's threshold.
   if (manaPercentage < thresholdPercentage/100 and
       lastManaPercentage >= thresholdPercentage/100) then

    -- Check if the secondary conditions are met and fire the trigger.
    if (MSBTTriggers.CheckSecondaryConditions(triggerKey)) then
     MSBTTriggers.PopulateTriggerEventData(triggerKey, manaAmount);
     MSBTTriggers.SendTriggerEvent();
    end
   end
  end

  -- Return the new mana percentage.
  return manaPercentage;
 end

 -- Return 0 since we aren't dealing with mana.
 return 0;
end


-- **********************************************************************************
-- Parses the search pattern triggers for a match with the passed combat message
-- and event.
-- **********************************************************************************
function MSBTTriggers.ParseSearchPatternTriggers(event, combatMessage)
 -- Check if event search mode is enabled.
 if (searchModePattern) then
  -- Check if the pattern is in the combat message.
  if (string.find(combatMessage, searchModePattern)) then
   -- Print out the event type and the combat message.
   MikSBT.Print(event .. " - " .. combatMessage, 0, 1, 0);
  end
 end

 -- Get search pattern triggers.
 local searchPatternTriggers = categorizedTriggers[MSBTTriggers.MAINCONDITIONTYPE_SEARCH_PATTERN];


 -- Check if there are no triggers for the event type and bail.
 if (not (searchPatternTriggers and searchPatternTriggers[event])) then
  return;
 end

 -- Loop through all triggers for the event.
 for triggerKey, searchPatterns in pairs(searchPatternTriggers[event]) do
  -- Loop through all of the search patterns for the trigger.
  for _, searchPattern in pairs(searchPatterns) do

   -- Check if the search pattern is a global string.
   if (_G[searchPattern] ~= nil) then
    -- Format the global string into a lua compatible search string.
    searchPattern = MSBTParser.ConvertGlobalString(searchPattern);
   end

   -- Get capture data.
   local capturedData = MSBTTriggers.GetUnorderedCaptures(string.find(combatMessage, searchPattern));

   -- Check if a match was found. 
   if (capturedData) then
    if (MSBTTriggers.CheckSecondaryConditions(triggerKey)) then
     -- Get trigger event data and call the trigger handler.
     MSBTTriggers.PopulateTriggerEventData(triggerKey, unpack(capturedData));
     MSBTTriggers.SendTriggerEvent();
    end

    break;
   end
  end -- Loop through search patterns. 
 end -- Loop through triggers.
end


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

MSBTTriggers.OnLoad();