--[[
  Healers Assist by Kiki - European Cho'gall

  TODO :
  
    GESTION/CONFIG :
     - Handle Talent/Armor bonus in _HA_GetDuration function
     - Handle pets (with config option to show them in emergency)
     - For the buf request, add an option to refuse requests when in combat (for some bufs, maybe configurable per buf with 3 options, deny:not-in-combat:always)

    GUI :
     - Possibility to configure color used in the HA_GUI_Process_Announce function
     - Possibility to only show hots I can cast in emergency list
     - Emergency list : Show in another color, people out of my healing range (CheckInteractDistance() is 30yards max, maybe show in yellow)
     - Show an estimated time of remaining regen time for a healer in Resting mode (dans le champ de casting) (new field EndRegenTime to compute in HA_Healers struct)
     - Show remaining effect time of a cooldown spell casted on a healer

    SORTING :
     [Quote from Freddy] Some feature ideas I heard about the emergency list:
      - Mark players which are in your party (different color of HP bar?) + priority option
      - Option to limit filter the estimated heal value that is added to the HP by casting time (e.g. only add heal which should have finished in <1.5sek)
      - Make an option which allowes it to set a fixed value of HP difference to show up the player in the list (not percentage)
      - Add something which put's the tanks at a higher priorty (class specific settings for casting tiem filter?) 

  TODO WARRIOR PLUGIN :
   - Show for "YOU", current casts on yourself, with casting times (sorted by casting time)
     -> Prints a warning to yourself (use healing potion !! for example) if no spell is coming, and you have the agro, or gonna die soon ^^

  BUGS :
   - Unit IDs changing during a SpellRequest (raid changes while the SpellRequest popup is shown -> possible wrong unitid) (NOT SURE IF THIS IS POSSIBLE)
    -> In _HA_SetRaiderVariables function (and GA callback)
      -> Callback a function in GUI to update all internal variables using IDs (like SpellRequest)
   - RaiderDeath not always correct (maybe check with hp==0 ?)
   - Hunter that Feign death will be counted as "dead/rezzed"
   - Druid shifting back to humain with full mana, will show empty mana (need to grab mp/mpmax after a shift)
   - Without GroupAnalyse, if I'm in Group Mode (not raid) all members are set as GroupLeader
   - Heal on an external player or NPC (out of raid), will trigger overheal status after spell complete -> Well... nevermind right now

 ChangeLog :
   - 2006/09/12 : 1.1
     - Fixed incorrect healing debuff detection of Nefarius encounter
   - 2006/08/31 :
     - Fixed Item Bonuses not correct with latest BonusScanner addon installed (Scan not done, if no other addon ask BS for a scan)
   - 2006/08/30 :
     - Fixed minor xml error (minimap button not highlighting correctly (thanks Kagar)
     - Fixed initialization issue, if not using GroupAnalyse addon
     
  [Full ChangeLog in readme.txt file]
]]



--------------- Shared Constantes ---------------
HA_MODE_NONE = 0;
HA_MODE_SOLO = 1
HA_MODE_GROUP = 2
HA_MODE_RAID = 3;

--------------- Shared variables ---------------
HA_VERSION = "1.1";
HA_PlayerName = nil;
HA_CurrentTarget = nil;
HA_LibramItem = nil;
HA_Healers = {};
HA_Raiders = {};
HA_RaidersByID = {};
HA_CurrentGroupMode = HA_MODE_NONE;
HA_MyselfHealer = nil;
HA_MyselfRaider = nil;
HA_CurrentZone = nil;
HA_SpellCooldowns = {};
HA_AFK_Mode = false;
HA_ClassesID = {
  ["DRUID"] = 1;
  ["HUNTER"] = 2;
  ["MAGE"] = 3;
  ["PRIEST"] = 4;
  ["ROGUE"] = 5;
  ["WARLOCK"] = 6;
  ["WARRIOR"] = 7;
  ["PALADIN"] = 8;
  ["SHAMAN"] = 8;
};

--------------- Local Constantes ---------------
local HA_COOLDOWN_SPELLS_UPDATE_DELAY = 60; -- Every 60sec
local HA_VERSION_SEND_DELAY = 60; -- Every 60sec
local HA_MAX_HEAL_UPDATES = 10; -- Keep max 10 incoming heal per raider
local _HA_LastTimeEmergency = 0;

--------------- Local variables ---------------
local HA_NeedInit = true;
local HA_CastingSpell = nil;
local HA_HookActionName = nil;
local HA_HookActionRank = 0;
local HA_StopCommandSent = true;
local HA_StopCommandLastTime = 0;
local HA_CastingInstantSpell = nil;
local HA_CastingInstantRank = 0;
local HA_SpellTargetName = nil;
local HA_PotentialSpellTargetName = nil;
local HA_LastRegrowthRank = 0;
local HA_MaxRanks = {};
local _HA_UseBonusScannerAddon = false;
local _HA_BonusScanScheduled = false;
local _HA_LastSendVersion = 0;


--------------- Internal functions ---------------

function HA_SetNewLock(printmsg)
  if(HA_Config.Lock)
  then
    HealersAssistMainFrame:EnableMouse("false");
    if(printmsg)
    then
      HA_ChatPrint(HA_CHAT_LOCK_ON);
    end
  else
    HealersAssistMainFrame:EnableMouse("true");
    if(printmsg)
    then
      HA_ChatPrint(HA_CHAT_LOCK_OFF);
    end
  end
end

local function _HA_GetMaxSpellRanks()
  local maxRanks = {};
  local index = 1;
  while(1) do
    local spellName, spellRank = GetSpellName(index, BOOKTYPE_SPELL)
    if(not spellName) then
       break;
    elseif(HA_Spells[spellName]) then
       local _,_,ranknum = string.find(spellRank,HA_RANK.." (%d+)");
     ranknum = tonumber(ranknum,10);
     if(not maxRanks[spellName]) then
          maxRanks[spellName] = ranknum;
       elseif(ranknum > maxRanks[spellName]) then
          maxRanks[spellName] = ranknum;
       end
    end
  index = index + 1
  end
  return maxRanks;
end

local function _HA_SetRaiderAsHealer(raider)
  if(not raider.ishealer)
  then
    raider.ishealer = true;
    -- Call plugins
    for n,pl in HA_ActivePlugins
    do
      if(pl.OnEvent)
      then
        pl.OnEvent(HA_EVENT_HEALER_JOINED,{raider.name});
      end
    end
  end
end

local function _HA_UpdateOtherVariables(raider)
  raider.percent = floor(raider.hp / raider.hpmax * 100);
  raider.mppercent = floor(raider.mp / raider.mpmax * 100);

  local healer = HA_Healers[raider.name];
  if(healer and (raider.class == "PRIEST" or raider.class == "DRUID" or raider.class == "PALADIN" or raider.class == "SHAMAN")) -- This is a healer
  then
    _HA_SetRaiderAsHealer(raider);
    healer.id = raider.id;
    healer.Raider = raider;
  end
end

local function _HA_SetMinimalRaiderVariables(raider,raidid,rank,subgrp)
  raider.id = raidid;
  HA_RaidersByID[raidid] = raider;
  raider.rank = rank;
  raider.subgrp = subgrp;
end

local function _HA_SetFullRaiderVariables(raider,raidid,rank,subgrp)
  _HA_SetMinimalRaiderVariables(raider,raidid,rank,subgrp);
  _,raider.class = UnitClass(raider.id);
  raider.classid = HA_ClassesID[raider.class];
  raider.isdead = UnitIsDeadOrGhost(raider.id);
  raider.oldisdead = raider.isdead;
  raider.hp_real = UnitHealth(raider.id);
  raider.hpmax = UnitHealthMax(raider.id);
  raider.ischarmed = UnitIsCharmed(raider.id);
  raider.mp = UnitMana(raider.id);
  raider.mpmax = UnitManaMax(raider.id);
  raider.isconnected = UnitIsConnected(raider.id);

  raider.hp = raider.hp_real;
  raider.hp_estim = raider.hp_real;
  raider.ignore_next_wound = 0;
  raider.life_updates = {};
  raider.heal_updates = {};
  
  _HA_UpdateOtherVariables(raider);
end

local function _HA_CreateRaider(name)
  local infos = {};
  infos.name = name;
  infos.count = 0;
  infos.estimates = {};
  infos.estimate_ratio = 1;
  infos.overtime = {};
  return infos;
end

local function _HA_AddHealer(pl_name)
  if(HA_Healers[pl_name] == nil)
  then
    local infos = {};
    local raider = HA_Raiders[pl_name];
    infos.Cooldown = {};
    infos.Name = pl_name;
    infos.State = HA_STATE_NOTHING;
    infos.OverHealPercent = 0;
    infos.EstimateRatio = 1;
    infos.Estimate = 0;
    infos.StartTime = 0;
    infos.Raider = raider;

    if(raider)
    then
      local class = raider.class;
      infos.id = raider.id;
      if(class == "PRIEST" or class == "DRUID" or class == "PALADIN" or class == "SHAMAN")
      then
        HA_ChatDebug(HA_DEBUG_MEMBERS,"HA_CheckPlayerJoined : "..pl_name.." is in my Raiders list and is a Healer");
        _HA_SetRaiderAsHealer(raider);
      else
        HA_ChatDebug(HA_DEBUG_MEMBERS,"HA_CheckPlayerJoined : "..pl_name.." is in my Raiders list but is not a Healer");
      end
    else
      HA_ChatDebug(HA_DEBUG_MEMBERS,"HA_CheckPlayerJoined : "..pl_name.." is not in my Raiders list");
    end

    HA_Healers[pl_name] = infos;
    if(pl_name == HA_PlayerName)
    then
      HA_MyselfHealer = infos;
      HA_ChatDebug(HA_DEBUG_GLOBAL,"Setting MYSELF as HEALER : "..tostring(infos));
      HA_GUI_Process_Version(HA_PlayerName,HA_VERSION);
    end
  end
end

local function _HA_AddRaider(name,tab)
  HA_Raiders[name] = tab;
  HA_RaidersByID[tab.id] = tab;
  HA_ChatDebug(HA_DEBUG_RAIDERS,"_HA_AddRaider : Adding raider "..name.." with id="..tab.id);
  if(name == HA_PlayerName) -- Myself
  then
    HA_MyselfRaider = tab;
    HA_ChatDebug(HA_DEBUG_GLOBAL,"Setting MYSELF as RAIDER : "..tostring(tab));
  end
  -- Call plugins
  for _,pl in HA_ActivePlugins
  do
    if(pl.OnEvent)
    then
      pl.OnEvent(HA_EVENT_RAIDER_JOINED,{name});
    end
  end
  if(tab.class == "PRIEST" or tab.class == "DRUID" or tab.class == "PALADIN" or tab.class == "SHAMAN")
  then
    _HA_AddHealer(name);
  end
end

local function _HA_RemoveHealer(pl_name)
  if(HA_Healers[pl_name])
  then
    -- Call plugins
    for n,pl in HA_ActivePlugins
    do
      if(pl.OnEvent)
      then
        pl.OnEvent(HA_EVENT_HEALER_LEFT,{pl_name});
      end
    end
    HA_Healers[pl_name] = nil;
  end
end

local function _HA_RemoveRaider(name)
  local raider = HA_Raiders[name];
  if(raider)
  then
    HA_RaidersByID[raider.id] = nil;
  end
  HA_Raiders[name] = nil;
  HA_ChatDebug(HA_DEBUG_RAIDERS,"_HA_RemoveRaider : Removed raider "..name);
  -- Call plugins
  for _,pl in HA_ActivePlugins
  do
    if(pl.OnEvent)
    then
      pl.OnEvent(HA_EVENT_RAIDER_LEFT,{name});
    end
  end
  _HA_RemoveHealer(name); -- Check for healer removal
end

local function _HA_AnalyseGroupRaidMembers()
  local newmode = HA_MODE_NONE;
  local new_raiders = {};
  HA_ChatDebug(HA_DEBUG_RAIDERS,"_HA_AnalyseGroupRaidMembers : Start Analyse");

  if(GetNumRaidMembers() ~= 0) -- In a raid
  then
    if(HA_CurrentGroupMode ~= HA_MODE_RAID) -- But was not
    then
      HealersAssistMainFrame:UnregisterEvent("PARTY_MEMBERS_CHANGED");
    end
    HA_ChatDebug(HA_DEBUG_RAIDERS,"_HA_AnalyseGroupRaidMembers : I'm in a RAID");
    newmode = HA_MODE_RAID;
    local count = GetNumRaidMembers();
    for i = 1, count do
      local name,rank,subgrp = GetRaidRosterInfo(i);
      if(name)
      then
        new_raiders[name] = _HA_CreateRaider(name);
        _HA_SetMinimalRaiderVariables(new_raiders[name],"raid"..i,rank,subgrp);
      end
    end
  else
    if(HA_CurrentGroupMode == HA_MODE_RAID) -- Was in a RAID
    then
      HealersAssistMainFrame:RegisterEvent("PARTY_MEMBERS_CHANGED");
    end
    new_raiders[HA_PlayerName] = _HA_CreateRaider(HA_PlayerName);
    _HA_SetMinimalRaiderVariables(new_raiders[HA_PlayerName],"player",2,1);
    if(GetNumPartyMembers() ~= 0) -- In a group
    then
      HA_ChatDebug(HA_DEBUG_RAIDERS,"_HA_AnalyseGroupRaidMembers : I'm in a GROUP");
      newmode = HA_MODE_GROUP;
      for i = 1,4 do
        local name = UnitName("party"..i);
        if(name and (name ~= UNKNOWNOBJECT) and (name ~= UKNOWNBEING))
        then
          new_raiders[name] = _HA_CreateRaider(name);
          _HA_SetMinimalRaiderVariables(new_raiders[name],"party"..i,2,1);
        end
      end
    else
      HA_ChatDebug(HA_DEBUG_RAIDERS,"_HA_AnalyseGroupRaidMembers : I'm not grouped");
      newmode = HA_MODE_SOLO;
    end
  end

  -- Update list of Raiders
  for n,tab in HA_Raiders -- Remove old raiders
  do
    if(new_raiders[n] == nil) -- No longer in the raid
    then
      _HA_RemoveRaider(n);
    end
  end

  HA_RaidersByID = {};
  for n,tab in new_raiders -- Add new raiders
  do
    local raider = HA_Raiders[n];
    if(raider == nil) -- New member
    then
      _HA_SetFullRaiderVariables(tab,tab.id,tab.rank,tab.subgrp);
      _HA_AddRaider(n,tab);
    else -- Was already here -> Update variables
      _HA_SetMinimalRaiderVariables(raider,tab.id,tab.rank,tab.subgrp);
      _HA_UpdateOtherVariables(raider);
    end
  end

  if(HA_Config.Auto and newmode ~= HA_CurrentGroupMode)
  then
    if(newmode == HA_MODE_SOLO)
    then
      HealersAssistMainFrame:Hide();
    else
      HealersAssistMainFrame:Show();
    end
  end
  HA_CurrentGroupMode = newmode;
  
  HA_ChatDebug(HA_DEBUG_RAIDERS,"_HA_AnalyseGroupRaidMembers : Analyse Completed");
end

local function _HA_GroupAnalyseCallback(event,param)
  local infos;
  if(event == GA_EVENT_INFOS_CHANGED)
  then
    HA_ChatDebug(HA_DEBUG_RAIDERS,"_HA_GroupAnalyseCallback : Start members data update");
    HA_RaidersByID = {};
    debugprofilestart();
    for name,member in GA_Members
    do
      infos = HA_Raiders[name];
      if(infos)
      then
        infos.id = member.unitid;
        HA_RaidersByID[member.unitid] = infos;
        infos.rank = member.rank;
        infos.subgrp = member.subgrp;
        infos.ischarmed = member.ischarmed;
        infos.isconnected = member.isconnected;
        _HA_UpdateOtherVariables(infos);
      end
    end
    HA_PROFILE_AnalyseRaidersRoutine = debugprofilestop();
    HA_ChatDebug(HA_DEBUG_RAIDERS,"_HA_GroupAnalyseCallback : Update Completed");
  elseif(event == GA_EVENT_MEMBER_JOINED)
  then
    infos = _HA_CreateRaider(param);
    local member = GA_Members[param];
    _HA_SetFullRaiderVariables(infos,member.unitid,member.rank,member.subgrp);
    _HA_AddRaider(param,infos);
  elseif(event == GA_EVENT_MEMBER_LEFT)
  then
    _HA_RemoveRaider(param);
  elseif(event == GA_EVENT_GROUP_MODE_CHANGED)
  then
    HA_ChatDebug(HA_DEBUG_GLOBAL,"_HA_GroupAnalyseCallback : Group mode changed to "..param);
    if(HA_Config.Auto)
    then
      if(param == GA_MODE_SOLO)
      then
        HealersAssistMainFrame:Hide();
      else
        HealersAssistMainFrame:Show();
      end
    end
    HA_CurrentGroupMode = param;
  end
end

local function _HA_CooldownUpdateScheduleRoutine()
  local serv_time = GetTime();
  local tim = time();
  -- Update my cooldown status
  if(HA_MyselfHealer)
  then
    for spell,infos in HA_SpellCooldowns
    do
      -- Update my cooldown
      local startTime,cd_value = GetSpellCooldown(infos.id,"spell");
      local Cooldown = 0;
      if(startTime ~= 0 and cd_value > 30) -- (Prevent global cooldown and silence spells to interfere)
      then
        Cooldown = floor((startTime+cd_value) - serv_time);
      end
  
      -- Check for update send
      if(not HA_AFK_Mode and ((Cooldown == 0 and infos.last ~= 0) or -- Was in cooldown, but not anymore
         (Cooldown ~= 0 and infos.last == 0) or -- Was up, but in cooldown now
         (tim > (infos.lastSend+HA_COOLDOWN_SPELLS_UPDATE_DELAY)))) -- Delay expired
      then
        HA_GUI_Process_CooldownUpdate(HA_PlayerName,infos.ispell,Cooldown);
        HA_COM_CooldownUpdate(infos.ispell,Cooldown);
        infos.lastSend = tim;
        infos.last = Cooldown;
      end
    end
    -- Check for HA version send
    if(not HA_AFK_Mode and (tim > (_HA_LastSendVersion+HA_VERSION_SEND_DELAY))) -- Delay expired
    then
      HA_ChatDebug(HA_DEBUG_GLOBAL,"Too long since last Version send");
      HA_COM_SendVersion();
      _HA_LastSendVersion = tim;
    end
  end

  -- Update other cooldown status
  for name,tab in HA_Healers
  do
    for spell,infos in tab.Cooldown
    do
      if(infos.Remain ~= 0) -- Player having a Cooldown spell
      then
        infos.Remain = infos.Remain - (serv_time - infos.Start);
        if(infos.Remain <= 0)
        then
          infos.Remain = 0;
        end
        infos.Start = serv_time;
      end
    end
  end
  
  -- Re schedule
  HASystem_Schedule(1,_HA_CooldownUpdateScheduleRoutine);
end

local function _HA_SetDefaultConfig(param,value)
  if(HA_Config[param] == nil)
  then
    HA_Config[param] = value;
  end
end

local function _HA_ScheduledInit()
  HA_InitializeCooldownSpells();
  HASystem_Schedule(1,_HA_CooldownUpdateScheduleRoutine); -- Special cooldown routine
  HASystem_Schedule(1,HA_OvertimeScheduleRoutine); -- Special overtime routine
  HASystem_Schedule(1,HA_StatusScheduleRoutine); -- Special status routine
end

local function HA_StartupInitVars()
  local playerName = UnitName("player");
  if((playerName) and (playerName ~= UNKNOWNOBJECT) and (playerName ~= UKNOWNBEING))
  then
    -- Initialize Toon specific stuff
    HA_PlayerName = playerName;
    HA_NeedInit = false;
    _HA_SetDefaultConfig("MinEmergencyPercent",90);
    _HA_SetDefaultConfig("KeepValue",3);
    _HA_SetDefaultConfig("ShowInstants",false);
    _HA_SetDefaultConfig("ShowHoT",false);
    _HA_SetDefaultConfig("ButtonPosition",190);
    _HA_SetDefaultConfig("EmergencyGroups",{ true,true,true,true,true,true,true,true });
    _HA_SetDefaultConfig("EmergencyClasses",{ true,true,true,true,true,true,true,true });
    _HA_SetDefaultConfig("HealersClasses",{ true,true,true,true,true,true,true,true });
    _HA_SetDefaultConfig("AllowSpellRequest",{});
    _HA_SetDefaultConfig("HealersLines",10);
    _HA_SetDefaultConfig("EmergLines",5);
    _HA_SetDefaultConfig("Scale",100);
    _HA_SetDefaultConfig("Alpha",100);
    _HA_SetDefaultConfig("BackdropAlpha",100);
    _HA_SetDefaultConfig("GUIRefresh",0.1);
    _HA_SetDefaultConfig("Plugins",{});
    _HA_SetDefaultConfig("PluginOrder",{});
    _HA_SetDefaultConfig("PluginAuto",{});
    _HA_SetDefaultConfig("UseEstimatedHealth",true);
    HA_CheckLoadPlugins();
    HA_SetNewLock(false);
    HASystem_Schedule(5,_HA_ScheduledInit);
    HA_MoveMinimapButton();
    -- Setup Default function
    if(CastParty_OnClickByUnit and type(CastParty_OnClickByUnit) == "function") -- CastParty
    then
      HA_CustomOnClickFunction = CastParty_OnClickByUnit;
    end
    if(WatchDog_OnClick and type(WatchDog_OnClick) == "function") -- WatchDog
    then
      HA_CustomOnClickFunction = WatchDog_OnClick;
    end
    if(JC_CatchKeyBinding and type(JC_CatchKeyBinding) == "function") -- JustClick
    then
      HA_CustomOnClickFunction = JC_CatchKeyBinding;
    end
  end
end

function HA_PlayerHasBuf(texture)
  for i=1,16
  do
    local t = UnitBuff("player",i);
    if(t == nil) then break; end;
    if(t == texture)
    then
      return true;
    end
  end
  return false;
end

local function HA_BuildRankString(ranknum)
  local rankstr = "";

  if(ranknum and ranknum > 1)
  then
    for i=1,ranknum do
      rankstr = rankstr.."I";
    end
  end

  return rankstr;
end

local function HA_Commands(command)
  local i,j, cmd, param = string.find(command, "^([^ ]+) (.+)$");
  if(not cmd) then cmd = command; end
  if(not cmd) then cmd = ""; end
  if(not param) then param = ""; end

  if((cmd == "") or (cmd == "help"))
  then
    local lock = "off";
    if(HA_Config.Lock) then lock = "on"; end
    local auto = "off";
    if(HA_Config.Auto) then auto = "on"; end
    HA_ChatPrint("Usage:");
    HA_ChatPrint("  |cffffffff/ha show|r - "..HA_CHAT_HELP_SHOW);
    HA_ChatPrint("  |cffffffff/ha lock (on|off)|r |cff2040ff["..lock.."]|r - "..HA_CHAT_HELP_LOCK);
    HA_ChatPrint("  |cffffffff/ha auto (on|off)|r |cff2040ff["..auto.."]|r - "..HA_CHAT_HELP_AUTO);
    HA_ChatPrint("  |cffffffff/ha versions |r - "..HA_CHAT_HELP_VERSIONS);
    HA_ChatPrint("  |cffffffff/ha msg <Msg to send>|r - "..HA_CHAT_HELP_MSG);
    HA_ChatPrint("  ---- DEBUG ----");
    HA_ChatPrint("  |cffffffff/ha debug (on|off)|r - "..HA_CHAT_HELP_DEBUG);
  elseif(cmd == "show")
  then
    HealersAssistMainFrame:Show();
  elseif(cmd == "lock")
  then
    if(param == "off")
    then
      HA_Config.Lock = nil;
    elseif(param == "on")
    then
      HA_Config.Lock = true;
    else
      HA_ChatPrint(HA_CHAT_CMD_PARAM_ERROR.."lock");
      return;
    end
    HA_SetNewLock(true);
  elseif(cmd == "auto")
  then
    if(param == "off")
    then
      HA_Config.Auto = nil;
    elseif(param == "on")
    then
      HA_Config.Auto = true;
    else
      HA_ChatPrint(HA_CHAT_CMD_PARAM_ERROR.."auto");
      return;
    end
  elseif(cmd == "debug")
  then
    if(param == "off")
    then
      HA_DBG_SetDebugMode(0);
    elseif(param == "on")
    then
      HA_DBG_SetDebugMode(1)
    else
      HA_ChatPrint(HA_CHAT_CMD_PARAM_ERROR.."debug");
      return;
    end
  elseif(cmd == "versions")
  then
    HA_ShowVersions();
  elseif(cmd == "msg" and param and param ~= "")
  then
    HA_GUI_Process_Announce(HA_PlayerName,param);
    HA_COM_Announce(param);
  else
    HA_ChatPrint(HA_CHAT_CMD_UNKNOWN);
  end
end

function CheckSpellTarget(FunctionHook)
  if(SpellIsTargeting())
  then
    HA_PotentialSpellTargetName = UnitName("mouseover");
    HA_ChatDebug(HA_DEBUG_ACTIONS,FunctionHook.." : PotentialTarget="..tostring(HA_PotentialSpellTargetName));
  elseif(UnitIsFriend("player","target"))
  then
    HA_SpellTargetName = UnitName("target");
    HA_ChatDebug(HA_DEBUG_ACTIONS,FunctionHook.." : Target="..tostring(HA_SpellTargetName));
  end
end

--------------- Hooked functions ---------------

function HealersAssist_SpellTargetUnit(unit)
  if(SpellIsTargeting() or not HA_SpellTargetName)
  then
    HA_SpellTargetName = UnitName(unit);
    HA_ChatDebug(HA_DEBUG_ACTIONS,"SpellTargetUnit : Target="..tostring(HA_SpellTargetName));
  end
  return HA_Old_SpellTargetUnit(unit);
end

HA_Old_SpellTargetUnit = SpellTargetUnit;
SpellTargetUnit = HealersAssist_SpellTargetUnit;

--

function HealersAssist_CastShapeshiftForm(index)
  local ret_val = HA_Old_CastShapeshiftForm(index);

  HA_ChatDebug(HA_DEBUG_ACTIONS,"CastShapeshiftForm : Index="..tostring(index));

  HA_CastingInstantSpell = nil;
  HA_CastingInstantRank = 0;
  return ret_val;
end

HA_Old_CastShapeshiftForm = CastShapeshiftForm;
CastShapeshiftForm = HealersAssist_CastShapeshiftForm;

--

function HealersAssist_UseAction(id, val, onSelf)
  HealersAssistTooltip:SetOwner(UIParent, "ANCHOR_NONE");
  HealersAssistTooltip:ClearLines();
  HealersAssistTooltip:SetAction(id);
  local spellName = tostring(HealersAssistTooltipTextLeft1:GetText());
  local rank = HealersAssistTooltipTextRight1:GetText();
  local rankstr = "";
  HealersAssistTooltip:Hide();

  local ret_val = HA_Old_UseAction(id,val,onSelf);

  if(rank and rank ~= "")
  then
    local _,_,ranknum = string.find(rank,HA_RANK.." (%d+)");
    rank = tonumber(ranknum,10);
  elseif(HA_MaxRanks[spellName])
  then
    rank = HA_MaxRanks[spellName];
  else
    rank = 1;
  end

  -- Call plugins
  for n,pl in HA_ActivePlugins
  do
    if(pl.OnEvent)
    then
      pl.OnEvent(HA_EVENT_USE_ACTION,{id,val,onSelf,spellName,rank});
    end
  end

  if(HA_Spells[spellName])
  then
    CheckSpellTarget("UseAction");
    HA_ChatDebug(HA_DEBUG_ACTIONS,"UseAction : SpellName="..tostring(spellName).." Rank="..tostring(rank));
    HA_HookActionName = spellName;
    HA_HookActionRank = rank;
  end

  if(HA_InstantSpells[spellName])
  then
    CheckSpellTarget("UseAction");
    HA_CastingInstantSpell = spellName;
    HA_CastingInstantRank = rank;
  end
  
  -- If onSelf
  if(onSelf and onSelf == 1)
  then
    HA_SpellTargetName = HA_PlayerName;
    HA_ChatDebug(HA_DEBUG_ACTIONS,"UseAction on Self : Target="..tostring(HA_SpellTargetName));
  end
  return ret_val;
end

HA_Old_UseAction = UseAction;
UseAction = HealersAssist_UseAction;

--

function HealersAssist_CastSpell(id,spellBook)
  local ret_val = HealersAssist_Old_CastSpell(id,spellBook);

  local spellName,rank = GetSpellName(id,spellBook);

  if(rank and rank ~= "")
  then
    local _,_,ranknum = string.find(rank,HA_RANK.." (%d+)");
    rank = tonumber(ranknum,10);
  elseif(HA_MaxRanks[spellName])
  then
    rank = HA_MaxRanks[spellName];
  else
    rank = 1;
  end

  -- Call plugins
  for n,pl in HA_ActivePlugins
  do
    if(pl.OnEvent)
    then
      pl.OnEvent(HA_EVENT_CAST_SPELL,{id,spellBook,spellName,rank});
    end
  end

  if(HA_Spells[spellName])
  then
    CheckSpellTarget("CastSpell");
    HA_ChatDebug(HA_DEBUG_ACTIONS,"CastSpell : SpellName="..tostring(spellName).." Rank="..tostring(rank));
    HA_HookActionName = spellName;
    HA_HookActionRank = rank;
  end

  if(HA_InstantSpells[spellName])
  then
    CheckSpellTarget("CastSpell");
    HA_CastingInstantSpell = spellName;
    HA_CastingInstantRank = rank;
  end
  return ret_val;
end

HealersAssist_Old_CastSpell = CastSpell;
CastSpell = HealersAssist_CastSpell;

function HealersAssist_CastSpellByName(spell, onSelf)
  local ret_val = HealersAssist_Old_CastSpellByName(spell, onSelf);

  if(spell)
  then
    local _,_,spellName,rank = string.find(spell,"(.+)%("..HA_RANK.." (%d+)%)");
    if(spellName == nil) -- Spell without rank
    then
      spellName = spell;
    end
    
    if(rank)
    then
      rank = tonumber(rank,10);
    elseif(HA_MaxRanks[spellName])
    then
      rank = HA_MaxRanks[spellName];
    else
      rank = 1;
    end

    -- Call plugins
    for n,pl in HA_ActivePlugins
    do
      if(pl.OnEvent)
      then
        pl.OnEvent(HA_EVENT_CAST_SPELL_BY_NAME,{spell,onSelf,spellName,rank});
      end
    end
  
    if(HA_Spells[spellName])
    then
      CheckSpellTarget("CastSpellByName");
      HA_ChatDebug(HA_DEBUG_ACTIONS,"CastSpellByName : SpellName="..tostring(spellName).." Rank="..tostring(rank));
      HA_HookActionName = spellName;
      HA_HookActionRank = rank;
    end

    if(HA_InstantSpells[spellName])
    then
      CheckSpellTarget("CastSpellByName");
      HA_CastingInstantSpell = spellName;
      HA_CastingInstantRank = rank;
    end
  end
  -- If onSelf
  if(onSelf and onSelf == 1)
  then
    HA_SpellTargetName = HA_PlayerName;
    HA_ChatDebug(HA_DEBUG_ACTIONS,"CastSpellByName on Self : Target="..tostring(HA_SpellTargetName));
  end
  return ret_val;
end

HealersAssist_Old_CastSpellByName = CastSpellByName;
CastSpellByName = HealersAssist_CastSpellByName;

--

local function _HA_GetISpell(SpellName)
  if(HA_Spells[SpellName])
  then
    return HA_Spells[SpellName].iname;
  elseif(HA_InstantSpells[SpellName])
  then
    return HA_InstantSpells[SpellName].iname;
  end
  return nil;
end

function HA_UnitHasBlessingOfLight(TargetName)
  local id = nil;
  if(TargetName)
  then
    if(HA_Raiders[TargetName] and HA_Raiders[TargetName].id)
    then
      id = HA_Raiders[TargetName].id;
    elseif(UnitName("target") == TargetName)
    then
      id = "target";
    end
  end
  if(id)
  then
    for i=1,16
    do
      local t = UnitBuff(id,i,1);
      if(t == nil) then break; end;
      if(t == HA_BLESSING_OF_LIGHT_TEXTURE or t == HA_GREATER_BLESSING_OF_LIGHT_TEXTURE)
      then
        return true;
      end
    end
  end
  return false;
end

local function _HA_GetEstimated(ISpell,IHookName,HookRank,TargetName,CastTime)
  local estimated = 0;
  local spiritadd = 0;
  local willcrit = false;

  if(IHookName and HookRank and ISpell == IHookName and HA_SpellRanks[ISpell] and HA_SpellRanks[ISpell][HookRank])
  then
    local infos = HA_SpellRanks[ISpell][HookRank];
    
    estimated = infos.base;

    -- Add Talent values
    local tinfos = HA_SpellTalents[ISpell];
    if(tinfos)
    then
      if(tinfos.ratios) -- Ratio
      then
      --[[ -- Old code (if talent bonus are applied one after another, and not all at once)
        for _,tid in tinfos.ratios
        do
          if(HA_Talents[tid] and HA_Talents[tid].rank) -- Talent found, and player's rank set
          then
            if(HA_Talents[tid].rankratio) -- Direct ratios
            then
              estimated = estimated * HA_Talents[tid].rankratio[HA_Talents[tid].rank];
            elseif(HA_Talents[tid].spiritratio) -- Spirit based ratios
            then
              spiritadd = HA_Talents[tid].spiritratio[HA_Talents[tid].rank] * UnitStat("player",5);
            end
          end
        end
        ]]
        local total_percent = 1.0;
        for _,tid in tinfos.ratios
        do
          if(HA_Talents[tid] and HA_Talents[tid].rank) -- Talent found, and player's rank set
          then
            if(HA_Talents[tid].rankratio) -- Direct ratios
            then
              total_percent = total_percent + HA_Talents[tid].rankratio[HA_Talents[tid].rank];
            elseif(HA_Talents[tid].spiritratio) -- Spirit based ratios
            then
              spiritadd = HA_Talents[tid].spiritratio[HA_Talents[tid].rank] * UnitStat("player",5);
            end
          end
        end
        estimated = estimated * total_percent;
      end
    end

    -- Add +heal values
    local itembonus;
    if(_HA_UseBonusScannerAddon)
    then
      itembonus = tonumber(BonusScanner.bonuses["HEAL"]);
    else
      itembonus = tonumber(HA_BonusScanner_bonuses["HEAL"]);
    end
    if(itembonus)
    then
      if(HA_LibramItem) -- Check for librams
      then
        if(ISpell == HA_SPELL_FLASH_OF_LIGHT and HA_LibramItem == 23201) -- Libram of Divinity
        then
          itembonus = itembonus + 53;
        elseif(ISpell == HA_SPELL_FLASH_OF_LIGHT and HA_LibramItem == 23006) -- Libram of Light
        then
          itembonus = itembonus + 83;
        elseif(ISpell == HA_SPELL_LESSER_HEALING_WAVE and HA_LibramItem == 23200) -- Totem of Sustaining
        then
          itembonus = itembonus + 53;
        elseif(ISpell == HA_SPELL_LESSER_HEALING_WAVE and HA_LibramItem == 22396) -- Totem of Life
        then
          itembonus = itembonus + 80;
        elseif(ISpell == HA_SPELL_REJUVENATION and HA_LibramItem == 22398) -- Idol of Rejuvenation
        then
          itembonus = itembonus + 62.5; -- Real value is 50, but this value will be 80% applied below, but the game does not apply the 80% rule on the idol
        end
      end
      estimated = estimated + itembonus * infos.castratio * infos.levelratio;
    end

    -- Add + Blessing of light value
    if(HA_SpellTalents[ISpell] and HA_SpellTalents[ISpell].blessing) -- Check for Blessing of light
    then
      if(HA_UnitHasBlessingOfLight(TargetName))
      then
        estimated = estimated + HA_SpellTalents[ISpell].blessing;
      end
    end
  
    -- Check for WillCrit
    local index = 0;
    local texture;
    while(GetPlayerBuffTexture(index))
    do
      texture = GetPlayerBuffTexture(index);
      applications = GetPlayerBuffApplications(index);
      if(texture == HA_DIVINE_FAVOR_TEXTURE)
      then
        willcrit = true;
      elseif(texture == HA_UNSTABLE_POWER_TEXTURE)
      then
        estimated = estimated + (70 * applications * infos.castratio * infos.levelratio);
      elseif(texture == HA_HEALING_OF_THE_AGES_TEXTURE)
      then
        estimated = estimated + (350 * infos.castratio * infos.levelratio);
      end
      index = index + 1;
    end

    if(willcrit)
    then
      estimated = estimated * 1.5;
    end 
  end
  
  return floor(estimated+spiritadd),willcrit;
end

local function _HA_GetDuration(ISpell)
  local duration = 0;

  if(HA_SpellOvertime[ISpell])
  then
    local infos = HA_SpellOvertime[ISpell];
    
    duration = infos.duration;

    -- Add Talent values for duration
    -- Add armor values for duration

  end
  
  return duration;
end

local function _HA_DoStop(resetSpell,msg)
  HA_ChatDebug(HA_DEBUG_ACTIONS,"HA_DoStop : Event="..msg.." CmdSend="..tostring(HA_StopCommandSent).." LastTime="..HA_StopCommandLastTime.." CurTime="..HA_CurrentTime);
  if(not HA_StopCommandSent and (HA_StopCommandLastTime+0.10 < HA_CurrentTime))
  then
    HA_GUI_Process_SpellStop(HA_PlayerName);
    HA_COM_SpellStop();
    HA_StopCommandSent = true;
    HA_StopCommandLastTime = HA_CurrentTime;
    if(resetSpell)
    then
      HA_CastingSpell = nil;
    end
  end
end

local function _HA_RegExpr_CallBack(event, Source, Target, SpellName, Value, Crit)
  -- Check for HoT Ticks
  if(event == "CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS" or
    event == "CHAT_MSG_SPELL_PERIODIC_PARTY_BUFFS" or
    event == "CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS")
  then
    local ispell = HA_HotSpells[SpellName];
    if(ispell)
    then
      HA_ChatDebug(HA_DEBUG_SPELLS,"Callback HOT : Source="..tostring(Source).." Target="..tostring(Target).." Spell="..tostring(SpellName).." ("..ispell..") Value="..tostring(Value));
      HA_GUI_Process_SpellHotTick(Source,ispell,Target,Value);
      HA_COM_SpellHotTick(ispell,Target,Value);
    end
  
  else -- Ok, check for direct heal spells
    local spell = HA_Spells[SpellName];
    local instaspell = false;
    if(spell == nil) -- Not casted, check instant
    then
      spell = HA_InstantSpells[SpellName];
      instaspell = true;
    end
    if(spell)
    then
      if(event == "CHAT_MSG_SPELL_SELF_BUFF") -- Heal from self
      then
        local ispell = _HA_GetISpell(SpellName);
        if(not instaspell)
        then
          _HA_DoStop(true,"HIT");
        end
        HA_GUI_Process_SpellHit(Source,ispell,Target,Value,Crit);
        HA_COM_SpellComplete(ispell,Target,Value,Crit);
        -- Special HA_SPELL_REGROWTH case
        if(ispell == HA_SPELL_REGROWTH)
        then
          ispell = HA_SPELL_REGROWTH_HOT;
          local estimated = _HA_GetEstimated(ispell,ispell,HA_LastRegrowthRank,Target,0);
          local duration = _HA_GetDuration(ispell);
          if(duration ~= 0)
          then
            HA_GUI_Process_SpellOvertime(HA_PlayerName,ispell,Target,duration,estimated,HA_LastRegrowthRank);
            HA_COM_SpellOvertime(ispell,Target,duration,estimated,HA_LastRegrowthRank);
          end
        end
      else -- Heal from other
        if(not spell.group)
        then
          local ispell = _HA_GetISpell(SpellName);
          HA_GUI_Process_SpellHit(Source,ispell,Target,Value,Crit);
        end
      end
    end
  end
end

local function _HA_CheckRegenModeFromCast()
  local healer = HA_MyselfHealer;
  if(healer and healer.State == HA_STATE_RESTING)
  then
    HA_SetRegenMode(false);
  end
end

local function _HA_ScanScheduleRoutine()
  HA_ChatDebug(HA_DEBUG_GLOBAL,"_HA_ScanScheduleRoutine : Scanning bonuses");
  -- Check for Items bonuses
  if(not _HA_UseBonusScannerAddon)
  then
    HA_ChatDebug(HA_DEBUG_GLOBAL,"_HA_ScanScheduleRoutine : Not using BonusScanner addon - Scanning equipment !");
    HA_BonusScanner_ScanAll();
  end
  -- Check for Relic
  local libramlink = GetInventoryItemLink("player",18);
  if(libramlink)
  then
    for iItemId, iEnchantId, iPropertieId, sItemName in string.gfind(libramlink, "|c%x+|Hitem:(%d+):(%d+):(%d+):%d+|h%[(.-)%]|h|r")
    do
      HA_LibramItem = tonumber(iItemId);
    end
  else
    HA_LibramItem = nil;
  end
  -- Ok done
  _HA_BonusScanScheduled = false;
end

local function _HA_GetReasonCode(Reason)
  for i,n in HA_FailReasons
  do
    if(Reason == n)
    then
      return i,"";
    end
  end
  return 0,Reason;
end

local function _HA_CheckForBonusScan()
  if(not _HA_BonusScanScheduled)
  then
    _HA_BonusScanScheduled = true;
    HASystem_Schedule(2,_HA_ScanScheduleRoutine); -- Schedule scan in 2 sec
  end
end

local function _HA_RegisterEvents()
  HealersAssistMainFrame:RegisterEvent("UNIT_INVENTORY_CHANGED");
end

local function _HA_UnregisterEvents()
  HealersAssistMainFrame:UnregisterEvent("UNIT_INVENTORY_CHANGED");
end

local function _HA_CheckLifeUpdates(raider,new_hp)
  if(HA_Config.UseEstimatedHealth)
  then
    local estim = raider.hp_real;
    local updates = raider.life_updates;
  
    --[[if(updates[1] == nil and new_hp == estim)
    then
      HA_ChatDebug(HA_DEBUG_GLOBAL,"UNIT_HEALTH Diff OK : NewHP="..new_hp.." - Got no updates");
    end]]
  
    while(updates[1])
    do
      estim = estim + updates[1].value;
      table.remove(updates,1);
      if(estim == new_hp)
      then
        --HA_ChatDebug(HA_DEBUG_GLOBAL,"UNIT_HEALTH Diff Matching ("..table.getn(updates).." remaning updates) : NewHP="..new_hp);
        break;
      end
    end
    if(estim > raider.hpmax) then estim = raider.hpmax; end
    if(new_hp ~= estim)
    then
      --HA_ChatDebug(HA_DEBUG_GLOBAL,"UNIT_HEALTH Diff Error : NewHP="..new_hp.." Ignoring next Wound Event");
      raider.ignore_next_wound = HA_CurrentTime;
    end
  end
  raider.hp_estim = new_hp;
end

-- Currently don't handle "STOP because of failure" for casted request-spell
local function _HA_CheckSpellRequestSuccess(spell,instant)
  if(HA_SpellRequest and HA_SpellRequest.spell == spell)
  then
    HA_ChatDebug(HA_DEBUG_ACTIONS,"_HA_CheckSpellRequestSuccess : SpellRequest successfully casted (or stopped for casted ones) : "..tostring(spell).." : "..tostring(instant));
    HA_SpellRequest = nil;
    StaticPopup_Hide("HA_REQUEST_FOR_SPELL");
  end
end

local function _HA_CheckSpellRequestFailed(spell,reason)
  if(HA_SpellRequest and HA_SpellRequest.spell == spell)
  then
    HA_ChatDebug(HA_DEBUG_ACTIONS,"_HA_CheckSpellRequestFailed : SpellRequest failed for "..tostring(spell).." : "..tostring(reason));
    HA_SpellRequest.failure = reason;
  end
end

local function _HA_ProcessEvent_SpellCastStart()
  _HA_CheckRegenModeFromCast();
  if(HA_Spells[arg1])
  then
    _HA_DoStop(false,"START");
    HA_CastingSpell = arg1;
    HA_StopCommandSent = false; -- Set *waiting for stop* state
    HA_StopCommandLastTime = HA_CurrentTime; -- And reset time
    if(HA_SpellTargetName == nil)
    then
      HA_SpellTargetName = HA_PotentialSpellTargetName;
      HA_ChatDebug(HA_DEBUG_ACTIONS,"SPELLCAST_START : Target="..tostring(HA_SpellTargetName));
    end
    local casttime = tonumber(arg2,10)
    local target = HA_SpellTargetName;
    local ispell = _HA_GetISpell(HA_CastingSpell);
    local ihook = _HA_GetISpell(HA_HookActionName);
    local estimated,willcrit = _HA_GetEstimated(ispell,ihook,HA_HookActionRank,target,casttime);
    HA_GUI_Process_SpellStart(HA_PlayerName,ispell,target,casttime,estimated,willcrit,HA_HookActionRank);
    HA_COM_SpellStart(ispell,target,casttime,estimated,willcrit,HA_HookActionRank);
    -- Special HA_SPELL_REGROWTH case
    if(ispell == HA_SPELL_REGROWTH)
    then
      HA_LastRegrowthRank = HA_HookActionRank;
    end
  end
  HA_SpellTargetName = nil;
end

local function _HA_ProcessEvent_SpellCastStop()
  _HA_CheckRegenModeFromCast();
  if(HA_CastingInstantSpell) -- An instant spell ?
  then
    if(HA_SpellTargetName == nil)
    then
      HA_SpellTargetName = HA_PotentialSpellTargetName;
      HA_ChatDebug(HA_DEBUG_ACTIONS,"SPELLCAST_STOP (INSTANT SPELL) : Target="..tostring(HA_SpellTargetName));
    end
    local target = HA_SpellTargetName;
    local ispell = _HA_GetISpell(HA_CastingInstantSpell);
    if(HA_InstantSpells[HA_CastingInstantSpell].cooldown) -- A cooldown instant spell
    then
      HA_GUI_Process_SpellCooldown(HA_PlayerName,ispell,target);
      HA_COM_SpellCooldown(ispell,target);
    elseif(HA_InstantSpells[HA_CastingInstantSpell].nonheal) -- A non-heal spell
    then
      HA_GUI_Process_SpellInstant(HA_PlayerName,ispell,target,HA_CastingInstantRank);
      HA_COM_SpellInstant(ispell,target,HA_CastingInstantRank);
    else
      local estimated = _HA_GetEstimated(ispell,ispell,HA_CastingInstantRank,target,0);
      local duration = _HA_GetDuration(ispell);
      if(duration ~= 0)
      then
        HA_GUI_Process_SpellOvertime(HA_PlayerName,ispell,target,duration,estimated,HA_CastingInstantRank);
        HA_COM_SpellOvertime(ispell,target,duration,estimated,HA_CastingInstantRank);
      end
    end
    _HA_CheckSpellRequestSuccess(HA_CastingInstantSpell,true);
    HA_CastingInstantSpell = nil;
  elseif(HA_CastingSpell)
  then
    HA_ChatDebug(HA_DEBUG_ACTIONS,"SPELLCAST_STOP : Spell="..tostring(HA_CastingSpell));
    _HA_CheckSpellRequestSuccess(HA_CastingSpell,false);
    _HA_DoStop(false,"STOP");
  end
  HA_SpellTargetName = nil;
end


--------------- Shared functions ---------------

function HA_GetSpellIDFromName(spellName,spellRank)
  local i = 1;
  local name,rank = GetSpellName(i,BOOKTYPE_SPELL);
  local searched;
  
  if(spellRank) -- With rank
  then
    if(type(spellRank) == "number") -- Number
    then
      searched = spellName.."("..HA_RANK.." "..tostring(spellRank)..")";
    else -- String
      searched = spellName.."("..spellRank..")";
    end
  else -- No rank
    searched = spellName;
  end

  while(name)
  do
    local current;
    if(rank ~= "") -- With rank
    then
      current = name.."("..rank..")";
    else -- No rank
      current = name;
    end
    if(searched == current) -- Found
    then
      return i;
    end
    i = i + 1;
    name,rank = GetSpellName(i,BOOKTYPE_SPELL);
  end
  return 0;
end

local function HA_CheckForEmergencyListUpdate()
  -- Update Emergency every 30 msec
  if(HA_CurrentTime > (_HA_LastTimeEmergency+0.030))
  then
    HA_UpdateListEmergency();
    _HA_LastTimeEmergency = HA_CurrentTime;
  end
end

--------------- XML functions ---------------

function HA_OnEvent()
  if(event == "VARIABLES_LOADED")
  then
    if(HA_NeedInit)
    then
      HA_StartupInitVars();
    end
    return;
  elseif(event == "PLAYER_LOGIN")
  then
    HA_MaxRanks = _HA_GetMaxSpellRanks();
  elseif(event == "PLAYER_ENTERING_WORLD")
  then
    _HA_AnalyseGroupRaidMembers(); -- Force group analyse now
    HA_CurrentZone = GetRealZoneText(); -- Get it here too, because when you /reloadui the ZONE_CHANGED is not called - Thanks blizzard
    _HA_RegisterEvents();
    _HA_CheckForBonusScan();
    HA_CurrentTarget = nil
    if(HA_Config.Auto and HA_CurrentGroupMode ~= HA_MODE_SOLO)
    then
      HealersAssistMainFrame:Show();
    end
  elseif(event == "PLAYER_LEAVING_WORLD")
  then
    _HA_UnregisterEvents();
  elseif(event == "ZONE_CHANGED_NEW_AREA")
  then
    HA_CurrentZone = GetRealZoneText();
  end

  if(event == "UNIT_COMBAT")
  then
    if(HA_Config.UseEstimatedHealth)
    then
      local raider = HA_RaidersByID[arg1];
      if(raider and arg1 ~= "target")
      then
        if(arg2 == "WOUND")
        then
          if(HA_CurrentTime > (raider.ignore_next_wound+0.5))
          then
            --HA_ChatDebug(HA_DEBUG_GLOBAL,"UNIT_COMBAT '"..tostring(arg1).."' ("..tostring(raider.name)..") : DEGATS : "..tostring(arg4).." : UnitHealth="..UnitHealth(arg1).." hp="..raider.hp_real);
            raider.hp_estim = raider.hp_estim - arg4;
            if(raider.hp_estim < 0) then raider.hp_estim = 0; end
            tinsert(raider.life_updates,{ value=-arg4; stamp=HA_CurrentTime });
          --[[else
            HA_ChatDebug(HA_DEBUG_GLOBAL,"UNIT_COMBAT '"..tostring(arg1).."' ("..tostring(raider.name)..") : DEGATS : "..tostring(arg4).." : UnitHealth="..UnitHealth(arg1).." hp="..raider.hp_real.." - IGNORED");]]
          end
          raider.ignore_next_wound = 0;
        elseif(arg2 == "HEAL")
        then
          --HA_ChatDebug(HA_DEBUG_GLOBAL,"UNIT_COMBAT '"..tostring(arg1).."' ("..tostring(raider.name)..") : SOINS : "..tostring(arg4));
          tinsert(raider.heal_updates,{ value=arg4, oldhp=raider.hp_estim }); -- Store health before applying heal
          raider.hp_estim = raider.hp_estim + arg4;
          if(raider.hp_estim > raider.hpmax) then raider.hp_estim = raider.hpmax; end
          tinsert(raider.life_updates,{ value=arg4; stamp=HA_CurrentTime });
          if(getn(raider.heal_updates) > HA_MAX_HEAL_UPDATES) -- Remove a heal update, if too many in the array
          then
            table.remove(raider.heal_updates,1);
          end
        else
          --HA_ChatDebug(HA_DEBUG_GLOBAL,"UNIT_COMBAT '"..tostring(arg1).."' ("..tostring(raider.name)..") : "..tostring(arg2).." : "..tostring(arg4).." : "..tostring(arg3));
        end
        raider.hp = raider.hp_estim; -- Update HP value
      end
    end
    return;

  -- Health/Mana events
  elseif(event == "UNIT_HEALTH")
  then
    local raider = HA_RaidersByID[arg1];
    if(raider)
    then
      local new_hp = UnitHealth(arg1);
      if(arg1 ~= "target")
      then
        _HA_CheckLifeUpdates(raider,new_hp); -- Parse "updates" array, and update hp_estim to 'new_hp' value
      end
      raider.hp_real = new_hp;
      raider.hp = new_hp;
      raider.percent = floor(raider.hp / raider.hpmax * 100);
      HA_CheckForEmergencyListUpdate();
    end
    return;
  elseif(event == "UNIT_MAXHEALTH")
  then
    local raider = HA_RaidersByID[arg1];
    if(raider)
    then
      raider.hpmax = UnitHealthMax(arg1);
      raider.percent = floor(raider.hp / raider.hpmax * 100);
      HA_CheckForEmergencyListUpdate();
    end
    return;
  elseif(event == "UNIT_MANA")
  then
    local raider = HA_RaidersByID[arg1];
    if(raider and raider.ishealer)
    then
      raider.mp = UnitMana(arg1);
      raider.mppercent = floor(raider.mp / raider.mpmax * 100);
    end
    return;
  elseif(event == "UNIT_MAXMANA")
  then
    local raider = HA_RaidersByID[arg1];
    if(raider and raider.ishealer)
    then
      raider.mpmax = UnitManaMax(arg1);
      raider.mppercent = floor(raider.mp / raider.mpmax * 100);
    end
    return;
  elseif(event == "PLAYER_AURAS_CHANGED")
  then
    if(HA_MyselfHealer and not HA_RaiderInfused(HA_PlayerName) and HA_PlayerHasBuf(HA_POWER_INFUSION_TEXTURE))
    then
      HA_GUI_Process_GotPowerInfusion(HA_PlayerName);
      HA_COM_GotPowerInfusion();
    end
    return;
  end

  -- Group events
  if(event == "PARTY_MEMBERS_CHANGED" or event == "RAID_ROSTER_UPDATE")
  then
    HA_ChatDebug(HA_DEBUG_RAIDERS,event.." : Party members changed. Analysing Raiders...");
    debugprofilestart();
    _HA_AnalyseGroupRaidMembers();
    HA_PROFILE_AnalyseRaidersRoutine = debugprofilestop();
    return;
  end
  
  -- Inventory events
  if(HA_NeedInit == false and event == "UNIT_INVENTORY_CHANGED")
  then
    _HA_CheckForBonusScan();
    return;
  end
  
  -- Mouse over event - Get target
  if(event == "UPDATE_MOUSEOVER_UNIT")
  then
    if(SpellIsTargeting() and UnitIsFriend("player", "mouseover"))
    then
      HA_PotentialSpellTargetName = UnitName("mouseover");
      HA_ChatDebug(HA_DEBUG_ACTIONS,"MouseOverUpdate : PotentialTarget="..tostring(HA_PotentialSpellTargetName));
    end 
    return;
  end

  -- Target changed  
  if(event == "PLAYER_TARGET_CHANGED" )
  then
    if(UnitExists("target") and UnitIsFriend("player","target")) -- Add target if don't exist
    then
      local name = UnitName("target");
      local raider = HA_Raiders[name];
      if(raider == nil) -- Don't exist, add it as subgrp 0
      then
        raider = _HA_CreateRaider(name);
        HA_Raiders[name] = raider;
        _HA_SetFullRaiderVariables(raider,"target",0,0);
        HA_UpdateListEmergency();
        -- Call plugins
        for n,pl in HA_ActivePlugins
        do
          if(pl.OnEvent)
          then
            pl.OnEvent(HA_EVENT_RAIDER_JOINED,{name});
          end
        end
      end
      HA_RaidersByID["target"] = raider;
      HA_CurrentTarget = name;
    else -- No valid target
      HA_RaidersByID["target"] = nil;
      HA_CurrentTarget = nil
    end
    HA_PotentialSpellTargetName = HA_CurrentTarget;
    return;
  end
  
  -- System events
  if(event == "CHAT_MSG_SYSTEM")
  then
    if(arg1 == MARKED_AFK or string.find(arg1,string.format(MARKED_AFK_MESSAGE,".*")))
    then
      HA_AFK_Mode = true;
    end
    if(arg1 == CLEARED_AFK)
    then
      HA_AFK_Mode = false;
    end
  elseif(event == "CHAT_MSG_ADDON" and (arg3 == "RAID" or arg3 == "PARTY") and arg1 == HA_ADDON_PREFIX)
  then
    HA_COM_ParseMessage(arg4,arg2);
  
  -- Spell Events
  elseif(event == "SPELLCAST_START")
  then
    _HA_ProcessEvent_SpellCastStart();
  elseif(event == "SPELLCAST_STOP")
  then
    _HA_ProcessEvent_SpellCastStop();
  elseif(event == "SPELLCAST_DELAYED")
  then
    if(HA_CastingSpell)
    then
      HA_GUI_Process_SpellDelayed(HA_PlayerName,arg1);
      HA_COM_SpellDelayed(arg1);
    end
  elseif(event == "CHAT_MSG_SPELL_FAILED_LOCALPLAYER")
  then
    local _,_,spell,reason = string.find(arg1,HA_PARSE_SPELL_FAILED_REASON);
    if(reason == nil) then reason = "???"; end;
    HA_ChatDebug(HA_DEBUG_ACTIONS,"SPELLCAST_FAILED : ParsedSpell="..tostring(spell).." Casting="..tostring(HA_CastingSpell).." Instant="..tostring(HA_CastingInstantSpell).." Reason="..reason);
    if(reason ~= SPELL_FAILED_SPELL_IN_PROGRESS and HA_CastingSpell and HA_CastingSpell == spell)
    then
      local ispell = _HA_GetISpell(HA_CastingSpell);
      local ireason,sreason = _HA_GetReasonCode(reason);
      _HA_DoStop(true,"FAILED");
      if(HA_StopCommandSent)
      then
        HA_GUI_Process_SpellFailed(HA_PlayerName,ispell,ireason,sreason);
        HA_COM_SpellFailed(ispell,ireason,sreason);
      end
    end
    _HA_CheckSpellRequestFailed(spell,reason);
    HA_CastingInstantSpell = nil;
  else
    HA_findEventMatch(event,_HA_RegExpr_CallBack); -- Check remaing events
  end
end

--------------- Exported functions ---------------

--[[
  Function HA_QuerySpell
   - SpellName  : String - Spell to request.
   - PlayerName : String - Player to request spell to.
  Sends a request for spell to this player.
  Returns true is spell was found, ready, and queried. False otherwise.
]]
function HA_QuerySpell(SpellName,PlayerName)
  local healer = HA_Healers[PlayerName];
  if(healer)
  then
    local spell = healer.Cooldown[SpellName];
    if(spell and spell.Remain == 0)
    then
      HA_COM_SpellRequest(spell.ispell,PlayerName);
      return true;
    else
      if(spell == nil)
      then
        HA_ChatPrint("HA_QuerySpell Failed : "..SpellName.." is not in "..PlayerName.."'s list of spells");
      else
        HA_ChatPrint("HA_QuerySpell Failed : "..SpellName.." is not ready ("..spell.Remain.." sec remaining)");
      end
    end
  else
    HA_ChatPrint("HA_QuerySpell Failed : "..PlayerName.." is not in my group/raid");
  end
  return false;
end

--[[
  Function HA_ShowVersions
  Prints HA's version for all Healers in your group/raid (if available).
]]
function HA_ShowVersions()
  for name,raider in HA_Raiders
  do
    if(raider.Version)
    then
      HA_ChatPrint(name.." : "..raider.Version);
    end
  end
end


--------------- Init functions ---------------

local function _HA_CheckSpellCooldown(spell,ispell,rank)
  local id = HA_GetSpellIDFromName(spell,rank);
  if(id == 0 and type(rank) == "number")
  then
    while(id == 0)
    do
      rank = rank - 1;
      if(rank == 0) -- Really don't have this spell
      then
        break;
      end
      id = HA_GetSpellIDFromName(spell,rank);
    end
  end
  if(id ~= 0)
  then
    HA_SpellCooldowns[spell] = { ispell=ispell; id=id; last=0; lastSend=0 };
    HA_ChatDebug(HA_DEBUG_SPELLS,"_HA_CheckSpellCooldown : Found spell "..spell.." rank "..tostring(rank).." as ID "..id);
    return true;
  end
  return false;
end

local function _HA_CheckTalents(talentids)
  local numTabs = GetNumTalentTabs();
  for t=1, numTabs
  do
    local numTalents = GetNumTalents(t);
    for i=1, numTalents
    do
      local name,icon,x,y,rank,max = GetTalentInfo(t,i);
      for _,tid in talentids
      do
        if(HA_Talents[tid] and HA_Talents[tid].texture == icon)
        then
          HA_Talents[tid].rank = rank;
          HA_ChatDebug(HA_DEBUG_SPELLS,"_HA_CheckTalents : Found talent "..name.." at rank "..rank.."/"..max);
        end
      end
    end
  end
end

function HA_InitializeCooldownSpells()
  -- Reset infos
  HA_SpellCooldowns = {};
  for id,tab in HA_Talents
  do
    tab.rank = nil;
  end

  -- Fill infos
  local _,clas = UnitClass("player");
  if(clas == "DRUID")
  then
    _HA_CheckSpellCooldown(HA_INNERVATE,HA_SPELL_INNERVATE);
    _HA_CheckSpellCooldown(HA_REBIRTH,HA_SPELL_REBIRTH,5);
    _HA_CheckTalents({HA_TALENT_GIF_OF_NATURE,HA_TALENT_IMPROVED_REJUVINATION});
  elseif(clas == "PALADIN")
  then
    _HA_CheckSpellCooldown(HA_DIVINE_INTERVENTION,HA_SPELL_DIVINE_INTERVENTION);
    _HA_CheckSpellCooldown(HA_BLESSING_OF_PROTECTION,HA_SPELL_BLESSING_OF_PROTECTION,3);
    _HA_CheckTalents({HA_TALENT_HEALING_LIGHT});
  elseif(clas == "SHAMAN")
  then
    _HA_CheckSpellCooldown(HA_REINCARNATION,HA_SPELL_REINCARNATION,HA_PASSIVE);
    _HA_CheckSpellCooldown(HA_MANA_TIDE,HA_SPELL_MANA_TIDE,2);
    _HA_CheckTalents({HA_TALENT_PURIFICATION});
  elseif(clas == "PRIEST")
  then
    _HA_CheckSpellCooldown(HA_LIGHTWELL,HA_SPELL_LIGHTWELL,3);
    _HA_CheckSpellCooldown(HA_POWER_INFUSION,HA_SPELL_POWER_INFUSION);
    _HA_CheckTalents({HA_TALENT_SPIRITUAL_HEALING,HA_TALENT_IMPROVED_RENEW,HA_TALENT_SPIRITUAL_GUIDANCE});
  end
end

function HA_OnLoad()
  -- Print init message
  HA_ChatPrint("Version "..HA_VERSION.." "..HA_CHAT_MISC_LOADED);

  -- Register events
  this:RegisterEvent("VARIABLES_LOADED");
  this:RegisterEvent("PLAYER_LOGIN");
  this:RegisterEvent("PLAYER_ENTERING_WORLD");
  this:RegisterEvent("PLAYER_LEAVING_WORLD");
  this:RegisterEvent("ZONE_CHANGED_NEW_AREA");

  this:RegisterEvent("UNIT_COMBAT");
  this:RegisterEvent("UNIT_HEALTH");
  this:RegisterEvent("UNIT_MAXHEALTH");
  this:RegisterEvent("UNIT_MANA");
  this:RegisterEvent("UNIT_MAXMANA");
  this:RegisterEvent("PLAYER_AURAS_CHANGED");
  this:RegisterEvent("UPDATE_MOUSEOVER_UNIT");
  this:RegisterEvent("PLAYER_TARGET_CHANGED");

  this:RegisterEvent("CHAT_MSG_SYSTEM");
  this:RegisterEvent("CHAT_MSG_ADDON");

  this:RegisterEvent("SPELLCAST_START");
  this:RegisterEvent("SPELLCAST_STOP");
  this:RegisterEvent("SPELLCAST_DELAYED");
  this:RegisterEvent("CHAT_MSG_SPELL_SELF_BUFF");
  this:RegisterEvent("CHAT_MSG_SPELL_PARTY_BUFF");
  this:RegisterEvent("CHAT_MSG_SPELL_FRIENDLYPLAYER_BUFF");
  this:RegisterEvent("CHAT_MSG_SPELL_HOSTILEPLAYER_BUFF");
  this:RegisterEvent("CHAT_MSG_SPELL_FAILED_LOCALPLAYER");
  this:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_SELF_BUFFS"); -- Self hots
  this:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_PARTY_BUFFS"); -- Group hots
  this:RegisterEvent("CHAT_MSG_SPELL_PERIODIC_FRIENDLYPLAYER_BUFFS"); -- Raid hots

  HealersAssistTitle:SetText("HealersAssist v"..HA_VERSION);
  -- Initialize Slash commands
  SLASH_HA1 = "/ha";
  SlashCmdList["HA"] = function(msg)
    HA_Commands(msg);
  end
  
  tinsert(UISpecialFrames, "HAConfFrame");

  -- Initialiaze RegExprs
  HA_CreateRegexFromGlobals();

  -- Build ISpell structure
  HA_BuildLocalNames();

  -- Check for GroupAnalyse AddOn
  if(GA_RegisterForEvents ~= nil)
  then
    GA_RegisterForEvents(_HA_GroupAnalyseCallback);
  else
    this:RegisterEvent("PARTY_MEMBERS_CHANGED");
    this:RegisterEvent("RAID_ROSTER_UPDATE");
  end
  -- Check for BonusScanner AddOn
  if(BonusScanner and BONUSSCANNER_VERSION and BONUSSCANNER_VERSION >= "v1.0")
  then
    _HA_UseBonusScannerAddon = true;
  end
end


--/script HA_RequestSpell("Blessing of Protection")
--/script SendAddonMessage("HealAss","<HA7>09"..'\30'.."16"..'\30'.."Kiki","RAID")
