vanilla-wow-addons – Rev 1

Subversion Repositories:
Rev:
-- 09/04/2005 12:30:49 PIng: Replace hardcoded strings for UI by localized variables
-- 09/04/2005 12:31:41 PIng: Bug fix. Replace ShM_PrintMessage by SkM_PrintMessage
-- 09/04/2005 15:52:09 PIng: Add guild support
-- 09/04/2005 16:18:34 PIng: Update war info on target frame when status is changed

SKM_UNIT_PLAYER = "player";
SKM_UNIT_TARGET = "target";
SKM_UNIT_MOUSEOVER = "mouseover";
SKM_UNIT_PARTY = "party";
SKM_UNIT_PET = "pet";

SKM_UNIT_PARTY_1 = SKM_UNIT_PARTY.."1";
SKM_UNIT_PARTY_2 = SKM_UNIT_PARTY.."2";
SKM_UNIT_PARTY_3 = SKM_UNIT_PARTY.."3";
SKM_UNIT_PARTY_4 = SKM_UNIT_PARTY.."4";



_RealmName = nil;
_PlayerName = nil;


SKM_MAX_MAP_NOTES       = 200;


local TXT_NIL = "nil";

local SKM_MESSAGE_PREFIX = "SKM: ";

local SKM_TRACE_MODE_NONE = 0;
local SKM_TRACE_MODE_PRINT = 1;
local SKM_TRACE_MODE_CHATMSG = 2;

local SKM_TRACE_NIL = false;

local TXT_NIL = "nil";

--local SKM_TRACE_MODE = SKM_TRACE_MODE_NONE;
--local SKM_TRACE_MODE = SKM_TRACE_MODE_PRINT;
local SKM_TRACE_MODE = SKM_TRACE_MODE_CHATMSG;



-- number of days in a month for a non leap year
local DaysInMonth = { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };

-- list of leap years for the first half of the 21st century
local LeapYears = { 2000, 2004, 2008, 2012, 2016, 2020, 2024, 2028, 2032, 2036, 2040, 2044, 2048 };



-- List of ennemy player factions races (alliance and horde)
-- (could easily add more by altering the list, though it probably won't happen !)
SKM_PlayerFaction = {
  {     Faction = "Alliance",
        RaceList = { SKM_RACE.Dwarf, SKM_RACE.Gnome, SKM_RACE.Human, SKM_RACE.NightElf }
  };

  {     Faction = "Horde",
        RaceList = { SKM_RACE.Orc, SKM_RACE.Tauren, SKM_RACE.Troll, SKM_RACE.Undead }
  };
};

SKM_HonorKillPerDay = 4;


SKM_ToStandardCase = {
        ["A"] = { "\195\128", "\195\129", "\195\130", "\195\131", "\195\132", "\195\133" },
        ["E"] = { "\195\136", "\195\137", "\195\138", "\195\139"},
        ["I"] = { "\195\140", "\195\141", "\195\142", "\195\143"},
        ["O"] = { "\195\146", "\195\147", "\195\148", "\195\149", "\195\150", "\195\152" },
        ["U"] = { "\195\153", "\195\154", "\195\155", "\195\156" },
        ["Y"] = { "\195\157", "\195\191", "\197\184" },

        ["C"] = { "\195\135" },
        ["D"] = { "\195\144" },
        ["N"] = { "\195\145" },
        ["S"] = { "\197\160", "\197\161"}
};


SKM_GuildChannelPrefix = "SKM";



-- OLD table indexes - before data migration - SKMap 1.4
-- -----------------------------------------------------
_SKM_OLD = {
        _name = "name";
        _class = "class";
        _guild = "guild";
        _race = "race";
        _level = "level";

        _playerNote     = "playerNote";

        _playerKill = "playerKill";
        _playerAssistKill = "playerAssistKill";
        _playerFullKill = "playerFullKill";

        _honorKill = "honorKill";
        _honorCount = "honorCount";
        _honorLastKill = "honorLastKill";
        _rank = "rank";

        _meetCount = "meetCount";
        _atWar = "atWar";
--      _guildAtWar = "guildAtWar";
        _warDate = "warDate";

        _continent = "continent";
        _zone = "zone";
        _xPos = "xPos";
        _yPos = "yPos";
        _zoneName = "zoneName";

--      _lastUpdate     = "lastUpdate";
        _lastView = "lastView";
        _lastPlayerViewed = "lastPlayerViewed";

        _enemyKillPlayer = "enemyKillPlayer";
        _enemyKillBG = "enemyKillBG";
        _playerBGKill = "playerBGKill";

        _members = "members";

        _type = "type";
        _date = "date";
        _enemyType = "enemyType";
        _enemyPlayer = "enemyPlayer";
        _enemyCreature = "enemyCreature";

        _playerDeath = "playerDeath";
        _playerDeathPvP = "playerDeathPvP";
        _playerDeathPvE = "playerDeathPvE";
        _creatureKill_Target = "creatureKill_Target";
        _creatureKill_Xp = "creatureKill_Xp";
        _levelUp = "levelUp";

        _loneWolfKill = "loneWolfKill";

        _storedInfo     = "storedInfo";

        _win = "win";
        _loss = "loss";
        _duel = "duel";
        _lastDuel = "lastDuel";
--      _score = "score";
};

-- new table indexes - after data migration - SKMap 1.4
-- ----------------------------------------------------
_SKM = {
        _name = "Na";
        _class = "Cl";
        _guild = "Gu";
        _race = "Ra";
        _level = "Lv";

        _playerNote     = "PlN";

        _playerKill = "PK";
        _playerAssistKill = "PaK";
        _playerFullKill = "PfK";

        _honorKill = "hK";
        _honorCount = "hC";
        _honorLastKill = "hLK";
        _rank = "Rk";

        _meetCount = "mC";
        _atWar = "Wr";
        _guildAtWar = "gWr"; -- not saved
        _warDate = "WD";

        _continent = "Co";
        _zone = "Zo";
        _xPos = "x";
        _yPos = "y";
        _zoneName = "ZN";

        _lastUpdate     = "lU"; -- not saved
        _lastView = "lV";
        _lastPlayerViewed = "lPV";


        _enemyKillPlayer = "EKP";
        _enemyKillBG = "EKb";
        _playerBGKill = "PbK";

        _members = "Mb"; -- not saved

        _type = "Ty";
        _date = "Da";
        _enemyType = "ETy";
        _enemyPlayer = "EPl";
        _enemyCreature = "ECr";

        _playerDeath = "PD";
        _playerDeathPvP = "PDp";
        _playerDeathPvE = "PDc";
        _creatureKill_Target = "CKt";
        _creatureKill_Xp = "CKx";
        _levelUp = "LvU";

        _loneWolfKill = "LwK";

        _storedInfo     = "Inf";

        _win = "Win";
        _loss = "Los";
        _duel = "Du";
        _lastDuel = "lDu";
        _score = "Scr"; -- not saved


        -- following indexes are for temporary use (ie not saved in SavedVariables.lua, so it's
        -- not as important if they're a bit longer)

        _noteIndex = "NIn";
        _default = "Def";
        _multiType = "MTy";
        _playerKillAndDeath     = "PKaD";

        _totalDamage = "tDm";
        _groupDamage = "gDm";
        _lastHateUpdate = "lHU";
        _hateLevel = "HLv";
        _damage = "Dm";
        _hatePercent = "HPct";

        _time = "Ti";

        _owner = "Own";
        _player = "Ply";
        _other = "Oth";

        _players = "players";
        _guilds = "guilds";
        _duels = "duels";

        _bookCredits = "bookCredits";
        _bookGeneralStat = "bookGeneralStat";
        _bookClassStat = "bookClassStat";
        _bookRaceStat = "bookRaceStat";
        _bookPlayerStat = "bookPlayerStat";
        _bookGuildStat = "bookGuildStat";
        _bookMapStat = "bookMapStat";
        _bookDateStat = "bookDateStat";

        _bookBGDateMapStat = "bookBGDateMapStat";
        _bookBGDateStat = "bookBGDateStat";
        _bookBGMapStat = "bookBGMapStat";

        _checkButton = "CBtn";
        _slider = "Slid";

};



SKM_IndexMigr = {
        [2] = {
                EnemyHistory = {
                        {Old=_SKM_OLD._name, New=_SKM._name},
                        {Old=_SKM_OLD._class, New=_SKM._class},
                        {Old=_SKM_OLD._guild, New=_SKM._guild},
                        {Old=_SKM_OLD._race, New=_SKM._race},
                        {Old=_SKM_OLD._level, New=_SKM._level},
                        {Old=_SKM_OLD._playerNote, New=_SKM._playerNote},
                        {Old=_SKM_OLD._playerKill, New=_SKM._playerKill},
                        {Old=_SKM_OLD._playerAssistKill, New=_SKM._playerAssistKill},
                        {Old=_SKM_OLD._playerFullKill, New=_SKM._playerFullKill},
                        {Old=_SKM_OLD._honorKill, New=_SKM._honorKill},
                        {Old=_SKM_OLD._honorCount, New=_SKM._honorCount},
                        {Old=_SKM_OLD._honorLastKill, New=_SKM._honorLastKill},
                        {Old=_SKM_OLD._rank, New=_SKM._rank},
                        {Old=_SKM_OLD._meetCount, New=_SKM._meetCount},
                        {Old=_SKM_OLD._atWar, New=_SKM._atWar},
                        {Old=_SKM_OLD._warDate, New=_SKM._warDate},
                        {Old=_SKM_OLD._continent, New=_SKM._continent},
                        {Old=_SKM_OLD._zone, New=_SKM._zone},
                        {Old=_SKM_OLD._xPos, New=_SKM._xPos},
                        {Old=_SKM_OLD._yPos, New=_SKM._yPos},
                        {Old=_SKM_OLD._zoneName, New=_SKM._zoneName},
        --              {Old=_SKM_OLD._lastUpdate, New=_SKM._lastUpdate},
                        {Old=_SKM_OLD._lastView, New=_SKM._lastView},
                        {Old=_SKM_OLD._enemyKillPlayer, New=_SKM._enemyKillPlayer},
                        {Old=_SKM_OLD._enemyKillBG, New=_SKM._enemyKillBG},
                        {Old=_SKM_OLD._playerBGKill, New=_SKM._playerBGKill},
                };
                GuildHistory = {
                        {Old=_SKM_OLD._name, New=_SKM._name},
        --              {Old=_SKM_OLD._members, New=_SKM._members},
                        {Old=_SKM_OLD._meetCount, New=_SKM._meetCount},
                        {Old=_SKM_OLD._atWar, New=_SKM._atWar},
                        {Old=_SKM_OLD._warDate, New=_SKM._warDate},
                        {Old=_SKM_OLD._playerKill, New=_SKM._playerKill},
                        {Old=_SKM_OLD._playerAssistKill, New=_SKM._playerAssistKill},
                        {Old=_SKM_OLD._playerFullKill, New=_SKM._playerFullKill},
                        {Old=_SKM_OLD._enemyKillPlayer, New=_SKM._enemyKillPlayer},
                        {Old=_SKM_OLD._lastView, New=_SKM._lastView},
                        {Old=_SKM_OLD._lastPlayerViewed, New=_SKM._lastPlayerViewed},
                };
                GlobalMapData = {
                        {Old=_SKM_OLD._continent, New=_SKM._continent},
                        {Old=_SKM_OLD._zone, New=_SKM._zone},
                        {Old=_SKM_OLD._xPos, New=_SKM._xPos},
                        {Old=_SKM_OLD._yPos, New=_SKM._yPos},
                        {Old=_SKM_OLD._storedInfo, New=_SKM._storedInfo},
                };
                StoredInfo = {
                        {Old=_SKM_OLD._type, New=_SKM._type},
                        {Old=_SKM_OLD._date, New=_SKM._date},
                        {Old=_SKM_OLD._name, New=_SKM._name},
                        {Old=_SKM_OLD._level, New=_SKM._level},
                        {Old=_SKM_OLD._enemyType, New=_SKM._enemyType},
                        {Old=_SKM_OLD._loneWolfKill, New=_SKM._loneWolfKill},
                        {Old=_SKM_OLD._honorKill, New=_SKM._honorKill},
                };
                EnemyType = {
                        [_SKM_OLD._enemyPlayer] = _SKM._enemyPlayer;
                        [_SKM_OLD._enemyCreature] = _SKM._enemyCreature;
                };
                RecordType = {

                        [_SKM_OLD._playerKill] = _SKM._playerKill;
                        [_SKM_OLD._playerAssistKill] = _SKM._playerAssistKill;
                        [_SKM_OLD._playerFullKill] = _SKM._playerFullKill;
                        [_SKM_OLD._playerDeath] = _SKM._playerDeath;
                        [_SKM_OLD._playerDeathPvP] = _SKM._playerDeathPvP;
                        [_SKM_OLD._playerDeathPvE] = _SKM._playerDeathPvE;
                        [_SKM_OLD._creatureKill_Target] = _SKM._creatureKill_Target;
                        [_SKM_OLD._creatureKill_Xp] = _SKM._creatureKill_Xp;
                        [_SKM_OLD._levelUp] = _SKM._levelUp;
                };
                DuelHistory = {
                        {Old=_SKM_OLD._name, New=_SKM._name},
                        {Old=_SKM_OLD._class, New=_SKM._class},
                        {Old=_SKM_OLD._guild, New=_SKM._guild},
                        {Old=_SKM_OLD._race, New=_SKM._race},
                        {Old=_SKM_OLD._level, New=_SKM._level},
                        {Old=_SKM_OLD._playerNote, New=_SKM._playerNote},
                        {Old=_SKM_OLD._win, New=_SKM._win},
                        {Old=_SKM_OLD._loss, New=_SKM._loss},
                        {Old=_SKM_OLD._duel, New=_SKM._duel},
                        {Old=_SKM_OLD._lastDuel, New=_SKM._lastDuel},
                };
                BGStatDate = {
                        {Old=_SKM_OLD._enemyKillBG, New=_SKM._enemyKillBG},
                        {Old=_SKM_OLD._playerBGKill, New=_SKM._playerBGKill},
                };
        };

};







--_time                 = "time";

--_type                 = "type";

--_name                 = "name";
--_class                        = "class";
--_guild                        = "guild"; -- PIng: Add guild support
--_race                 = "race";
--_level                        = "level";
--_date                 = "date";
--_sortdate             = "sortdate";

--_killedBy             = "killBy";

--_playerNote           = "playerNote";

--_noteIndex = "noteIndex";

--_icon_PlayerKill      = "icon_PlayerKill";
--_icon_PlayerDeath     = "icon_PlayerDeath";

-- type of map events
--_playerKill           = "playerKill";
--_playerAssistKill     = "playerAssistKill";
--_playerFullKill       = "playerFullKill";
--_playerDeath          = "playerDeath";
--_playerDeathPvP               = "playerDeathPvP";
--_playerDeathPvE               = "playerDeathPvE";
--_creatureKill_Target  = "creatureKill_Target";
--_creatureKill_Xp      = "creatureKill_Xp";
--_levelUp              = "levelUp";

--_loneWolfKill         = "loneWolfKill";

--_honorKill = "honorKill";
--_honorCount = "honorCount";
--_honorLastKill = "honorLastKill";
--_rank = "rank";

--_win = "win";
--_loss = "loss";
--_duel = "duel";
--_lastDuel = "lastDuel";
--_score = "score";

--_default              = "default";
--_multiType            = "multiType";
--_playerKillAndDeath   = "playerKillAndDeath";

--_enemyKillPlayer      = "enemyKillPlayer";

--_enemyKillBG = "enemyKillBG";
--_playerBGKill = "playerBGKill";


--_meetCount            = "meetCount";
--_atWar                        = "atWar";
--_guildAtWar           = "guildAtWar";
--_warDate              = "warDate";

--_continent            = "continent";
--_zone                 = "zone";
--_xPos                 = "xPos";
--_yPos                 = "yPos";
--_icon                 = "icon";
--_storedInfo           = "storedInfo";

--_zoneName             = "zoneName";


--_lastUpdate           = "lastUpdate";
--_totalDamage          = "totalDamage";
--_groupDamage          = "groupDamage";

--_lastHateUpdate               = "lastHateUpdate";
--_hateLevel            = "hateLevel";
--_enemyType            = "enemyType";
--_damage                       = "damage";

--_lastView             = "lastView";
--_lastPlayerViewed     = "lastPlayerViewed";

--_enemyPlayer          = "enemyPlayer";
--_enemyCreature                = "enemyCreature";

--_owner                        = "owner";
--_player                       = "player";
--_other                        = "other";

--_players              = "players";
--_guilds                       = "guilds";
--_duels                        = "duels";
--_members              = "members";

--_bookCredits          = "bookCredits";
--_bookGeneralStat      = "bookGeneralStat";
--_bookClassStat                = "bookClassStat";
--_bookRaceStat         = "bookRaceStat";
--_bookPlayerStat               = "bookPlayerStat";
--_bookGuildStat                = "bookGuildStat";
--_bookMapStat          = "bookMapStat";
--_bookDateStat         = "bookDateStat";

--_bookBGDateMapStat    = "bookBGDateMapStat";
--_bookBGDateStat               = "bookBGDateStat";
--_bookBGMapStat                = "bookBGMapStat";


--_checkButton  = "checkButton";

--_slider = "slider";





function SkM_TableInsertMaxLengthLine(Lines, sLine, iMaxLen, iThreshold, sColorPrefix)
        local FName = "SkM_TableInsertMaxLengthLine";
        local sTmp = sLine;
        local SeparList = {" ", "-", ".", ",", ":", ";"};
        local sColor = "";
        if (sColorPrefix) then
                sColor = sColorPrefix;
        end
        while (string.len(sTmp) > 0) do
                if (string.len(sTmp) <= iMaxLen) then
                        table.insert(Lines, sColor..sTmp);
                        sTmp = "";
                else
                        local iPos = iMaxLen;
                        local sChar = string.sub(sTmp, iPos, iPos);
                        while (iPos > iMaxLen - iThreshold) and not (intable(sChar, SeparList)) do
                                iPos = iPos - 1;
                                sChar = string.sub(sTmp, iPos, iPos);
                        end
                        if (intable(sChar, SeparList)) then
                                table.insert(Lines, sColor..string.sub(sTmp, 1, iPos));
                                sTmp = string.sub(sTmp, iPos+1, string.len(sTmp));
                        else
                                table.insert(Lines, sColor..string.sub(sTmp, 1, iMaxLen));
                                sTmp = string.sub(sTmp, iMaxLen+1, string.len(sTmp));
                        end
                end
        end

end



-- --------------------------------------------------------------------------------------
-- ifnil
-- --------------------------------------------------------------------------------------
-- Return input value if it's not nil, else return alternative value specified.
-- --------------------------------------------------------------------------------------
function ifnil( iVal, iNewVal)
        if ( iVal == nil) then
                return iNewVal;
        else
                return iVal;
        end
end


-- --------------------------------------------------------------------------------------
-- snil
-- --------------------------------------------------------------------------------------
-- Return the input string, or the string "nil" if the variable is nil
-- --------------------------------------------------------------------------------------
function snil_old( Val )
        if ( Val == nil ) then
                return TXT_NIL;
        else
                return Val;
        end
end

function snil( Val )
        if ( Val == nil ) then
                return TXT_NIL;
        else
                if ( Val == true) then
                        return "true";
                elseif ( Val == false) then
                        return "false";
                else
                        return Val;
                end
        end
end


-- --------------------------------------------------------------------------------------
-- copytable
-- --------------------------------------------------------------------------------------
-- Make a copy of a table
-- --------------------------------------------------------------------------------------
function copytable(MyTable)
        if (type(MyTable) ~= "table" ) then
                return MyTable;
        end
        local idx, val;
        local NewTable = {};
        for idx, val in MyTable do
                NewTable[idx] = copytable(val);
        end
        return NewTable;
end


-- --------------------------------------------------------------------------------------
-- intable
-- --------------------------------------------------------------------------------------
-- Return true if "Val" is in table "TheTable"
-- --------------------------------------------------------------------------------------
function intable( Val, TheTable)
        local idx, TableValue;
        for idx, TableValue in TheTable do
                if Val == TableValue then
                        return true;
                end
        end
        return false;
end


-- remove "Val" if found in list "TheTable"
-- return resulting list
function removefromlist( Val, TheTable )
        local MyTable = TheTable;
        local iSize = table.getn(MyTable);
        local i = 1;
        while (i <= iSize) do
                if (MyTable[i] == Val) then
                        table.remove(MyTable, i);
                        iSize = iSize - 1;
                else
                        i = i + 1;
                end
        end
        return MyTable;
end


-- --------------------------------------------------------------------------------------
-- appendtables
-- --------------------------------------------------------------------------------------
-- Append several list tables and returns the resulting table.
-- Can take single elements also, in that case convert them to tables.
-- --------------------------------------------------------------------------------------
function appendtables( ... )

        local TableRes = { };
        local iNbArg = arg.n;
        local i;

        for i=1, iNbArg do
                local MyTable;
                local Table = arg[i];
                if (type(Table) ~= "table" ) then
                        MyTable = { Table };
                else
                        MyTable = Table;
                end

                local idx, TableValue;
                for idx, TableValue in MyTable do
                        table.insert(TableRes, TableValue);
                end
        end

        return TableRes;
end


-- --------------------------------------------------------------------------------------
-- mergetables
-- --------------------------------------------------------------------------------------
-- Merge two list tables and returns the resulting table.
-- If both tables contain the same indexes, the first tables values will be returned for
-- this index.
-- Recursively merge sub tables if needed.
-- --------------------------------------------------------------------------------------
function mergetables(Table1, Table2)

        if (type(Table1) ~= "table") and (type(Table2) ~= "table") then
                return Table1;
        end
        if (type(Table1) ~= "table") then
                return Table2;
        end
        if (type(Table2) ~= "table") then
                return Table1;
        end

        local idx, TableValue;
        local TableRes = Table1;
        for idx, TableValue in Table2 do
                if (not Table1[idx]) then
                        TableRes[idx] = TableValue;
                else
                        TableRes[idx] = mergetables(Table1[idx], TableValue);

                end
        end

        return TableRes;
end


-- --------------------------------------------------------------------------------------
-- revlist
-- --------------------------------------------------------------------------------------
-- Returns a list in reverse order
-- --------------------------------------------------------------------------------------
function revlist(ListIn)
        local ListOut = {};
        local MyList;

        if (type(ListIn) ~= "table" ) then
                MyList = { ListIn };
        else
                MyList = ListIn;
        end

        local idx, TableValue;
        for idx, TableValue in MyList do
                table.insert(ListOut, 1, TableValue);
        end

        return ListOut;
end


function tablesize(Table)
        local idx, val
        local iSize = 0
        for idx, val in Table do
                iSize = iSize + 1;
        end
        return iSize;
end



-- **************************************************************************************
-- Logs, debug and display functions
-- **************************************************************************************

-- --------------------------------------------------------------------------------------
-- SkM_Trace
-- --------------------------------------------------------------------------------------
-- Debug function. Display information on screen, or on the default chat frame.
--   FName = calling function name
--   Level = level of trace, used to check if this debug will be displayed
--   Message = message to trace
-- --------------------------------------------------------------------------------------
function SkM_Trace( FName, Level, Message )
        local OutMessage;

        if (Level <= SKM_Config.DebugLevel) then

                if (SKM_Config.DebugMaxFuncLen) and (string.len(FName) > SKM_Config.DebugMaxFuncLen) then
                        if (SKM_Config.DebugMaxFuncLen < 10) then
                                OutMessage = string.sub(FName, 1, SKM_Config.DebugMaxFuncLen).."/"..snil(Message);
                        else
                                OutMessage = string.sub(FName, 1, SKM_Config.DebugMaxFuncLen - 7)..".."
                                             ..string.sub(FName, string.len(FName)-4, string.len(FName)).."/"..snil(Message);
                        end
                else
                        OutMessage = FName.."/"..snil(Message);
                end

                if (SKM_Config.RecordDebug) then
                        local sDate = SkM_GetDate();
                        local sRecordMsg = "["..sDate.."]<"..Level.."> "..snil(FName).."/"..snil(Message);
                        table.insert(SKM_Debug, sRecordMsg);

                        SKM_Context.RecLines = SKM_Context.RecLines + 1;
                        SKM_Context.TmpRecLines = SKM_Context.TmpRecLines + 1;
                        if (SKM_Context.TmpRecLines >= SKM_Config.RecordIntervalInfo) then
                                SkM_ChatMessageCol("Lines recorded : "..SKM_Context.RecLines.." (Total : "..table.getn(SKM_Debug)..")");
                                SKM_Context.TmpRecLines = 0;
                        end

                        return;
                end

                if (SKM_TRACE_MODE == SKM_TRACE_MODE_PRINT) then
                        print(OutMessage);
                elseif (SKM_TRACE_MODE == SKM_TRACE_MODE_CHATMSG) then
                        if not DEFAULT_CHAT_FRAME then
                                return;
                        end
                        local r = SKM_Config.RGB_Debug[Level].r;
                        local g = SKM_Config.RGB_Debug[Level].g;
                        local b = SKM_Config.RGB_Debug[Level].b;
                        SkM_PrintMessage(OutMessage, r, g, b);  -- PIng: Bug Fix.Replace ShM_PrintMessage by SkM_PrintMessage
                end
        end
end


-- --------------------------------------------------------------------------------------
-- SkM_PrintMessage
-- --------------------------------------------------------------------------------------
-- Display a message in a frame in a specified RGB color, calling Blizz function
-- <frame>:AddMessage.
-- I don't know what the two last parameters stand for :/
-- --------------------------------------------------------------------------------------
function SkM_PrintMessage(msg, r, g, b, frame, id, unknown4th)
        local OutMessage;

        if(unknown4th) then
                local temp = id;
                id = unknown4th;
                unknown4th = id;
        end

        OutMessage = snil(msg);

        if (not r) then r = 1.0; end
        if (not g) then g = 1.0; end
        if (not b) then b = 1.0; end
        if ( frame ) then
                frame:AddMessage(OutMessage, r, g, b, id, unknown4th);
        else
                if ( DEFAULT_CHAT_FRAME ) then
                        DEFAULT_CHAT_FRAME:AddMessage(OutMessage, r, g, b, id, unknown4th);
                end
        end
end


-- --------------------------------------------------------------------------------------
-- SkM_ChatMessageCol
-- --------------------------------------------------------------------------------------
-- Display a message on the default chat frame, with the module prefix.
-- Color is the module global chat color, if it has been set (default : white).
-- --------------------------------------------------------------------------------------
function SkM_ChatMessageCol( Message )
        local OutMessage;
        local r = SKM_Config.RGB_Msg.r;

        local g = SKM_Config.RGB_Msg.g;
        local b = SKM_Config.RGB_Msg.b;

        if not DEFAULT_CHAT_FRAME then
                return;
        end
        OutMessage = SKM_MESSAGE_PREFIX..snil(Message);
        SkM_PrintMessage(OutMessage, r, g, b);
end


-- --------------------------------------------------------------------------------------
-- SkM_ChatMessageColP
-- --------------------------------------------------------------------------------------
-- Display a message on the default chat frame, with the module prefix.
-- RBGTriplet specifies the text color.
-- --------------------------------------------------------------------------------------
function SkM_ChatMessageColP( Message, RGBTriplet )
        local OutMessage;
        local r = RGBTriplet.r;
        local g = RGBTriplet.g;
        local b = RGBTriplet.b;

        if not DEFAULT_CHAT_FRAME then
                return;
        end
        OutMessage = SKM_MESSAGE_PREFIX..snil(Message);
        SkM_PrintMessage(OutMessage, r, g, b);
end







-- --------------------------------------------------------------------------------------
-- SkM_Initialize
-- --------------------------------------------------------------------------------------
-- Module initialization
-- --------------------------------------------------------------------------------------
function SkM_Initialize()

        SKM_Context = {

                MapOpen = false;

                -- record damage done by player to enemies
                EnemyCombat = { };

                -- record damage done by enemies to player
                PlayerCombat = { };


                -- record recent enemy kills
                RecentEnemyKill = { };

                -- record recent WAR warnings to reduce spam
                RecentWarWarning = { };

                --DuelEnemy = nil;

                -- list of player names in group
                GroupList = { };

                -- list of player names in guild
                GuildList = { };

                -- list of notes associated to a physical POI on the world map
                WorldMapPOINotes = { };

                PlayerAlive = true;

                DataInit = false;

                -- parse patterns
                Pattern = { };

        };

        SKM_Context.Continents = { GetMapContinents() } ;
        SKM_Context.Zones = { };

        for idx, val in SKM_Context.Continents do
                SKM_Context.Zones[idx] = { GetMapZones(idx) } ;
        end

        SkM_BuildParsePatterns();

        SKM_Context.Race = {
                IndexToString = {
                        [1] = SKM_RACE.Dwarf;
                        [2] = SKM_RACE.Gnome;

                        [3] = SKM_RACE.Human;
                        [4] = SKM_RACE.NightElf;
                        [5] = SKM_RACE.Orc;
                        [6] = SKM_RACE.Tauren;

                        [7] = SKM_RACE.Troll;
                        [8] = SKM_RACE.Undead;
                };
                StringToIndex = {
                        [SKM_RACE.Dwarf] = 1;
                        [SKM_RACE.Gnome] = 2;
                        [SKM_RACE.Human] = 3;
                        [SKM_RACE.NightElf] = 4;
                        [SKM_RACE.Orc] = 5;
                        [SKM_RACE.Tauren] = 6;
                        [SKM_RACE.Troll] = 7;
                        [SKM_RACE.Undead] = 8;
                };
        };


        SKM_Context.Class = {
                IndexToString = {
                        [1] = SKM_CLASS.Druid;
                        [2] = SKM_CLASS.Hunter;
                        [3] = SKM_CLASS.Mage;
                        [4] = SKM_CLASS.Paladin;
                        [5] = SKM_CLASS.Priest;
                        [6] = SKM_CLASS.Rogue;
                        [7] = SKM_CLASS.Shaman;
                        [8] = SKM_CLASS.Warrior;
                        [9] = SKM_CLASS.Warlock;
                };
                StringToIndex = {
                        [SKM_CLASS.Druid] = 1;
                        [SKM_CLASS.Hunter] = 2;
                        [SKM_CLASS.Mage] = 3;
                        [SKM_CLASS.Paladin] = 4;
                        [SKM_CLASS.Priest] = 5;
                        [SKM_CLASS.Rogue] = 6;
                        [SKM_CLASS.Shaman] = 7;
                        [SKM_CLASS.Warrior] = 8;
                        [SKM_CLASS.Warlock] = 9;
                };
        };

        SkM_InitData(false);
end


-- --------------------------------------------------------------------------------------
-- SkM_InitData

-- --------------------------------------------------------------------------------------
-- Initialization of data saved across sessions
-- --------------------------------------------------------------------------------------
function SkM_InitData(bForceInit)
        local FName = "SkM_InitData";

        -- context not yet created
        if (not SKM_Context) then
                return false;
        end

        -- initialization already performed
        if (SKM_Context.DataInit == true) and (not bForceInit) then
                return true;
        end

        _PlayerName = SkM_UnitName(SKM_UNIT_PLAYER);
        _RealmName = GetCVar("realmName");

        -- if player name or realm name can't be retrieved yet, abort initialization
        if (not _PlayerName) or (not _RealmName) then
                return false;
        end

        SKM_Context.PlayerName = _PlayerName;
        SKM_Context.RealmName = _RealmName;

        -- store player current level, because we get old level upon event "level up", but I'm
        -- not sure it would always be the case.
        --SKM_Context.PlayerLevel = UnitLevel(SKM_UNIT_PLAYER);
        -- since 1.5 it returns 0 at this point... have to do it later.

        -- global module data
        if (not SKM_Data) then
                SKM_Data = { } ;
        end

        -- for recording debug messages
        if (not SKM_Debug) then
                SKM_Debug = { };
        end

        -- module data for current realm
        if (not SKM_Data[_RealmName]) then
                SKM_Data[_RealmName] = { };
        end

        -- module data for current player of current realm
        if (not SKM_Data[_RealmName][_PlayerName]) then
                SKM_Data[_RealmName][_PlayerName] = { };
                SKM_Data[_RealmName][_PlayerName].PlayerName = SKM_Context.PlayerName;

                local sDate = SkM_GetDate();
                SKM_Data[_RealmName][_PlayerName].InitDate = sDate;
        end
        
        -- dunno when it happenned, but some chars do not have a "PlayerName". Fix it...
        if (not SKM_Data[_RealmName][_PlayerName].PlayerName) then
                SKM_Data[_RealmName][_PlayerName].PlayerName = _PlayerName;                                             
        end

        -- sub maps of player data

        if (not SKM_Data[_RealmName][_PlayerName].GlobalMapData) then
                SKM_Data[_RealmName][_PlayerName].GlobalMapData = { };
        end
        if (not SKM_Data[_RealmName][_PlayerName].MapData) then
                SKM_Data[_RealmName][_PlayerName].MapData = { };

                local idx_c, val_c;
                for idx_c, val_c in SKM_Context.Continents do
                        SKM_Data[_RealmName][_PlayerName].MapData[idx_c] = { };

                        local idx_z, val_z
                        for idx_z, val_z in SKM_Context.Zones[idx_c] do
                                SKM_Data[_RealmName][_PlayerName].MapData[idx_c][idx_z] = { };
                        end
                end
        end

        if (not SKM_Data[_RealmName][_PlayerName].EnemyHistory) then
                SKM_Data[_RealmName][_PlayerName].EnemyHistory = { };
        end

        -- 09/04/2005 16:28:31 PIng: Add Guild support
        if (not SKM_Data[_RealmName][_PlayerName].GuildHistory) then
                SKM_Data[_RealmName][_PlayerName].GuildHistory = { };
        end

        if (not SKM_Data[_RealmName][_PlayerName].UnknownEnemy) then
                SKM_Data[_RealmName][_PlayerName].UnknownEnemy = { };
        end

        if (not SKM_Data[_RealmName][_PlayerName].DuelHistory) then
                SKM_Data[_RealmName][_PlayerName].DuelHistory = { };
        end

        if (not SKM_Data[_RealmName][_PlayerName].BGStats) then
                SKM_Data[_RealmName][_PlayerName].BGStats = { };
        end


        local sGuildName = GetGuildInfo(SKM_UNIT_PLAYER);
        SkM_Trace(FName, 3, "Player guild = "..snil(sGuildName));
        if (sGuildName) and (sGuildName ~= "") and (false) then
        -- build guild list : open guild roster to retrieve guild players list
                GuildRoster();
                --SetGuildRosterShowOffline(1);
        SkM_BuildGuildList();
        --FriendsFrame:Hide();
        HideUIPanel(FriendsFrame);
  end

        -- Global settings
        if (not SKM_Settings) then
                SKM_Settings = { };
        end
        SkM_LoadDefaultSettings(false);

        SkM_RecordVersionHistory();

        SKM_Context.DataInit = true;
        SkM_Trace(FName, 3, "Done");
        return true;
end


-- --------------------------------------------------------------------------------------
-- SkM_LoadDefaultSettings
-- --------------------------------------------------------------------------------------
-- Load (or reload) setting default values from configuration file
-- --------------------------------------------------------------------------------------
function SkM_LoadDefaultSettings(bForceReload)
        local FName = "SkM_LoadDefaultSettings";

        if (not SKM_Settings) then
                SKM_Settings = { };
        end

        if (bForceReload) then

                SkM_Trace(FName, 3, "Force reload default settings");

                for i=1, table.getn(SKM_OPTION_LIST), 1 do
                        local key = SKM_OPTION_LIST[i];
                        SKM_Settings[key] = SKM_Config[key];
                end

        else
                SkM_Trace(FName, 3, "Load missing default settings");

                for i=1, table.getn(SKM_OPTION_LIST), 1 do
                        local key = SKM_OPTION_LIST[i];
                        SkM_Trace(FName, 3, "key = "..key);
                        if (SKM_Settings[key] == nil) then
                                SKM_Settings[key] = SKM_Config[key];
                        end
                end

        end

        if (SKM_Settings.CreatureKillRecordsByZone > SKM_MAX_CREEP_RECORD_BY_ZONE) then
                SKM_Settings.CreatureKillRecordsByZone = SKM_MAX_CREEP_RECORD_BY_ZONE;
        end


        -- for management of data migration. No use for now, but might be needed later on.
        if (SKM_Settings.DataModel == nil) then
                SKM_Settings.DataModel = SKM_VERSION;
        else
                SKM_Settings.DataModel = SKM_VERSION;
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_ResetData
-- --------------------------------------------------------------------------------------
-- Warning : this deletes ALL module data (for all players and realms)
-- Use with caution !
-- --------------------------------------------------------------------------------------
function SkM_ResetData()
        SKM_Data = nil;
        SkM_InitData(true);
end


-- --------------------------------------------------------------------------------------
-- SkM_ResetPlayerData
-- --------------------------------------------------------------------------------------
-- Delete all current player data
-- --------------------------------------------------------------------------------------
function SkM_ResetPlayerData()
        SKM_Data[_RealmName][_PlayerName] = nil;
        SkM_InitData(true);
end


-- --------------------------------------------------------------------------------------
-- SkM_ResetPlayerData
-- --------------------------------------------------------------------------------------
-- Delete current player map data only
-- --------------------------------------------------------------------------------------
function SkM_ResetPlayerMapData()
        SKM_Data[_RealmName][_PlayerName].GlobalMapData = nil;
        SKM_Data[_RealmName][_PlayerName].MapData = nil;
        SkM_InitData(true);
end


-- --------------------------------------------------------------------------------------
-- SkM_ResetAccountMapData
-- --------------------------------------------------------------------------------------
-- Delete all map data on a given account
-- USE WITH CAUTION !!!
-- --------------------------------------------------------------------------------------
function SkM_ResetAccountMapData()
        local FName = "SkM_ResetAccountMapData";

        local idx_realm, val_realm, idx_char, val_char, idx_enemy, val_enemy;

        for idx_realm, val_realm in SKM_Data do
                for idx_char, val_char in SKM_Data[idx_realm] do

                        if (SKM_Data[idx_realm][idx_char].PlayerName == idx_char) then
                                SkM_Trace(FName, 1, "Cleaning for "..idx_realm.." / "..idx_char);

                                -- clean up map data
                                SKM_Data[idx_realm][idx_char].GlobalMapData = nil;
                                SKM_Data[_RealmName][_PlayerName].MapData = nil;

                                -- clean up zone stored in enemy information
                                for idx_enemy, val_enemy in SKM_Data[idx_realm][idx_char].EnemyHistory do
                                        SKM_Data[idx_realm][idx_char].EnemyHistory[idx_enemy][_SKM._continent] = nil;
                                        SKM_Data[idx_realm][idx_char].EnemyHistory[idx_enemy][_SKM._zone] = nil;
                                        SKM_Data[idx_realm][idx_char].EnemyHistory[idx_enemy][_SKM._xPos] = nil;
                                        SKM_Data[idx_realm][idx_char].EnemyHistory[idx_enemy][_SKM._yPos] = nil;
                                end
                        end
                end
        end

        SkM_InitData(true);
end


-- --------------------------------------------------------------------------------------
-- SkM_GetOption
-- --------------------------------------------------------------------------------------
-- Get a player option value
-- --------------------------------------------------------------------------------------
function SkM_GetOption(sConfigKey)
        local FName = "SkM_GetOption";
        if (SKM_Settings == nil) then
                SkM_Trace(FName, 1, "Settings not initialized, can't get value for "..snil(sConfigKey));
                return;
        end
        if (sConfigKey == nil) then
                SkM_Trace(FName, 1, "received nil param");
                return;
        end
        --return (SKM_Data[_RealmName][_PlayerName].Settings[sConfigKey]);
        return (SKM_Settings[sConfigKey]);
end


-- --------------------------------------------------------------------------------------
-- SkM_SetOption
-- --------------------------------------------------------------------------------------
-- Set a player option to a given value
-- --------------------------------------------------------------------------------------
function SkM_SetOption(sConfigKey, Value)
        local FName = "SkM_SetOption";
        if (SKM_Settings == nil) then
                SkM_Trace(FName, 1, "Settings not initialized, can't set value for "..snil(sConfigKey));
                return;
        end
        if (sConfigKey == nil) then
                SkM_Trace(FName, 1, "received nil param");
                return;
        end
        --SKM_Data[_RealmName][_PlayerName].Settings[sConfigKey] = Value;
        SKM_Settings[sConfigKey] = Value;
end


function SkM_GetRaceText(Index)
        return SKM_Context.Race.IndexToString[Index];
end

function SkM_GetRaceIndex(Text)
        return SKM_Context.Race.StringToIndex[Text];
end

function SkM_GetClassText(Index)
        return SKM_Context.Class.IndexToString[Index];
end

function SkM_GetClassIndex(Text)
        return SKM_Context.Class.StringToIndex[Text];
end


-- --------------------------------------------------------------------------------------
-- SkM_ExtractParam
-- --------------------------------------------------------------------------------------
-- Extract parameters from a string. Delimiter is the blank character (space).
-- Return the first parameter and the rest of the string ("" if empty).
-- --------------------------------------------------------------------------------------
function SkM_ExtractParam(msg)
        local params = msg;
        local command = params;
        local index = strfind(command, " ");

        if ( index ) then
                command = strsub(command, 1, index-1);
                params = strsub(params, index+1);
        else
                params = "";
        end
        return command, params;
end


-- --------------------------------------------------------------------------------------
-- SkM_IdentifyCommand
-- --------------------------------------------------------------------------------------
-- Check if the given command matches a module command.
-- it's the case if the passed in command starts with the module command
-- eg. : module command "fort" allows to type in "fortitude".
-- --------------------------------------------------------------------------------------
function SkM_IdentifyCommand(sCmd, sModuleCommand)

        if ((not sModuleCommand) or (sModuleCommand == "")) then
                return false;
        end
        if ((not sCmd) or (sCmd == "")) then
                return false;
        end

        local iLen = string.len(sModuleCommand);

        if ( string.sub(sCmd, 1, iLen) == sModuleCommand) then
                return true;
        else
                return false;
        end
end


-- --------------------------------------------------------------------------------------
-- SkM_SetDebugLevel
-- --------------------------------------------------------------------------------------
-- Set the module debug level (-1, 0, 1, 2, 3)
-- --------------------------------------------------------------------------------------
function SkM_SetDebugLevel( iLevel )
        local iNewLevel = ifnil(iLevel, -1);

        SKM_Config.DebugLevel = iNewLevel;
        SkM_ChatMessageCol("Debug level set to "..iNewLevel);
end


function SkM_StartDebugRecord()
        SKM_Context.RecLines = 0;
        SKM_Context.TmpRecLines = 0;
        SKM_Config.RecordDebug = true;
        SkM_ChatMessageCol("Started recording debug messages");
end


function SkM_StopDebugRecord()
        SKM_Config.RecordDebug = false;
        SKM_Context.RecLines = nil;
        SKM_Context.TmpRecLines = nil;
        SkM_ChatMessageCol("Stopped recording debug messages");
end


function SkM_ClearDebugRecord()
        SKM_Debug = { };
        SKM_Context.RecLines = 0;
        SKM_Context.TmpRecLines = 0;
        SkM_ChatMessageCol("Recorded debug messages cleared");
end



-- --------------------------------------------------------------------------------------
-- SkM_GetDate
-- --------------------------------------------------------------------------------------
-- Get current date in two formats :
-- DD/MM/YYYY HH:MI:SS
-- YYYY/MM/DD HH:MI:SS (this is a sortable date)
-- --------------------------------------------------------------------------------------
function SkM_GetDate()
        -- date is a shortcut to "os.date" (os package is not available, but Blizz provided
        -- us the date function :)
        local CurDate = date("*t");
        local sYear = string.format("%0.04d", CurDate.year);
        local sMonth = string.format("%0.02d", CurDate.month);
        local sDay = string.format("%0.02d", CurDate.day);
        local sHour = string.format("%0.02d", CurDate.hour);
        local sMin = string.format("%0.02d", CurDate.min);
        local sSec = string.format("%0.02d", CurDate.sec);

        -- date in format DD/MM/YYYY HH:MI:SS
        local StrDate1 = sDay.."/"..sMonth.."/"..sYear.." "..sHour..":"..sMin..":"..sSec;

        -- date in format YYYY/MM/DD HH:MI:SS (useful for sorting !)
        local StrDate2 = sYear.."/"..sMonth.."/"..sDay.." "..sHour..":"..sMin..":"..sSec;

        return StrDate1, StrDate2;
end


function SkM_GetDay()
        local sDate1, sDate2 = SkM_GetDate();
        local sDay1, sDay2;
        sDay1 = string.sub(sDate1, 1, 10);
        sDay2 = string.sub(sDate2, 1, 10);
        return sDay1, sDay2;
end


-- --------------------------------------------------------------------------------------
-- SkM_GetSortableDate
-- --------------------------------------------------------------------------------------
-- Convert date to sortable format :
-- from DD/MM/YYYY HH:MI:SS format to YYYY/MM/DD HH:MI:SS format
-- or, from DD/MM/YYYY format to YYYY/MM/DD format
-- --------------------------------------------------------------------------------------
function SkM_GetSortableDate(sDate)
        if (not sDate) then
                return nil;
        end

        local sMonth, sDay, sYear, sHour, sMin, sSec;
        local sSortDate;

        sDay = string.sub(sDate, 1, 2);
        sMonth = string.sub(sDate, 4, 5);
        sYear = string.sub(sDate, 7, 10);

        if (string.len(sDate) == 19) then
                sHour = string.sub(sDate, 12, 13);
                sMin = string.sub(sDate, 15, 16);
                sSec = string.sub(sDate, 18, 19);

                SortDate = sYear.."/"..sMonth.."/"..sDay.." "..sHour..":"..sMin..":"..sSec;
        else
                SortDate = sYear.."/"..sMonth.."/"..sDay;
        end

        -- sortable date
        return SortDate;
end


function SkM_GetZoneText()
        local sText = GetZoneText();
        -- apply fix if needed.
        for idx, val in SKM_ZoneFix do
                if sText == val.Val_ZoneText then
                        return val.Val_MapZones;
                end
        end
        return sText;
end


-- Return continent and zone number shifted to english order
function SkM_GetCurrentMapZone_Shift()
        local FName = "SkM_GetCurrentMapZone_Shift";

        local iCont = GetCurrentMapContinent();
        local iZone = GetCurrentMapZone();
        if (iCont) and (iCont ~= 0) and (iZone) and (iZone ~= 0) and (SKM_ZoneShift) then
                if (SKM_ZoneShift[iCont]) and (SKM_ZoneShift[iCont][iZone]) then
                        iZone = SKM_ZoneShift[iCont][iZone];
                else
                        SkM_Trace(FName, 1, "Undefined shift : cont = "..iCont.." / zone = "..iZone);
                end
        end
        return iCont, iZone;
end


-- --------------------------------------------------------------------------------------
-- SkM_GetZone
-- --------------------------------------------------------------------------------------
-- Return continent and zone number
-- or (nil, nil) if GetZoneText() does not match any known zone
-- --------------------------------------------------------------------------------------
function SkM_GetZone()
        for i=1, getn(SKM_Context.Continents), 1 do
                for j=1, getn(SKM_Context.Zones[i]), 1 do
                        --if (SKM_Context.Zones[i][j] == GetZoneText()) then
                        if (SKM_Context.Zones[i][j] == SkM_GetZoneText()) then
                                return i, j;
                        end
                end
        end
        return nil, nil;
end


function SkM_GetZone_Shift(p_iCont, p_iZone)
        local iCont, iZone;
        if (p_iCont) and (p_iZone) then
                iCont = p_iCont;
                iZone = p_iZone;
        else
                iCont, iZone = SkM_GetZone();
        end

        if (iCont) and (iZone) and (SKM_ZoneShift) then
                iZone = SKM_ZoneShift[iCont][iZone];
        end
        return iCont, iZone;
end


-- get zone text from shifted zone index (english order)
function SkM_GetZoneTextFromIndex(p_iCont, p_iZone)
        return ifnil(SKM_ZoneText[p_iCont][p_iZone], "");
end


function SkM_IsInBattleground()
        local FName = "SkM_IsInBattleground";
        local sZone = SkM_GetZoneText();
        if (intable(sZone, SKM_BATTLEGROUNDS)) then
                return true;
        else
                return false;
        end
end


-- --------------------------------------------------------------------------------------
-- SkM_CheckNearNotes
-- --------------------------------------------------------------------------------------
-- Check if there are notes nearby given location
-- OBSOLETE. I don't use it any more.
-- --------------------------------------------------------------------------------------
function SkM_CheckNearNotes(idx_c, idx_z, xPos, yPos, icon)
        local iNoteCount = getn(SKM_Data[_RealmName][_PlayerName].MapData[idx_c][idx_z]);

        for i=1, iNoteCount, 1 do
                local idx_gn = SKM_Data[_RealmName][_PlayerName].MapData[idx_c][idx_z][i];
                local Note = SKM_Data[_RealmName][_PlayerName].GlobalMapData[idx_gn];

                if (abs(Note[_SKM._xPos] - xPos) <= 0.0009765625 * SKM_Config.MapNotes_MinDiff)
                and (abs(Note[_SKM._yPos] - yPos) <= 0.0013020833 * SKM_Config.MapNotes_MinDiff)
                then
                        return true;
                end
        end
        return false;
end


-- --------------------------------------------------------------------------------------
-- SkM_FindNotePOINum
-- --------------------------------------------------------------------------------------
-- Retrieve which POI object is associated to a given map note
-- --------------------------------------------------------------------------------------
function SkM_FindNotePOINum(idx_n)
        for idx_poi, val_poi in SKM_Context.WorldMapPOINotes do
                if (intable(idx_n, val_poi)) then
                        return idx_poi;
                end
        end
        return nil;
end


-- --------------------------------------------------------------------------------------
-- SkM_CheckNearNotesPOI
-- --------------------------------------------------------------------------------------
-- For a given map note, try and find nearby allocated POI so that we may group
-- current note to the notes associated to this POI.
-- --------------------------------------------------------------------------------------
function SkM_CheckNearNotesPOI(idx_c, idx_z, xPos, yPos, index, DiscardedNotes)
        local iNoteCount = getn(SKM_Data[_RealmName][_PlayerName].MapData[idx_c][idx_z]);

        local i;
        for i=1, iNoteCount, 1 do
                -- don't check notes > current note, because they've not been treated yet
                if (i == index) then
                        return nil;
                end

                if (intable(i, DiscardedNotes)) then
                        -- that note has been discarded, so don't check if it's too close !
                else
                        local idx_gn = SKM_Data[_RealmName][_PlayerName].MapData[idx_c][idx_z][i];
                        local Note = SKM_Data[_RealmName][_PlayerName].GlobalMapData[idx_gn];

                        if (abs(Note[_SKM._xPos] - xPos) <= 0.0009765625 * SKM_Config.MapNotes_MinDiff)
                        and (abs(Note[_SKM._yPos] - yPos) <= 0.0013020833 * SKM_Config.MapNotes_MinDiff)
                        then
                                -- we're close to another note that has an associated POI
                                -- find this POI number and return it
                                local iPOINum = SkM_FindNotePOINum(i);
                                return iPOINum;
                        end
                end
        end
        return nil;
end


-- --------------------------------------------------------------------------------------
-- SkM_AddMapNote
-- --------------------------------------------------------------------------------------
-- Add a new player map note
-- --------------------------------------------------------------------------------------
function SkM_AddMapNote(idx_c, idx_z, Note)
        local FName = "SkM_AddMapNote";

        -- insert note to global data in last position
        table.insert(SKM_Data[_RealmName][_PlayerName].GlobalMapData, Note);

        -- retrieve index of inserted note
        local idx_gn = table.getn(SKM_Data[_RealmName][_PlayerName].GlobalMapData);

        -- insert new global note index to map note list
        table.insert(SKM_Data[_RealmName][_PlayerName].MapData[idx_c][idx_z], idx_gn);
end


-- --------------------------------------------------------------------------------------
-- SkM_AddMapData
-- --------------------------------------------------------------------------------------
-- Create a new note from input stored data and store it in current zone records
-- --------------------------------------------------------------------------------------
function SkM_AddMapData(p_StoreInfo)
        local FName = "SkM_AddMapData";

        local idx_c, idx_z = SkM_GetZone();

        SkM_Trace(FName, 3, "idx_c = "..snil(idx_c)..", idx_z = "..snil(idx_z));

        if (idx_z) then
                SetMapZoom(idx_c, idx_z);
                local xPos, yPos = GetPlayerMapPosition(SKM_UNIT_PLAYER);

                local cont_shift, zone_shift = SkM_GetZone_Shift(idx_c, idx_z);

                SkM_Trace(FName, 3, "Player Position : x = "..snil(xPos)..", y = "..snil(yPos));

                --if ((not SkM_CheckNearNotes(idx_c, idx_z, xPos, yPos, icon)) and xPos ~= 0 and yPos ~= 0 ) then
                if (xPos ~= 0 and yPos ~= 0) then

                        local Note = { };
                        --Note[_SKM._continent] = idx_c;
                        --Note[_SKM._zone] = idx_z;
                        Note[_SKM._continent] = cont_shift;
                        Note[_SKM._zone] = zone_shift;
                        Note[_SKM._xPos] = xPos;
                        Note[_SKM._yPos] = yPos;
                        Note[_SKM._storedInfo] = p_StoreInfo;

                        -- DOH !! forgot this one in 1.4. Now all records are messed up :(
                        --SkM_AddMapNote(idx_c, idx_z, Note);
                        SkM_AddMapNote(cont_shift, zone_shift, Note);
                        SKM_Context.RecordingDisabled = false;
                        return true;
                end
        end

        if (not SKM_Context.RecordingDisabled) then
                -- 1.5 : removed message that is of no use !
                --SkM_ChatMessageCol("Event recording is disabled in this location");
                SKM_Context.RecordingDisabled = true;
        end

        return false;
end


-- --------------------------------------------------------------------------------------
-- SkM_RecordPlayerDeath
-- --------------------------------------------------------------------------------------
-- Record player death event.
-- Find if it's a PvE or PvP or unknown death, determine responsibilities for this
-- death, and create a record of the appropriate type.
-- --------------------------------------------------------------------------------------
function SkM_RecordPlayerDeath()
        local FName = "SkM_RecordPlayerDeath";

        local StoreInfo = { };

        StoreInfo[_SKM._type] = _SKM._playerDeath;

        local sDate1, sDate2 = SkM_GetDate();
        StoreInfo[_SKM._date] = sDate1;
        --StoreInfo[_sortdate] = sDate2;

        local sKiller, KillerType, HateList = SkM_PlayerDeathResp();

        -- check if in battleground
        local bBattleground = SkM_IsInBattleground();

        -- if killer is known, and if its type (player or creature) is also known, record
        -- specific death event

        -- note : it's possible that we determined that a given unit is responsible for our
        -- death, but we don't know if it's a player or a creature. It can happen if this
        -- unit killed us with dots only. In that case, the death type will be undetermined.

        -- addition : it can happen also because pets and players trigger the same events, thus
        -- if we never targetted or mouse-overed a given player, we will not know if he is
        -- a player or a pet.

        if (sKiller) and (KillerType == nil) then
                -- try to target to see if this is a player
                TargetByName(sKiller);
                SkM_UpdateUnitData();

                -- if this is an enemy player, then he should have been stored !
                if (SKM_Data[_RealmName][_PlayerName].EnemyHistory[sKiller]) then
                        KillerType = _SKM._enemyPlayer;
                end
        end

        if (sKiller) and (KillerType) then
                StoreInfo[_SKM._name] = sKiller;
                StoreInfo[_SKM._enemyType] = KillerType;

                if (KillerType == _SKM._enemyPlayer) then
                        SkM_UpdateEnemyLastView(sKiller, sDate1, false);

                        StoreInfo[_SKM._type] = _SKM._playerDeathPvP;

                        SkM_UpdateEnemy_IncrKillPlayer(sKiller, 1, bBattleground);

                        if (bBattleground) then
                                SkM_BGStats_AddDeath();
                        end

                        -- update target info if needed
                        SkM_SetTargetInfo();

                else
                        StoreInfo[_SKM._type] = _SKM._playerDeathPvE;
                end
        end

        -- check if world record are disabled, if it's the case return
        if (StoreInfo[_SKM._type] == _SKM._playerDeathPvP) then
                if (not SkM_GetOption("RecordPlayerDeath")) then
                        return;
                end
        else
                if (not SkM_GetOption("RecordPlayerDeathNonPvP")) then
                        return;
                end
        end


        if (SkM_AddMapData(StoreInfo)) then

                if (SkM_GetOption("DisplayDeathRecord")) then
                        if (sKiller) and (KillerType) then
                                if (KillerType == _SKM._enemyPlayer) then
                                        SkM_ChatMessageColP(string.format(SKM_UI_STRINGS.RecordMessage_PlayerPvPDeath, sKiller), SKM_Config.RGB_PlayerPvPDeath);
                                else
                                        SkM_ChatMessageColP(string.format(SKM_UI_STRINGS.RecordMessage_PlayerPvEDeath, sKiller), SKM_Config.RGB_PlayerPvEDeath);
                                end
                        else
                                SkM_ChatMessageColP(SKM_UI_STRINGS.RecordMessage_PlayerDeath, SKM_Config.RGB_PlayerDeath);
                        end
                end

        end

        if (SkM_GetOption("ReportPlayerDeath")) then
                local iNbLine = math.min(SKM_Config.ReportDeathMaxLines, table.getn(HateList));
                if (iNbLine > 0) then
                        SkM_ChatMessageColP(string.format(SKM_UI_STRINGS.ReportMessage_PlayerDeath, iNbLine),
                                SKM_Config.RGB_ReportDeath);

                        for i=1, iNbLine, 1 do
                                SkM_ChatMessageColP(string.format("%d. %s (%d %%)", i, HateList[i][_SKM._name], HateList[i][_SKM._hatePercent]),
                                        SKM_Config.RGB_ReportDeath);
                        end
                end
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_PlayerAlive
-- --------------------------------------------------------------------------------------
-- Player is back to life, remember it.
-- --------------------------------------------------------------------------------------
function SkM_PlayerAlive()
        SKM_Context.PlayerAlive = true;
end


-- --------------------------------------------------------------------------------------
-- SkM_PlayerDeath
-- --------------------------------------------------------------------------------------
-- Handle player death event.
-- --------------------------------------------------------------------------------------
function SkM_PlayerDeath()
        local FName = "SkM_PlayerDeath";

        -- check if player was alive because in some cases we get two death messages for the
        -- same death
        if (SKM_Context.PlayerAlive == false) then
                SkM_Trace(FName, 3, "Not alive, discard death event");
                return;
        end

        SKM_Context.PlayerAlive = false;

--      if (not SkM_GetOption("RecordPlayerDeath")) then
--              return;
--      end

        SkM_RecordPlayerDeath();
end


-- --------------------------------------------------------------------------------------
-- SkM_WorldMapUpdate
-- --------------------------------------------------------------------------------------
-- Handle World Map Update event
-- --------------------------------------------------------------------------------------
function SkM_WorldMapUpdate()
        local FName = "SkM_WorldMapUpdate";

        if (WorldMapFrame:IsVisible()) then
                -- world map is visible, draw all that need to be !

                --local serverTime = GetTime();
                SKM_Context.MapOpen = true;
                --local mapContinent = GetCurrentMapContinent();
                --local mapZone = GetCurrentMapZone();
                local mapContinent, mapZone = SkM_GetCurrentMapZone_Shift();
                SKM_Context.CloseMap = { continent = mapContinent, zone = mapZone, time = GetTime() };
                SkM_MainDraw();

        elseif (SKM_Context.MapOpen) then
                -- Map is not visible, but recorded as opened. Close it.
                SKM_Context.MapOpen = false;
                SkM_MainDraw();
                --SkM_ChangeMap();
        else
                -- Map not visible, probably closed
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_CloseWorldMap
-- --------------------------------------------------------------------------------------
-- Handle Close World Map event
-- --------------------------------------------------------------------------------------
function SkM_CloseWorldMap()
        local FName = "SkM_CloseWorldMap";

        SKM_Context.MapOpen = false;
        SkM_MainDraw();
        --SkM_ChangeMap();
end


-- --------------------------------------------------------------------------------------
-- SkM_MainDraw
-- --------------------------------------------------------------------------------------
-- Display our records as custom POI on the world map, and hide unused POI.
-- --------------------------------------------------------------------------------------
function SkM_MainDraw()
        local FName = "SkM_MainDraw";

        SKM_Context.WorldMapPOINotes = { };

        local lastUnused = 1;
        local maxNotes = SKM_MAX_MAP_NOTES;
        local iNbNotes = 0;

        --local idx_c = GetCurrentMapContinent();
        --local idx_z = GetCurrentMapZone();
        local idx_c, idx_z = SkM_GetCurrentMapZone_Shift();

        if (idx_c ~= 0) and (idx_z ~= 0)
          and (SKM_Data[_RealmName][_PlayerName].MapData[idx_c])
          and (SKM_Data[_RealmName][_PlayerName].MapData[idx_c][idx_z])
        then
                iNbNotes = table.getn(SKM_Data[_RealmName][_PlayerName].MapData[idx_c][idx_z]);
        end

        if (SKM_Context.MapOpen) and (SkM_GetOption("MapDisplayRecords")) then

                if (idx_c ~= 0) and (idx_z ~= 0)
                  and (SKM_Data[_RealmName][_PlayerName].MapData[idx_c])
                  and (SKM_Data[_RealmName][_PlayerName].MapData[idx_c][idx_z])
                then

                        --iNbNotes = table.getn(SKM_Data[_RealmName][_PlayerName].MapData[idx_c][idx_z]);

                        local DiscardedNotes = { };

                        for idx_n, val_n in SKM_Data[_RealmName][_PlayerName].MapData[idx_c][idx_z] do

                                local idx_gn = SKM_Data[_RealmName][_PlayerName].MapData[idx_c][idx_z][idx_n];
                                local Note = SKM_Data[_RealmName][_PlayerName].GlobalMapData[idx_gn];

                                if (Note[_SKM._xPos]) and (Note[_SKM._yPos]) and (lastUnused <= maxNotes) then
                                        local iPOINum = SkM_CheckNearNotesPOI(idx_c, idx_z, Note[_SKM._xPos], Note[_SKM._yPos], idx_n, DiscardedNotes);
                                        if (iPOINum) then
                                                SkM_Trace(FName, 3, "Already too close POI, discard this one : "..snil(idx_n));

                                                table.insert(DiscardedNotes, idx_n);

                                                -- and insert it in the close POI list
                                                table.insert(SKM_Context.WorldMapPOINotes[iPOINum], idx_n);
                                        else
                                                local mainNote = getglobal("SKMapPOI"..lastUnused);

                                                SKM_Context.WorldMapPOINotes[lastUnused] = { };
                                                table.insert(SKM_Context.WorldMapPOINotes[lastUnused], idx_n);

                                                local mnX,mnY;
                                                mnX = Note[_SKM._xPos] * WorldMapDetailFrame:GetWidth();
                                                mnY = -Note[_SKM._yPos] * WorldMapDetailFrame:GetHeight();
                                                mainNote:SetAlpha(0.8);

                                                -- SkM_Trace(FName, 3, "Attach position : x = "..snil(mnX)..", y = "..snil(mnY));


                                                mainNote:SetPoint("CENTER", "WorldMapDetailFrame", "TOPLEFT", mnX, mnY);
                                                mainNote:SetFrameLevel(WorldMapPlayer:GetFrameLevel() - 1);
                                                mainNote.toolTip = idx_n;

                                                -- icon is determined below

                                                lastUnused = lastUnused + 1;
                                        end

                                end
                        end  -- for map notes

                        for i=1, getn(SKM_Context.WorldMapPOINotes), 1 do
                                local mainNote = getglobal("SKMapPOI"..i);
                                local mainNoteTexture = getglobal("SKMapPOI"..i.."Texture");

                                local iNbNotes = getn(SKM_Context.WorldMapPOINotes[i]);
                                local icon;

                                local iNumOfTypes, iNumOfPlayerKillTypes, iNumOfPlayerDeathTypes,
                                      iNumOfCreatureKillTypes, TypeCount = SkM_CountPOIEventType(idx_c, idx_z, i);

                                SkM_Trace(FName, 3, "Number of different types = "..iNumOfTypes..", of player kill = "..iNumOfPlayerKillTypes..", of death = "..iNumOfPlayerDeathTypes);

                                if (iNumOfTypes == 1) then
                                        -- several notes, but of a single type. Pick the first !

                                        local idx_n = SKM_Context.WorldMapPOINotes[i][1];
                                        local idx_gn = SKM_Data[_RealmName][_PlayerName].MapData[idx_c][idx_z][idx_n];
                                        local Note = SKM_Data[_RealmName][_PlayerName].GlobalMapData[idx_gn];

                                        local noteType = Note[_SKM._storedInfo][_SKM._type];
                                        SkM_Trace(FName, 3, "Single type = "..noteType);

                                        icon = SKM_Config.IconsByType[noteType];
                                        if (not icon) then
                                                icon = SKM_Config.IconsByType[_SKM._default];
                                        end

                                else

                                        if (iNumOfPlayerDeathTypes == iNumOfTypes) then
                                                icon = SKM_Config.IconsByType[_SKM._playerDeath];
                                        elseif (iNumOfPlayerKillTypes == iNumOfTypes) then
                                                icon = SKM_Config.IconsByType[_SKM._playerKill];
                                        elseif (iNumOfCreatureKillTypes == iNumOfTypes) then
                                                icon = SKM_Config.IconsByType[_SKM._creatureKill_Target];
                                        elseif (iNumOfPlayerDeathTypes + iNumOfPlayerKillTypes == iNumOfTypes) then
                                                icon = SKM_Config.IconsByType[_SKM._playerKillAndDeath];
                                        else
                                                icon = SKM_Config.IconsByType[_SKM._multiType];
                                        end

                                end

                                local texture = SKM_Config.IconPath .. "\\" .. icon;
                                SkM_Trace(FName, 3, "texture = "..texture);

                                mainNoteTexture:SetTexture(texture);
                                mainNote:Show();
                        end

                end -- if known zone info
        end -- if display map

        -- hide unused notes
        for i=lastUnused, maxNotes, 1 do
                local mainNote = getglobal("SKMapPOI"..i);
                mainNote:Hide();
        end



        if (not SkM_GetOption("ShowWorldMapControl")) then
                SKMapWorldMapControl:Hide();
        else

                if (SKM_Context.MapOpen) and (idx_z ~= 0) and (iNbNotes > 0) then
                        local idx=1;
                        local MyButton = getglobal("SKMapWorldMapControl_CheckButton"..idx);
                        local ButtonText = getglobal("SKMapWorldMapControl_CheckButton"..idx.."Text");
                        ButtonText:SetText(SKM_UI_STRINGS.WorldMap_Button_ShowRecords);

                        if (SkM_GetOption("MapDisplayRecords")) then
                                MyButton:SetChecked(1);
                        else
                                MyButton:SetChecked(0);
                        end

                        SKMapWorldMapControl:Show();
                else
                        SKMapWorldMapControl:Hide();
                end
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_CountPOIEventType
-- --------------------------------------------------------------------------------------
-- Count the different types of records attached to a given POI.
-- Will be useful to allocate an icon to this POI.
-- --------------------------------------------------------------------------------------
function SkM_CountPOIEventType(idx_c, idx_z, iPOINum)
        local FName = "SkM_CountPOIEventType";

        local iNbNotes = getn(SKM_Context.WorldMapPOINotes[iPOINum]);
        local TypeCount = { };

        local iPlayerDeathTypes = 0;
        local iPlayerKillTypes = 0;
        local iCreatureKillTypes = 0;

        for i=1, iNbNotes, 1 do
                local idx_n = SKM_Context.WorldMapPOINotes[iPOINum][i];
                local idx_gn = SKM_Data[_RealmName][_PlayerName].MapData[idx_c][idx_z][idx_n];
                local Note = SKM_Data[_RealmName][_PlayerName].GlobalMapData[idx_gn];

                local noteType = Note[_SKM._storedInfo][_SKM._type];


                if (not TypeCount[noteType]) then
                        TypeCount[noteType] = 0;
                end
                TypeCount[noteType] = TypeCount[noteType] + 1;
        end
        local iTypes = 0;
        for idx, val in TypeCount do
                iTypes = iTypes + 1;
                if (idx == _SKM._playerAssistKill) or (idx == _SKM._playerKill) or (idx == _SKM._playerFullKill) then
                        iPlayerKillTypes = iPlayerKillTypes + 1;
                elseif (idx == _SKM._playerDeath) or (idx == _SKM._playerDeathPvE) or (idx == _SKM._playerDeathPvP) then
                        iPlayerDeathTypes = iPlayerDeathTypes + 1;
                elseif (idx == _SKM._creatureKill_Xp) or (idx == _SKM._creatureKill_Target) then
                        iCreatureKillTypes = iCreatureKillTypes + 1;
                end

                SkM_Trace(FName, 3, "Type = "..idx..", Count = "..val);
        end
        return iTypes, iPlayerKillTypes, iPlayerDeathTypes, iCreatureKillTypes, TypeCount;
end


-- --------------------------------------------------------------------------------------
-- SkM_GetNoteMessage
-- --------------------------------------------------------------------------------------
-- Build a note string message from a pattern and an information structure
-- --------------------------------------------------------------------------------------
function SkM_GetNoteMessage(Info, sMessagePattern)
        local FName = "SkM_GetNoteMessage";

        if (not Info) then
                SkM_Trace(FName, 3, "Info is nil");
                return nil;
        end
        if (not sMessagePattern) then
                SkM_Trace(FName, 3, "Pattern is nil");
                return nil;
        end

        local sMessage = sMessagePattern;

        local sVal_Name = ifnil(Info[_SKM._name], "??");
        local sVal_Guild = ifnil(Info[_SKM._guild], "??");
        local sVal_Class = ifnil(Info[_SKM._class], "??");
        local sVal_Race = ifnil(Info[_SKM._race], "??");
        local sVal_Level = ifnil(Info[_SKM._level], "??");
        local sVal_Player = ifnil(_PlayerName, "??");
        local sVal_Date = ifnil(Info[_SKM._date], "??");

        if (sVal_Level == -1) then
                sVal_Level = "++";
        end

        sMessage = string.gsub(sMessage, "%%name", sVal_Name);
        sMessage = string.gsub(sMessage, "%%race", sVal_Race);
        sMessage = string.gsub(sMessage, "%%guild", sVal_Guild);
        sMessage = string.gsub(sMessage, "%%class", sVal_Class);
        sMessage = string.gsub(sMessage, "%%level", sVal_Level);
        sMessage = string.gsub(sMessage, "%%player", sVal_Player);
        sMessage = string.gsub(sMessage, "%%date", sVal_Date);

        return sMessage;
end


-- --------------------------------------------------------------------------------------
-- SkM_FillWorldMapToolTip
-- --------------------------------------------------------------------------------------
-- Builds the ToolTip for one of our custom POI that just got mouse focus.
-- --------------------------------------------------------------------------------------
function SkM_FillWorldMapToolTip(idx_c, idx_z, idx_n, idx_poi)
        local FName = "SkM_FillWorldMapToolTip";

        SkM_Trace(FName, 3, "idx_n = "..snil(idx_n)..", idx_poi = "..snil(idx_poi));
        local iLineCount = 0;

        local idx, val;

        if (not SKM_Context.WorldMapPOINotes[idx_poi]) then
                SkM_Trace(FName, 3, "WorldMapPOINotes not defined for POI "..idx_poi);
                return;
        end
        for idx, val in SKM_Context.WorldMapPOINotes[idx_poi] do

                local idx_gn = SKM_Data[_RealmName][_PlayerName].MapData[idx_c][idx_z][val];
                local Note = SKM_Data[_RealmName][_PlayerName].GlobalMapData[idx_gn];

                SkM_Trace(FName, 4, "idx_gn = "..snil(idx_gn));

                --local StoredInfo = Note[_SKM._storedInfo];
                local StoredInfo = copytable(Note[_SKM._storedInfo]);

                iLineCount = iLineCount + 1;
                if (iLineCount > SKM_Config.MapNotes_MaxLines) then
                        return false;
                end

                local sPattern;
                local sName = StoredInfo[_SKM._name];

                SkM_Trace(FName, 3, "Type = "..snil(StoredInfo[_SKM._type]));
                SkM_Trace(FName, 3, "Name = "..snil(sName)..", Enemy Type = "..snil(StoredInfo[_SKM._enemyType]));

                local Enemy;
                if (sName) then
                        if not (StoredInfo[_SKM._enemyType] == _SKM._enemyCreature
                                or StoredInfo[_SKM._type] == _SKM._creatureKill_Target
                                or StoredInfo[_SKM._type] == _SKM._creatureKill_Xp) then
                                Enemy = SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName];
                        end
                end


                local bNoEnemyData = true;
                if (Enemy) then
                        StoredInfo[_SKM._class] = SkM_GetClassText(Enemy[_SKM._class]);
                        StoredInfo[_SKM._race] = SkM_GetRaceText(Enemy[_SKM._race]);
                        StoredInfo[_SKM._level] = Enemy[_SKM._level];

                        if (StoredInfo[_SKM._class]) or (StoredInfo[_SKM._race]) or (StoredInfo[_SKM._level]) then
                                bNoEnemyData = false;
                        end

                        SkM_Trace(FName, 3, "Class = "..snil(Enemy[_SKM._class])..", Race = "..snil(Enemy[_SKM._race])..", Level = "..snil(Enemy[_SKM._level]));
                end


                local RGB;
                local sPattern;

                if (StoredInfo[_SKM._type] == _SKM._playerAssistKill) then
                        sPattern = SKM_Config.Message_PlayerKill;
                        RGB = SKM_Config.RGB_PlayerAssistKill;

                elseif (StoredInfo[_SKM._type] == _SKM._playerKill) then
                        sPattern = SKM_Config.Message_PlayerKill;
                        RGB = SKM_Config.RGB_PlayerKill;


                elseif (StoredInfo[_SKM._type] == _SKM._playerFullKill) then
                        sPattern = SKM_Config.Message_PlayerKill;

                        if (StoredInfo[_SKM._loneWolfKill]) then
                                RGB = SKM_Config.RGB_LoneWolfKill;
                        else
                                RGB = SKM_Config.RGB_PlayerFullKill;
                        end

                elseif (StoredInfo[_SKM._type] == _SKM._playerDeath) then
                        sPattern = SKM_Config.Message_PlayerDeath;
                        RGB = SKM_Config.RGB_PlayerDeath;

                elseif (StoredInfo[_SKM._type] == _SKM._playerDeathPvE) then
                        sPattern = SKM_Config.Message_PlayerDeath_PvE;
                        RGB = SKM_Config.RGB_PlayerPvEDeath;

                elseif (StoredInfo[_SKM._type] == _SKM._playerDeathPvP) then
                        if (bNoEnemyData == false) then
                                sPattern = SKM_Config.Message_PlayerDeath_PvP;
                        else
                                sPattern = SKM_Config.Message_PlayerDeath_PvP_NoData;
                        end
                        RGB = SKM_Config.RGB_PlayerPvPDeath;

                elseif (StoredInfo[_SKM._type] == _SKM._creatureKill_Target) then
                        if (StoredInfo[_SKM._class]) then
                                sPattern =  SKM_Config.Message_CreatureKill_RareDetail;
                        else
                                sPattern =  SKM_Config.Message_CreatureKill_Detail;
                        end
                        RGB = SKM_Config.RGB_CreatureKill;

                elseif (StoredInfo[_SKM._type] == _SKM._creatureKill_Xp) then
                        sPattern = SKM_Config.Message_CreatureKill_Basic;
                        RGB = SKM_Config.RGB_CreatureKill;

                elseif (StoredInfo[_SKM._type] == _SKM._levelUp) then
                        sPattern = SKM_Config.Message_LevelUp;
                        RGB = SKM_Config.RGB_LevelUp;
                end

                if (StoredInfo[_SKM._honorKill]) then
                        sPattern = sPattern.." "..SKM_Config.SubMessage_HonorKill;
                end

                local sLine = SkM_GetNoteMessage(StoredInfo, sPattern);
                WorldMapTooltip:AddLine(sLine, RGB.r, RGB.g, RGB.b);
        end

        return true;
end


-- --------------------------------------------------------------------------------------
-- SkM_WorldMapEnterPOI
-- --------------------------------------------------------------------------------------
-- Handle POI "OnEnter" event :
-- Acquire and prepare ToolTip, then display it.
-- --------------------------------------------------------------------------------------
function SkM_WorldMapEnterPOI(id)
        local FName = "SkM_WorldMapEnterPOI";

        SkM_Trace(FName, 3, "id = "..id);

        --if ((not MapNotesPOIMenuFrame:IsVisible()) and (not MapNotesNewMenuFrame:IsVisible()) and MapNotes_BlockingFrame()) then
        if (true) then
                local x, y = this:GetCenter();
                local x2, y2 = WorldMapButton:GetCenter();
                local anchor = "";
                if (x > x2) then
                        anchor = "ANCHOR_LEFT";
                else
                        anchor = "ANCHOR_RIGHT";
                end

                --local idx_z = GetCurrentMapZone();
                --local idx_c = GetCurrentMapContinent();
                local idx_c, idx_z = SkM_GetCurrentMapZone_Shift();

                local idx_n = this.toolTip;

                local idx_gn = SKM_Data[_RealmName][_PlayerName].MapData[idx_c][idx_z][idx_n];
                local Note = SKM_Data[_RealmName][_PlayerName].GlobalMapData[idx_gn];


                SkM_Trace(FName, 3, "this.toolTip = "..this.toolTip);

                WorldMapTooltip:SetOwner(this, anchor);

                --WorldMapTooltip:SetText(""); -- doesn't display if no title
                WorldMapTooltip:SetText(SKM_UI_STRINGS.Map_Window_Title);

                local xPos = Note[_SKM._xPos];
                local yPos = Note[_SKM._yPos];


                --SkM_FillWorldMapToolTip(idx_c, idx_z, xPos, yPos);
                SkM_FillWorldMapToolTip(idx_c, idx_z, idx_n, id);

                WorldMapTooltip:Show();
        else
                WorldMapTooltip:Hide();
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_WorldMapLeavePOI
-- --------------------------------------------------------------------------------------
-- Handle POI "OnLeave" event :
-- Hide ToolTip.
-- --------------------------------------------------------------------------------------
function SkM_WorldMapLeavePOI(id)
        local FName = "SkM_WorldMapLeavePOI";
        WorldMapTooltip:Hide();
end


-- --------------------------------------------------------------------------------------
-- SkM_FormatParsePattern
-- --------------------------------------------------------------------------------------
-- Format a parsing pattern.
-- Input pattern is a readable string that contains %s and %d for substitutions.
-- Return value is a regular expression, with special characters escaped by '%'.
-- --------------------------------------------------------------------------------------
function SkM_FormatParsePattern(sTemplate)
        local FName = "SkM_FormatParsePattern";

        local sPattern = sTemplate;

        sPattern = string.gsub(sPattern, "%(", "%%(");
        sPattern = string.gsub(sPattern, "%)", "%%)");
        sPattern = string.gsub(sPattern, "%.", "%%.");
        sPattern = string.gsub(sPattern, "%+", "%%+");
        sPattern = string.gsub(sPattern, "%[", "%%[");
        sPattern = string.gsub(sPattern, "%]", "%%]");

        sPattern = string.gsub(sPattern, "%%d", "([0-9]+)");
        sPattern = string.gsub(sPattern, "%%s", "(.+)");

        return sPattern;
end


-- --------------------------------------------------------------------------------------
-- SkM_BuildParsePatterns
-- --------------------------------------------------------------------------------------
-- Format as regular expressions all the parsing patterns that we need.
-- --------------------------------------------------------------------------------------
function SkM_BuildParsePatterns()
        local FName = "SkM_BuildParsePatterns";

        for idx, val in SKM_PATTERN do
                local sPattern = SkM_FormatParsePattern(val);
                SKM_Context.Pattern[idx] = sPattern;
        end
end


-- --------------------------------------------------------------------------------------
-- SkM_ParseCombatChat_XpGain
-- --------------------------------------------------------------------------------------
-- Handle "player receives combat xp" chat event.
-- If we can match one of the "creature kill" messages, then record the creature kill.
-- --------------------------------------------------------------------------------------
function SkM_ParseCombatChat_XpGain(sMsg)
        local FName = "SkM_ParseCombatChat_XpGain";

        SkM_Trace(FName, 3, "You gain combat xp");

        for sFoe, iXpGain, iBaseXp, iRestXp in string.gfind(sMsg, SKM_Context.Pattern.XpGain_Rested_Solo) do
                if (sFoe and iXpGain and iBaseXp and iRestXp) then
                        SkM_Trace(FName, 3, "XpGain_Rested_Solo : Foe = "..sFoe..", Xp = "..iXpGain);

                        SkM_RecordCreatureKill_Xp(sFoe);
                        return;
                end
        end

        for sFoe, iXpGain, iBaseXp, iRestXp, iGroupBonus in string.gfind(sMsg, SKM_Context.Pattern.XpGain_Rested_Group) do
                if (sFoe and iXpGain and iBaseXp and iRestXp and iGroupBonus) then
                        SkM_Trace(FName, 3, "XpGain_Rested_Group : Foe = "..sFoe..", Xp = "..iXpGain);

                        SkM_RecordCreatureKill_Xp(sFoe);
                        return;
                end
        end

        for sFoe, iXpGain, iBaseXp, iRestXp, iRaidPenalty in string.gfind(sMsg, SKM_Context.Pattern.XpGain_Rested_Raid) do
                if (sFoe and iXpGain and iBaseXp and iRestXp and iRaidPenalty) then
                        SkM_Trace(FName, 3, "XpGain_Rested_Raid : Foe = "..sFoe..", Xp = "..iXpGain);

                        SkM_RecordCreatureKill_Xp(sFoe);
                        return;
                end
        end

        for sFoe, iXpGain in string.gfind(sMsg, SKM_Context.Pattern.XpGain_Solo) do
                if (sFoe and iXpGain) then
                        SkM_Trace(FName, 3, "XpGain_Solo : Foe = "..sFoe..", Xp = "..iXpGain);

                        SkM_RecordCreatureKill_Xp(sFoe);
                        return;
                end
        end

        for sFoe, iXpGain, iGroupBonus in string.gfind(sMsg, SKM_Context.Pattern.XpGain_Group) do
                if (sFoe and iXpGain and iGroupBonus) then
                        SkM_Trace(FName, 3, "XpGain_Group : Foe = "..sFoe..", Xp = "..iXpGain);

                        SkM_RecordCreatureKill_Xp(sFoe);
                        return;
                end
        end

        for sFoe, iXpGain, iRaidPenalty in string.gfind(sMsg, SKM_Context.Pattern.XpGain_Raid) do
                if (sFoe and iXpGain and iRaidPenalty) then
                        SkM_Trace(FName, 3, "XpGain_Raid : Foe = "..sFoe..", Xp = "..iXpGain);

                        SkM_RecordCreatureKill_Xp(sFoe);
                        return;
                end
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_ParseCombatChat_SelfCombatHit
-- --------------------------------------------------------------------------------------
-- Handle "Player hits something" chat event.
-- Record damage done to this enemy.
-- Note that this may be a player or a creature. We don't check it yet as enemy player
-- may not be yet known, it's better to check on death event.
-- --------------------------------------------------------------------------------------
function SkM_ParseCombatChat_SelfCombatHit(sMsg)
        local FName = "SkM_ParseCombatChat_SelfCombatHit";

        SkM_Trace(FName, 3, "You do physical damage to something");

        for sFoe, iDamage in string.gfind(sMsg, SKM_Context.Pattern.Player_HitDamage) do
                if (sFoe and iDamage) then
                        SkM_Trace(FName, 3, "Player_HitDamage : Foe = "..sFoe..", Damage = "..iDamage);

                        SkM_LogDamage_OnPvpEnemy(iDamage, _PlayerName, sFoe);
                        return;
                end
        end

        for sFoe, iDamage in string.gfind(sMsg, SKM_Context.Pattern.Player_HitCritDamage) do
                if (sFoe and iDamage) then
                        SkM_Trace(FName, 3, "Player_HitDamage : Foe = "..sFoe..", Damage = "..iDamage);

                        SkM_LogDamage_OnPvpEnemy(iDamage, _PlayerName, sFoe);
                        return;
                end
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_ParseCombatChat_SelfCombatSpell
-- --------------------------------------------------------------------------------------
-- Handle "Player does spell damage on something" chat event.
-- Record damage done to this enemy.
-- Note that this may be a player or a creature. We don't check it yet as enemy player
-- may not be yet known, it's better to check on death event.
-- --------------------------------------------------------------------------------------
function SkM_ParseCombatChat_SelfCombatSpell(sMsg)
        local FName = "SkM_ParseCombatChat_SelfCombatSpell";

        SkM_Trace(FName, 3, "You do spell damage to something");

        for sSpell, sFoe, iDamage in string.gfind(sMsg, SKM_Context.Pattern.Player_SpellDamage) do
                if (sSpell and sFoe and iDamage) then
                        SkM_Trace(FName, 3, "Player_SpellDamage : Spell = "..sSpell..", Foe = "..sFoe..", Damage = "..iDamage);

                        SkM_LogDamage_OnPvpEnemy(iDamage, _PlayerName, sFoe);
                        return;
                end
        end

        for sSpell, sFoe, iDamage in string.gfind(sMsg, SKM_Context.Pattern.Player_SpellCritDamage) do
                if (sSpell and sFoe and iDamage) then
                        SkM_Trace(FName, 3, "Player_SpellCritDamage : Spell = "..sSpell..", Foe = "..sFoe..", Damage = "..iDamage);

                        SkM_LogDamage_OnPvpEnemy(iDamage, _PlayerName, sFoe);
                        return;
                end
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_ParseCombatChat_FriendCombatHit
-- --------------------------------------------------------------------------------------
-- Handle "Friendly player hits something" chat event.
-- Record damage done to this enemy.
-- Note that this may be a player or a creature. We don't check it yet as enemy player
-- may not be yet known, it's better to check on death event.
-- --------------------------------------------------------------------------------------
function SkM_ParseCombatChat_FriendCombatHit(sMsg)
        local FName = "SkM_ParseCombatChat_FriendCombatHit";

        SkM_Trace(FName, 3, "A friend do physical damage to something");

        for sAttacker, sFoe, iDamage in string.gfind(sMsg, SKM_Context.Pattern.Other_HitDamage) do
                if (sAttacker and sFoe and iDamage) then
                        SkM_Trace(FName, 3, "Other_HitDamage : Attacker = "..sAttacker..", Foe = "..sFoe..", Damage = "..iDamage);

                        SkM_LogDamage_OnPvpEnemy(iDamage, sAttacker, sFoe);
                        return;
                end
        end

        for sAttacker, sFoe, iDamage in string.gfind(sMsg, SKM_Context.Pattern.Other_HitCritDamage) do
                if (sAttacker and sFoe and iDamage) then
                        SkM_Trace(FName, 3, "Other_HitCritDamage : Attacker = "..sAttacker..", Foe = "..sFoe..", Damage = "..iDamage);


                        SkM_LogDamage_OnPvpEnemy(iDamage, sAttacker, sFoe);
                        return;
                end
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_ParseCombatChat_FriendCombatSpell
-- --------------------------------------------------------------------------------------
-- Handle "Friendly player does spell damage to something" chat event.
-- Record damage done to this enemy.
-- Note that this may be a player or a creature. We don't check it yet as enemy player
-- may not be yet known, it's better to check on death event.
-- --------------------------------------------------------------------------------------
function SkM_ParseCombatChat_FriendCombatSpell(sMsg)
        local FName = "SkM_ParseCombatChat_FriendCombatSpell";

        SkM_Trace(FName, 3, "A friend do spell damage to something");

        -- PIng: Patch for French version where attacker and spell are inversed

        if ( GetLocale() == "frFR" ) then

                for sSpell, sAttacker, sFoe, iDamage in string.gfind(sMsg, SKM_Context.Pattern.Other_SpellDamage) do
                        if (sAttacker and sSpell and sFoe and iDamage) then
                                SkM_Trace(FName, 3, "Other_SpellDamage : Attacker = "..sAttacker..", Spell = "..sSpell..", Foe = "..sFoe..", Damage = "..iDamage);

                                if (SkM_IsTotem(sAttacker)) then
                                        -- ignore totem damage
                                        return;
                                end

                                SkM_LogDamage_OnPvpEnemy(iDamage, sAttacker, sFoe);
                                return;
                        end
                end
        else

                for sAttacker, sSpell, sFoe, iDamage in string.gfind(sMsg, SKM_Context.Pattern.Other_SpellDamage) do
                        if (sAttacker and sSpell and sFoe and iDamage) then
                                SkM_Trace(FName, 3, "Other_SpellDamage : Attacker = "..sAttacker..", Spell = "..sSpell..", Foe = "..sFoe..", Damage = "..iDamage);

                                if (SkM_IsTotem(sAttacker)) then
                                        -- ignore totem damage
                                        return;
                                end

                                SkM_LogDamage_OnPvpEnemy(iDamage, sAttacker, sFoe);
                                return;
                        end
                end
        end

        for sAttacker, sSpell, sFoe, iDamage in string.gfind(sMsg, SKM_Context.Pattern.Other_SpellCritDamage) do
                if (sAttacker and sSpell and sFoe and iDamage) then
                        SkM_Trace(FName, 3, "Other_SpellCritDamage : Attacker = "..sAttacker..", Spell = "..sSpell..", Foe = "..sFoe..", Damage = "..iDamage);

                        SkM_LogDamage_OnPvpEnemy(iDamage, sAttacker, sFoe);
                        return;
                end
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_ParseCombatChat_PetCombatHit
-- --------------------------------------------------------------------------------------
-- Handle "Our pet hits something" chat event.
-- Record damage (for pet owner !) done to this enemy.
-- Note that this may be a player or a creature. We don't check it yet as enemy player
-- may not be yet known, it's better to check on death event.
-- --------------------------------------------------------------------------------------
function SkM_ParseCombatChat_PetCombatHit(sMsg)
        local FName = "SkM_ParseCombatChat_PetCombatHit";

        SkM_Trace(FName, 3, "Your pet does physical damage to something");

        for sAttacker, sFoe, iDamage in string.gfind(sMsg, SKM_Context.Pattern.Other_HitDamage) do
                if (sAttacker and sFoe and iDamage) then
                        SkM_Trace(FName, 3, "Other_HitDamage : Attacker = "..sAttacker..", Foe = "..sFoe..", Damage = "..iDamage);

                        SkM_LogDamage_OnPvpEnemy(iDamage, _PlayerName, sFoe);
                        return;
                end
        end

        for sAttacker, sFoe, iDamage in string.gfind(sMsg, SKM_Context.Pattern.Other_HitCritDamage) do
                if (sAttacker and sFoe and iDamage) then
                        SkM_Trace(FName, 3, "Other_HitCritDamage : Attacker = "..sAttacker..", Foe = "..sFoe..", Damage = "..iDamage);

                        SkM_LogDamage_OnPvpEnemy(iDamage, _PlayerName, sFoe);
                        return;
                end
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_ParseCombatChat_PetCombatSpell
-- --------------------------------------------------------------------------------------
-- Handle "Our pet does spell damage to something" chat event.
-- Record damage (for pet owner !) done to this enemy.
-- Note that this may be a player or a creature. We don't check it yet as enemy player
-- may not be yet known, it's better to check on death event.
-- --------------------------------------------------------------------------------------
function SkM_ParseCombatChat_PetCombatSpell(sMsg)
        local FName = "SkM_ParseCombatChat_PetCombatSpell";

        SkM_Trace(FName, 3, "Your pet does spell damage to something");

        -- Note : it may be your totem if you are a shaman
        -- in this case, we don't ignore it : we count it as player damage also.



        for sAttacker, sSpell, sFoe, iDamage in string.gfind(sMsg, SKM_Context.Pattern.Other_SpellDamage) do
                if (sAttacker and sSpell and sFoe and iDamage) then
                        SkM_Trace(FName, 3, "Other_SpellDamage : Attacker = "..sAttacker..", Spell = "..sSpell..", Foe = "..sFoe..", Damage = "..iDamage);

                        SkM_LogDamage_OnPvpEnemy(iDamage, _PlayerName, sFoe);
                        return;
                end

        end

        for sAttacker, sSpell, sFoe, iDamage in string.gfind(sMsg, SKM_Context.Pattern.Other_SpellCritDamage) do
                if (sAttacker and sSpell and sFoe and iDamage) then
                        SkM_Trace(FName, 3, "Other_SpellCritDamage : Attacker = "..sAttacker..", Spell = "..sSpell..", Foe = "..sFoe..", Damage = "..iDamage);

                        SkM_LogDamage_OnPvpEnemy(iDamage, _PlayerName, sFoe);
                        return;
                end
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_ParseCombatChat_EnemyDot
-- --------------------------------------------------------------------------------------
-- Handle "Enemy player suffers from DoT" chat event.
-- Record damage done (by either self of another player) to this enemy.
-- --------------------------------------------------------------------------------------
function SkM_ParseCombatChat_EnemyDot(sMsg)
        local FName = "SkM_ParseCombatChat_EnemyDot";

        SkM_Trace(FName, 3, "An enemy suffers from DoT damage");

        -- PIng: Patch for French version

        if ( GetLocale() == "frFR" ) then
                for sSpell, iDamage, sDamageType, sFoe  in string.gfind(sMsg, SKM_Context.Pattern.Player_DotDamage) do
                        if (sFoe and iDamage and sDamageType and sSpell) then
                                SkM_Trace(FName, 3, "Player_DotDamage : Foe = "..sFoe..", Damage = "..iDamage..", Damage type = "..sDamageType..", Spell = "..sSpell);

                                SkM_LogDamage_OnPvpEnemy(iDamage, _PlayerName, sFoe);
                                return;
                        end
                end
                for sSpell, sAttacker, sFoe, iDamage, sDamageType  in string.gfind(sMsg, SKM_Context.Pattern.Other_DotDamage) do
                        if (sFoe and iDamage and sDamageType and sAttacker and sSpell) then
                                SkM_Trace(FName, 3, "Other_DotDamage : Attacker = "..sAttacker..", Foe = "..sFoe..", Damage = "..iDamage..", Damage type = "..sDamageType..", Spell = "..sSpell);

                                SkM_LogDamage_OnPvpEnemy(iDamage, sAttacker, sFoe);
                                return;
                        end
                end

        else
                for sFoe, iDamage, sDamageType, sSpell in string.gfind(sMsg, SKM_Context.Pattern.Player_DotDamage) do
                        if (sFoe and iDamage and sDamageType and sSpell) then
                                SkM_Trace(FName, 3, "Player_DotDamage : Foe = "..sFoe..", Damage = "..iDamage..", Damage type = "..sDamageType..", Spell = "..sSpell);

                                SkM_LogDamage_OnPvpEnemy(iDamage, _PlayerName, sFoe);
                                return;
                        end
                end
                for sFoe, iDamage, sDamageType, sAttacker, sSpell in string.gfind(sMsg, SKM_Context.Pattern.Other_DotDamage) do
                        if (sFoe and iDamage and sDamageType and sAttacker and sSpell) then
                                SkM_Trace(FName, 3, "Other_DotDamage : Attacker = "..sAttacker..", Foe = "..sFoe..", Damage = "..iDamage..", Damage type = "..sDamageType..", Spell = "..sSpell);

                                SkM_LogDamage_OnPvpEnemy(iDamage, sAttacker, sFoe);
                                return;
                        end
                end
        end

end


-- --------------------------------------------------------------------------------------

-- SkM_ParseCombatChat_SelfDot
-- --------------------------------------------------------------------------------------
-- Handle "Self suffers from DoT" chat event.
-- Record damage done to self, without any "enemy type" as we don't know if the enemy

-- is a player or a creep.
-- --------------------------------------------------------------------------------------
function SkM_ParseCombatChat_SelfDot(sMsg)
        local FName = "SkM_ParseCombatChat_SelfDot";

        SkM_Trace(FName, 3, "Self suffers from DoT damage");

        -- We suffered from a DoT : in this case, log damage without enemy type, because
        -- we do *not* know from the event if it's a player or a creature that cast the DoT !

        if ( GetLocale() == "frFR" ) then
                -- 0.09.1 Begin of modification Fix ?
                for sSpell, sAttacker, iDamage, sDamageType  in string.gfind(sMsg, SKM_Context.Pattern.Self_DotDamage) do
                        if (iDamage and sDamageType and sAttacker and sSpell) then
                                SkM_Trace(FName, 3, "Self_DotDamage : Attacker = "..sAttacker..", Damage = "..iDamage..", Damage type = "..sDamageType..", Spell = "..sSpell);

                                SkM_LogDamage_OnSelf(iDamage, sAttacker, nil);
                                return;
                        end
                end
                -- 0.09.1 End of modification

        else

                for iDamage, sDamageType, sAttacker, sSpell  in string.gfind(sMsg, SKM_Context.Pattern.Self_DotDamage) do
                        if (iDamage and sDamageType and sAttacker and sSpell) then
                                SkM_Trace(FName, 3, "Self_DotDamage : Attacker = "..sAttacker..", Damage = "..iDamage..", Damage type = "..sDamageType..", Spell = "..sSpell);

                                SkM_LogDamage_OnSelf(iDamage, sAttacker, nil);
                                return;
                        end
                end

        end

end


-- --------------------------------------------------------------------------------------
-- SkM_ParseCombatChat_EnemyCombatHit
-- --------------------------------------------------------------------------------------
-- Handle "Enemy player hits something" chat event.
-- If we are the victim, record damage done to self by that enemy.
-- --------------------------------------------------------------------------------------
function SkM_ParseCombatChat_EnemyCombatHit(sMsg)
        local FName = "SkM_ParseCombatChat_EnemyCombatHit";

        SkM_Trace(FName, 3, "A player enemy does physical damage to something");

        -- that may be an enemy PET also ! so we need to check if we know this enemy
        -- if not, log it without "enemy type" filled...

        for sAttacker, iDamage in string.gfind(sMsg, SKM_Context.Pattern.Self_HitDamage) do
                if (sAttacker and iDamage) then
                        SkM_Trace(FName, 3, "Self_HitDamage : Attacker = "..sAttacker..", Damage = "..iDamage);

                        SkM_LogDamage_OnSelf(iDamage, sAttacker, SkM_GetKnownEnemyType(sAttacker));
                        return;
                end
        end

        for sAttacker, iDamage in string.gfind(sMsg, SKM_Context.Pattern.Self_HitCritDamage) do
                if (sAttacker and iDamage) then
                        SkM_Trace(FName, 3, "Self_CritHitDamage : Attacker = "..sAttacker..", Damage = "..iDamage);

                        SkM_LogDamage_OnSelf(iDamage, sAttacker, SkM_GetKnownEnemyType(sAttacker));
                        return;
                end
        end

        -- Self is not the target
        -- we don't care who the hostile player hits and for which amount !
        SkM_Trace(FName, 3, "Hostile picks on other player or mob : do nothing");
end


-- --------------------------------------------------------------------------------------
-- SkM_ParseCombatChat_EnemyCombatSpell
-- --------------------------------------------------------------------------------------
-- Handle "Enemy player does spell damage to something" chat event.
-- If we are the victim, record damage done to self by that enemy.
-- --------------------------------------------------------------------------------------
function SkM_ParseCombatChat_EnemyCombatSpell(sMsg)
        local FName = "SkM_ParseCombatChat_EnemyCombatSpell";

        SkM_Trace(FName, 3, "A player enemy does spell damage to something");

        -- that may be an enemy PET also ! so we need to check if we know this enemy
        -- if not, log it without "enemy type" filled...

        -- Is Self the target ?

        -- PIng: Patch for French version

        if ( GetLocale() == "frFR" ) then

                for  sSpell, sAttacker, iDamage in string.gfind(sMsg, SKM_Context.Pattern.Self_SpellDamage) do
                        if (sAttacker and sSpell and iDamage) then
                                SkM_Trace(FName, 3, "Self_SpellDamage : Attacker = "..sAttacker..", Spell = "..sSpell..", Damage = "..iDamage);

                                if (SkM_IsTotem(sAttacker)) then

                                        -- ignore totem damage
                                        return;
                                end

                                SkM_LogDamage_OnSelf(iDamage, sAttacker, SkM_GetKnownEnemyType(sAttacker));
                                return;
                        end
                end
        else

                for sAttacker, sSpell, iDamage in string.gfind(sMsg, SKM_Context.Pattern.Self_SpellDamage) do
                        if (sAttacker and sSpell and iDamage) then
                                SkM_Trace(FName, 3, "Self_SpellDamage : Attacker = "..sAttacker..", Spell = "..sSpell..", Damage = "..iDamage);

                                if (SkM_IsTotem(sAttacker)) then
                                        -- ignore totem damage
                                        return;
                                end

                                SkM_LogDamage_OnSelf(iDamage, sAttacker, SkM_GetKnownEnemyType(sAttacker));
                                return;
                        end
                end
        end

        for sAttacker, sSpell, iDamage in string.gfind(sMsg, SKM_Context.Pattern.Self_SpellCritDamage) do
                if (sAttacker and sSpell and iDamage) then
                        SkM_Trace(FName, 3, "Self_SpellCritDamage : Attacker = "..sAttacker..", Spell = "..sSpell..", Damage = "..iDamage);

                        SkM_LogDamage_OnSelf(iDamage, sAttacker, SkM_GetKnownEnemyType(sAttacker));
                        return;
                end
        end



        -- Self is not the target
        -- we don't care who the hostile player hits and for which amount !
        SkM_Trace(FName, 3, "Hostile picks on other player or mob : do nothing");
end


-- --------------------------------------------------------------------------------------
-- SkM_ParseCombatChat_CreatureCombatHit
-- --------------------------------------------------------------------------------------
-- Handle "Creature hits something" chat event.
-- If we are the victim, record damage done to self by that creature.
-- --------------------------------------------------------------------------------------
function SkM_ParseCombatChat_CreatureCombatHit(sMsg)
        local FName = "SkM_ParseCombatChat_CreatureCombatHit";

        SkM_Trace(FName, 3, "A creature does physical damage to something");

        for sAttacker, iDamage in string.gfind(sMsg, SKM_Context.Pattern.Self_HitDamage) do
                if (sAttacker and iDamage) then
                        SkM_Trace(FName, 3, "Self_HitDamage : Attacker = "..sAttacker..", Damage = "..iDamage);

                        SkM_LogDamage_OnSelf(iDamage, sAttacker, _SKM._enemyCreature);
                        return;
                end
        end

        for sAttacker, iDamage in string.gfind(sMsg, SKM_Context.Pattern.Self_HitCritDamage) do
                if (sAttacker and iDamage) then
                        SkM_Trace(FName, 3, "Self_CritHitDamage : Attacker = "..sAttacker..", Damage = "..iDamage);

                        SkM_LogDamage_OnSelf(iDamage, sAttacker, _SKM._enemyCreature);
                        return;
                end
        end

        -- Self is not the target
        -- we don't care who the creature hits and for which amount !
        SkM_Trace(FName, 3, "Creature picks on other player or mob : do nothing");
end


-- --------------------------------------------------------------------------------------
-- SkM_ParseCombatChat_CreatureCombatSpell
-- --------------------------------------------------------------------------------------
-- Handle "Creature does spell damage to something" chat event.
-- If we are the victim, record damage done to self by that creature.
-- --------------------------------------------------------------------------------------
function SkM_ParseCombatChat_CreatureCombatSpell(sMsg)
        local FName = "SkM_ParseCombatChat_CreatureCombatSpell";

        SkM_Trace(FName, 3, "A creature does spell damage to something");

        -- 0.08.1 Begin of modification: Patch for French version

        if ( GetLocale() == "frFR" ) then

                for  sSpell, sAttacker, iDamage in string.gfind(sMsg, SKM_Context.Pattern.Self_SpellDamage) do
                        if (sAttacker and sSpell and iDamage) then
                                SkM_Trace(FName, 3, "Self_SpellDamage : Attacker = "..sAttacker..", Spell = "..sSpell..", Damage = "..iDamage);

                                if (SkM_IsTotem(sAttacker)) then
                                        -- ignore totem damage

                                        return;
                                end

                                SkM_LogDamage_OnSelf(iDamage, sAttacker, _SKM._enemyCreature);
                                return;
                        end
                end
        else

              for sAttacker, sSpell, iDamage in string.gfind(sMsg, SKM_Context.Pattern.Self_SpellDamage) do
                        if (sAttacker and sSpell and iDamage) then
                                SkM_Trace(FName, 3, "Self_SpellDamage : Attacker = "..sAttacker..", Spell = "..sSpell..", Damage = "..iDamage);

                                if (SkM_IsTotem(sAttacker)) then
                                        -- ignore totem damage
                                        return;
                                end

                                SkM_LogDamage_OnSelf(iDamage, sAttacker, _SKM._enemyCreature);
                                return;
                        end
                end
        end
        -- 0.08.1 End of modification

        for sAttacker, sSpell, iDamage in string.gfind(sMsg, SKM_Context.Pattern.Self_SpellCritDamage) do
                if (sAttacker and sSpell and iDamage) then
                        SkM_Trace(FName, 3, "Self_SpellCritDamage : Attacker = "..sAttacker..", Spell = "..sSpell..", Damage = "..iDamage);

                        SkM_LogDamage_OnSelf(iDamage, sAttacker, _SKM._enemyCreature);
                        return;
                end
        end

        -- Self is not the target
        -- we don't care who the creature hits and for which amount !
        SkM_Trace(FName, 3, "Creature picks on other player or mob : do nothing");
end


-- --------------------------------------------------------------------------------------
-- SkM_ParseCombatChat_HostileDeath
-- --------------------------------------------------------------------------------------
-- Handle "Enemy player dies" chat event.
-- Find out from information previously gathered if we can be awarded this kill,
-- and if so record it.
-- --------------------------------------------------------------------------------------
function SkM_ParseCombatChat_HostileDeath(sMsg)
        local FName = "SkM_ParseCombatChat_HostileDeath";

        for sFoe in string.gfind(sMsg, SKM_Context.Pattern.Other_Death) do
                if (sFoe) then
                        SkM_Trace(FName, 3, "Other_Death : Foe = "..sFoe);


                        SkM_Trace(FName, 2, "Enemy player death detected (from chat msg) : "..snil(sFoe));
                        SkM_PvpEnemyDeath(sFoe);
                        return;
                end
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_ParseCombatChat_HonorKill
-- --------------------------------------------------------------------------------------
-- Handle "Enemy player dies and is worth honor" chat event.
-- Find out from information previously gathered if we can be awarded this kill,
-- and if so record it.

-- --------------------------------------------------------------------------------------
function SkM_ParseCombatChat_HonorKill(sMsg)
        local FName = "SkM_ParseCombatChat_HonorKill";

        for sFoe, sRank in string.gfind(sMsg, SKM_Context.Pattern.Honor_Kill) do
                if (sFoe and sRank) then
                        SkM_Trace(FName, 3, "Honor_Kill : Foe = "..sFoe..", Rank = "..sRank);

                        SkM_Trace(FName, 2, "Enemy player death detected (from chat, honor) : "..snil(sFoe));
                        SkM_PvpEnemyDeath(sFoe, true, sRank);
                end
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_RecordCreatureKill_Xp
-- --------------------------------------------------------------------------------------
-- Record a creature kill by "combat xp message".
-- --------------------------------------------------------------------------------------
function SkM_RecordCreatureKill_Xp(sName)
        local FName = "SkM_RecordCreatureKill_Xp";

        if (not SkM_GetOption("RecordCreatureKill")) then
                return;
        end


        local curTime = GetTime();

        SkM_Trace(FName, 3, "Creature by xp kill : "..sName.." (time = "..curTime..")");

        -- check if we're already processed this kill on health change
        if (SKM_Context.LastCreatureKill) then

                SkM_Trace(FName, 3, "Recent creature kill : "..snil(SKM_Context.LastCreatureKill[_SKM._name]).." (time = "..snil(SKM_Context.LastCreatureKill[_SKM._time])..")");

                if (SKM_Context.LastCreatureKill[_SKM._name] == sName) and (curTime < SKM_Context.LastCreatureKill[_SKM._time] + 1) then
                        SkM_Trace(FName, 2, "Already processed kill : "..sName);

                        -- clear last creature kill and return
                        SKM_Context.LastCreatureKill = nil;
                        return;
                end
        end

        -- kill not recorded by target kill, process it
        local StoreInfo = { };

        StoreInfo[_SKM._type] = _SKM._creatureKill_Xp;

        local sDate1, sDate2 = SkM_GetDate();
        StoreInfo[_SKM._date] = sDate1;
        --StoreInfo[_sortdate] = sDate2;

        StoreInfo[_SKM._name] = sName;

        if (not SkM_AddMapData(StoreInfo)) then
                return;
        end


        if (SkM_GetOption("DisplayCreatureKillRecord")) then
                SkM_ChatMessageColP(string.format(SKM_UI_STRINGS.RecordMessage_CreatureKill, sName), SKM_Config.RGB_CreatureKill);
        end

        -- memorize killed mob to that we don't count it again when receiving xp message
        SKM_Context.LastCreatureKill = { };
        SKM_Context.LastCreatureKill[_SKM._name] = sName;
        SKM_Context.LastCreatureKill[_SKM._time] = GetTime();

        --local idx_c, idx_z = SkM_GetZone();
        local idx_c, idx_z = SkM_GetZone_Shift();
        if (idx_z) then
                SkM_CleanZoneCreatureKill(idx_c, idx_z);
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_RecordCreatureKill_Target
-- --------------------------------------------------------------------------------------
-- Record a creature kill by target (the creature we are currently targetting dies,
-- and it was tapped by us).
-- --------------------------------------------------------------------------------------
function SkM_RecordCreatureKill_Target(sName, iLevel, sCreatureClass)
        local FName = "SkM_RecordCreatureKill_Target";

        if (not SkM_GetOption("RecordCreatureKill")) then
                return;
        end


        local curTime = GetTime();

        SkM_Trace(FName, 3, "Creature by target kill : "..sName.." (time = "..curTime..")");

        -- check if we're already processed this kill on combat xp message
        if (SKM_Context.LastCreatureKill) then

                SkM_Trace(FName, 3, "Recent creature kill : "..snil(SKM_Context.LastCreatureKill[_SKM._name]).." (time = "..snil(SKM_Context.LastCreatureKill[_SKM._time])..")");

                if (SKM_Context.LastCreatureKill[_SKM._name] == sName) and (curTime < SKM_Context.LastCreatureKill[_SKM._time] + 1) then
                        SkM_Trace(FName, 2, "Already processed kill : "..sName);

                        -- clear last creature kill and return
                        SKM_Context.LastCreatureKill = nil;
                        return;
                end
        end

        local StoreInfo = { };

        StoreInfo[_SKM._type] = _SKM._creatureKill_Target;

        local sDate1, sDate2 = SkM_GetDate();
        StoreInfo[_SKM._date] = sDate1;
        --StoreInfo[_sortdate] = sDate2;

        StoreInfo[_SKM._name] = sName;
        StoreInfo[_SKM._level] = iLevel;
        StoreInfo[_SKM._class] = SKM_Config.CreatureClassLabel[sCreatureClass];

        if (not SkM_AddMapData(StoreInfo)) then
                return;
        end

        if (SkM_GetOption("DisplayCreatureKillRecord")) then
                SkM_ChatMessageColP(string.format(SKM_UI_STRINGS.RecordMessage_CreatureKill, sName), SKM_Config.RGB_CreatureKill);
        end


        -- memorize killed mob to that we don't count it again when receiving xp message
        SKM_Context.LastCreatureKill = { };
        SKM_Context.LastCreatureKill[_SKM._name] = sName;
        SKM_Context.LastCreatureKill[_SKM._time] = GetTime();

        --local idx_c, idx_z = SkM_GetZone();
        local idx_c, idx_z = SkM_GetZone_Shift();
        if (idx_z) then
                SkM_CleanZoneCreatureKill(idx_c, idx_z);
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_DeleteNote
-- --------------------------------------------------------------------------------------
-- Delete a global note. Remove this note from global and zone map data, and recompute
-- indexes of more recent notes.
-- --------------------------------------------------------------------------------------
function SkM_DeleteNote(RealmName, PlayerName, idx_gn)
        local FName = "SkM_DeleteNote";

        SkM_Trace(FName, 3, "Removing note ("..RealmName.." / "..PlayerName..") : global index = "..idx_gn);

        local idx_c, idx_z, idx_n;
        local val_c, val_z, val_n;

        local cont_rem, zone_rem, note_rem;

        for idx_c = 1,getn(SKM_Data[RealmName][PlayerName].MapData),1 do
                for idx_z = 1,getn(SKM_Data[RealmName][PlayerName].MapData[idx_c]) do
                        for idx_n = 1,getn(SKM_Data[RealmName][PlayerName].MapData[idx_c][idx_z]) do
                                local val_n = SKM_Data[RealmName][PlayerName].MapData[idx_c][idx_z][idx_n];
                                if (val_n == idx_gn) then
                                        cont_rem = idx_c;
                                        zone_rem = idx_z;
                                        note_rem = idx_n;
                                elseif (val_n > idx_gn) then
                                        SKM_Data[RealmName][PlayerName].MapData[idx_c][idx_z][idx_n] = val_n - 1;
                                end
                        end
                end
        end


        SkM_Trace(FName, 3, "Removing note : continent = "..snil(cont_rem)..", zone = "..snil(zone_rem)..", note = "..snil(note_rem));

        table.remove(SKM_Data[RealmName][PlayerName].MapData[cont_rem][zone_rem], note_rem);
        table.remove(SKM_Data[RealmName][PlayerName].GlobalMapData, idx_gn);
end


-- --------------------------------------------------------------------------------------
-- SkM_CleanZoneCreatureKill
-- --------------------------------------------------------------------------------------
-- A creature kill has been recorded, clean older creature kill records if necessary
-- (from configurable maximum number of creature records by zone).
-- --------------------------------------------------------------------------------------
function SkM_CleanZoneCreatureKill(idx_c, idx_z)
        local FName = "SkM_CleanZoneCreatureKill";


        SkM_Trace(FName, 3, "Continent = "..idx_c..", Zone = "..idx_z);

        local iZoneNoteCount = getn(SKM_Data[_RealmName][_PlayerName].MapData[idx_c][idx_z]);
        --iZoneNoteCount = tablesize(SKM_Data[_RealmName][_PlayerName].MapData[idx_c][idx_z]);

        SkM_Trace(FName, 3, "Notes in zone = "..iZoneNoteCount);

        local iCreatureKill = 0;
        local i;

        for i=iZoneNoteCount, 1, -1 do
                local idx_gn = SKM_Data[_RealmName][_PlayerName].MapData[idx_c][idx_z][i];
                local Note = SKM_Data[_RealmName][_PlayerName].GlobalMapData[idx_gn];

                if ((Note) and (Note[_SKM._storedInfo])) 
                and ((Note[_SKM._storedInfo][_SKM._type] == _SKM._creatureKill_Target) or (Note[_SKM._storedInfo][_SKM._type] == _SKM._creatureKill_Xp)) then
                        if (iCreatureKill >= SkM_GetOption("CreatureKillRecordsByZone")) then

                                -- already reached the max count of creature kill notes by zone
                                -- have to delete this one.

                                -- remove from GlobalMapData and from MapData
                                SkM_Trace(FName, 3, "Removing note : global index = "..idx_gn..", map index = "..i);

                                SkM_DeleteNote(_RealmName, _PlayerName, idx_gn);

                        else
                                iCreatureKill = iCreatureKill + 1;
                        end
                end
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_CheckForgetEnemy
-- --------------------------------------------------------------------------------------
-- Check if we can forget an enemy that has engaged in combat against us.
-- If no update has been received in a given time frame, then forget him.
-- --------------------------------------------------------------------------------------
function SkM_CheckForgetEnemy(sName)
        local Name = "SkM_CheckForgetEnemy";

        local curTime = GetTime();

        if (SKM_Context.EnemyCombat[sName]) then
                -- there exists information about that enemy.
                -- check when last updated
                if (curTime - SKM_Context.EnemyCombat[sName][_SKM._lastUpdate] > SKM_Config.ForgetEnemyTimer) then
                        SKM_Context.EnemyCombat[sName] = nil;
                end
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_BuildGroupList
-- --------------------------------------------------------------------------------------
-- Build a list of players currently in the group. Called each time the group members
-- change.
-- --------------------------------------------------------------------------------------
function SkM_BuildGroupList()
        local FName = "SkM_BuildGroupList";

        SkM_Trace(FName, 2, "Party changed, rebuild group list");


        SKM_Context.GroupList = { };

        local i;
        for i=1,GetNumPartyMembers() do
                local sUnit = SKM_UNIT_PARTY .. i;
                local sName = SkM_UnitName(sUnit);

                if (sName) then
                        table.insert(SKM_Context.GroupList, sName);
                end
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_IsNameInGroup
-- --------------------------------------------------------------------------------------
-- Check if given player name is in party.
-- --------------------------------------------------------------------------------------
function SkM_IsNameInGroup(sName)
        return (        (sName == _PlayerName)
                 or (intable(sName, SKM_Context.GroupList))
        );
end


-- --------------------------------------------------------------------------------------
-- SkM_LogDamage_OnPvpEnemy
-- --------------------------------------------------------------------------------------
-- Keep track of damage done on an enemy, and who did this damage (self or our group,
-- or someone else).
-- --------------------------------------------------------------------------------------
function SkM_LogDamage_OnPvpEnemy(iDamage, sFriendName, sName)
        local FName = "SkM_LogDamage_OnPvpEnemy";

        local curTime = GetTime();

        -- first check if we remember this enemy and if no update was received for a long time,
        -- then forget him and start anew.
        SkM_CheckForgetEnemy(sName);

        if (not SKM_Context.EnemyCombat[sName]) then
                SKM_Context.EnemyCombat[sName] = { };
                SKM_Context.EnemyCombat[sName][_SKM._name] = sName;
                SKM_Context.EnemyCombat[sName][_SKM._totalDamage] = 0;
                SKM_Context.EnemyCombat[sName][_SKM._groupDamage] = 0;
        end

        SKM_Context.EnemyCombat[sName][_SKM._lastUpdate] = curTime;

        SKM_Context.EnemyCombat[sName][_SKM._totalDamage] = SKM_Context.EnemyCombat[sName][_SKM._totalDamage] + iDamage;

        if (SkM_IsNameInGroup(sFriendName)) then
                SKM_Context.EnemyCombat[sName][_SKM._groupDamage] = SKM_Context.EnemyCombat[sName][_SKM._groupDamage] + iDamage;
        end

        SkM_Trace(FName, 3, "EnemyCombat updated for "..sName.." : Total damage = ".. SKM_Context.EnemyCombat[sName][_SKM._totalDamage] ..", Group damage = ".. SKM_Context.EnemyCombat[sName][_SKM._groupDamage]);
end


function SkM_UpdateEnemyHistory()
        SKM_Context.PlayerDataChanged = true;
end


-- --------------------------------------------------------------------------------------
-- SkM_UpdateEnemy_IncrCounter
-- --------------------------------------------------------------------------------------
-- Increment enemy counter for a given type, and enemy guild counter if applicable
-- --------------------------------------------------------------------------------------
function SkM_UpdateEnemy_IncrCounter(sName, sType, iValue, bPropagateToGuild)
        SkM_UpdateEnemyHistory();

        SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][sType] = ifnil(SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][sType], 0) + iValue;

        if (bPropagateToGuild) then
                local sGuildName = SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._guild];
                if ((sGuildName ~= nil) and (sGuildName ~= "")) then
                        SkM_UpdateGuild_IncrCounter(sGuildName, sType, iValue);
                end
        end
end


-- --------------------------------------------------------------------------------------
-- SkM_UpdateEnemy_IncrPlayerKill
-- --------------------------------------------------------------------------------------
-- Increment enemy kill count, and enemy guild kill count if applicable
-- --------------------------------------------------------------------------------------
function SkM_UpdateEnemy_IncrPlayerKill(sName, iValue)
        SkM_UpdateEnemy_IncrCounter(sName, _SKM._playerKill, iValue, true);
end


-- --------------------------------------------------------------------------------------
-- SkM_UpdateEnemy_IncrPlayerFullKill
-- --------------------------------------------------------------------------------------
-- Increment enemy full kill count, and enemy guild full kill count if applicable
-- --------------------------------------------------------------------------------------
function SkM_UpdateEnemy_IncrPlayerFullKill(sName, iValue)
        SkM_UpdateEnemy_IncrCounter(sName, _SKM._playerFullKill, iValue, true);
end


-- --------------------------------------------------------------------------------------
-- SkM_UpdateEnemy_IncrPlayerAssistKill
-- --------------------------------------------------------------------------------------
-- Increment enemy assist kill count, and enemy guild assist kill count if applicable
-- --------------------------------------------------------------------------------------
function SkM_UpdateEnemy_IncrPlayerAssistKill(sName, iValue)
        SkM_UpdateEnemy_IncrCounter(sName, _SKM._playerAssistKill, iValue, true);
end


-- --------------------------------------------------------------------------------------
-- SkM_UpdateEnemy_IncrKillPlayer
-- --------------------------------------------------------------------------------------
-- Increment enemy kill player count, and enemy guild kill player count if applicable
-- --------------------------------------------------------------------------------------
function SkM_UpdateEnemy_IncrKillPlayer(sName, iValue, bBattleground)
        local sType;
        if (bBattleground) then
                sType = _SKM._enemyKillBG;
        else
                sType = _SKM._enemyKillPlayer;
        end
        SkM_UpdateEnemy_IncrCounter(sName, sType, iValue, true);
end

-- --------------------------------------------------------------------------------------
-- SkM_UpdateEnemy_IncrHonorKill
-- --------------------------------------------------------------------------------------
-- Increment enemy honor kill count, and enemy guild kill count if applicable
-- --------------------------------------------------------------------------------------
function SkM_UpdateEnemy_IncrHonorKill(sName, iValue)
        SkM_UpdateEnemy_IncrCounter(sName, _SKM._honorKill, iValue, true);
end

function SkM_UpdateEnemy_IncrLoneWolfKill(sName, iValue)
        SkM_UpdateEnemy_IncrCounter(sName, _SKM._loneWolfKill, iValue, true);
end



-- --------------------------------------------------------------------------------------
-- SkM_UpdateEnemy_IncrMeet
-- --------------------------------------------------------------------------------------
-- Increment enemy meet count
-- --------------------------------------------------------------------------------------
function SkM_UpdateEnemy_IncrMeet(sName, iValue)
        SkM_UpdateEnemy_IncrCounter(sName, _SKM._meetCount, iValue, false);
end


-- --------------------------------------------------------------------------------------
-- SkM_UpdateEnemy_SetLastView
-- --------------------------------------------------------------------------------------
-- Update enemy last view date/time
-- --------------------------------------------------------------------------------------
function SkM_UpdateEnemy_SetLastView(sName, sDate)
        SkM_UpdateEnemyHistory();

        SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._lastView] = sDate;
end


-- --------------------------------------------------------------------------------------
-- SkM_UpdateEnemy_SetLastUpdate

-- --------------------------------------------------------------------------------------
-- Update enemy last update date/time
-- --------------------------------------------------------------------------------------
function SkM_UpdateEnemy_SetLastUpdate(sName, sDate)
        SkM_UpdateEnemyHistory();

        SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._lastUpdate] = sDate;
end


-- --------------------------------------------------------------------------------------
-- SkM_UpdateEnemy_SetLevel
-- --------------------------------------------------------------------------------------
-- Update enemy level
-- --------------------------------------------------------------------------------------
function SkM_UpdateEnemy_SetLevel(sName, iLevel)
        SkM_UpdateEnemyHistory();

        SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._level] = iLevel;
end


-- --------------------------------------------------------------------------------------
-- SkM_UpdateEnemy_SetClass
-- --------------------------------------------------------------------------------------
-- Update enemy class
-- --------------------------------------------------------------------------------------
function SkM_UpdateEnemy_SetClass(sName, sClass)
        SkM_UpdateEnemyHistory();

        if (not SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._class]) then
                SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._class] = sClass;
        end
end


-- --------------------------------------------------------------------------------------
-- SkM_UpdateEnemy_SetRace
-- --------------------------------------------------------------------------------------
-- Update enemy race
-- --------------------------------------------------------------------------------------
function SkM_UpdateEnemy_SetRace(sName, sRace)
        SkM_UpdateEnemyHistory();

        if (not SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._race]) then
                SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._race] = sRace;
        end
end


-- --------------------------------------------------------------------------------------
-- SkM_UpdateEnemy_SetLocation
-- --------------------------------------------------------------------------------------
-- Update enemy last seen location
-- --------------------------------------------------------------------------------------
function SkM_UpdateEnemy_SetLocation(sName, idx_c, idx_z, xPos, yPos)
        SkM_UpdateEnemyHistory();

        SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._zoneName] = nil;

        SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._continent] = idx_c;
        SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._zone] = idx_z;
        SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._xPos] = xPos;
        SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._yPos] = yPos;
end

function SkM_UpdateEnemy_SetLocationName(sName, sZoneName)
        SkM_UpdateEnemyHistory();

        SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._continent] = nil;
        SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._zone] = nil;
        SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._xPos] = nil;
        SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._yPos] = nil;

        SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._zoneName] = sZoneName;
end


-- --------------------------------------------------------------------------------------
-- SkM_UpdateEnemy_SetWar
-- --------------------------------------------------------------------------------------
-- Update enemy 'at war' status, and war date
-- --------------------------------------------------------------------------------------
function SkM_UpdateEnemy_SetWar(sName, bWar, sDate)
        SkM_UpdateEnemyHistory();

        SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._atWar] = bWar;
        if (bWar) then
                SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._warDate] = sDate;
        else
                SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._warDate] = nil;
        end
end


-- --------------------------------------------------------------------------------------
-- SkM_UpdateEnemyLastView
-- --------------------------------------------------------------------------------------
-- Received some information about an enemy : update this enemy information
-- --------------------------------------------------------------------------------------
function SkM_UpdateEnemyLastView(sName, sDate, bTarget, sUnit)
        local FName = "SkM_UpdateEnemyLastView";

        SkM_UpdateEnemyHistory();

        local sTheUnit;
        if (sUnit) then
                sTheUnit = sUnit;
        else
                sTheUnit = SKM_UNIT_TARGET;
        end

        -- if we have unit on target (or mouseover), check if we should ignore or not
        if (bTarget) then

                -- if we already have recorded this player, we update his information anyway,
                -- even if he's low level, and even if he has no PvP flag

                -- if we have added this enemy to the unknown enemy at war, then we record him anyway

                if    (not SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName])
                  and (not SKM_Data[_RealmName][_PlayerName].UnknownEnemy[sName])
                then

                        if (not SkM_GetOption("StoreEnemyPlayers")) then
                                SkM_Trace(FName, 2, "Storing disabled");
                                return;
                        end

                        if (SkM_GetOption("IgnoreLowerEnemies")) then
                                local iUnitLevel = UnitLevel(sTheUnit);
                                if (iUnitLevel == -1) then
                                        iUnitLevel = 500;
                                end
                                local iMinLevel = math.floor( UnitLevel(SKM_UNIT_PLAYER) * SkM_GetOption("IgnoreLevelThreshold") / 100 );
                                if ( iUnitLevel < iMinLevel ) then
                                        SkM_Trace(FName, 1, "Not storing, too low level : "..iUnitLevel..", min. = "..iMinLevel);
                                        return;
                                end
                        end
                end

                if (SkM_GetOption("IgnoreNoPvPFlag")) then
                        if not (UnitIsPVP(sTheUnit) or UnitIsPVPFreeForAll(sTheUnit)) then
                                SkM_Trace(FName, 1, "Not storing, Unit "..snil(sTheUnit).." is not PvP flagged");
                                return;
                        end
                end

        end

        if (not SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName]) then
                SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName] = { };
                SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._name] = sName;
                SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._playerAssistKill] = 0;
                SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._playerKill] = 0;
                SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._playerFullKill] = 0;
                SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._enemyKillPlayer] = 0;
                SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._enemyKillBG] = 0;
                SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._playerBGKill] = 0;
                SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._meetCount] = 1;

                if (SKM_Data[_RealmName][_PlayerName].UnknownEnemy[sName]) then
                        SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._meetCount] = 1;
                        SKM_Data[_RealmName][_PlayerName].UnknownEnemy[sName] = nil;

                        SkM_Trace(FName, 2, snil(sName).."Added with 'at WAR' status");
                end

                SkM_Trace(FName, 1, "First encounter of enemy : "..snil(sName));
        else

                if ((not SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._meetCount]
                  or (not SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._lastView]))) then

                        -- 09/04/2005 18:44:32 PIng Add a check since LastView is updated later now.
                        SkM_UpdateEnemy_IncrMeet(sName, 1);
                else
                        -- increase number of time player met this enemy if last viewed time is older than
                        -- configurable value
                        local iDiffTime = SkM_DiffDate(sDate, SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._lastView]);
                        if (iDiffTime ~= nil) and (iDiffTime > SKM_Config.TimeRangeForNewMeeting) then
                                SkM_UpdateEnemy_IncrMeet(sName, 1);
                        end
                end

        end

        -- if we have unit on target (or mouseover), update information
        if (bTarget) then
                SkM_UpdateEnemy_SetLevel(sName, UnitLevel(sTheUnit));
                SkM_UpdateEnemy_SetClass(sName, SkM_GetClassIndex(UnitClass(sTheUnit)));
                SkM_UpdateEnemy_SetRace(sName, SkM_GetRaceIndex(UnitRace(sTheUnit)));
                SkM_UpdateEnemy_SetGuild(sName, ifnil(GetGuildInfo(sTheUnit),"")); -- PIng Add guild support
                --SkM_UpdateEnemy_SetLastUpdate(sName, sDate);

                -- get/update rank also
                local iRank = UnitPVPRank(sTheUnit);
                if (iRank >= 1) then
                        local sRank = GetPVPRankInfo(iRank, sTheUnit);
                        if (sRank) then
                                SkM_UpdateEnemyRank(sName, sRank);
                        end
                end

        end

        local idx_c, idx_z = SkM_GetZone();
        if (idx_z) then
                SetMapZoom(idx_c, idx_z);
                local xPos, yPos = GetPlayerMapPosition(SKM_UNIT_PLAYER);

                --SkM_UpdateEnemy_SetLocation(sName, idx_c, idx_z, xPos, yPos);

                local cont_shift, zone_shift = SkM_GetZone_Shift(idx_c, idx_z);
                SkM_UpdateEnemy_SetLocation(sName, cont_shift, zone_shift, xPos, yPos);
        else
                local sZoneName = SkM_GetZoneText();
                SkM_UpdateEnemy_SetLocationName(sName, sZoneName);
        end

        -- 09/04/2005 16:46:34 PIng: Add guild support

        -- Update guild if known
        local sGuildName = SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._guild];
        -- if ((sGuildName ~= nil) and (sGuildName ~= "not in a guild")) then
        if ((sGuildName ~= nil) and (sGuildName ~= "")) then
                SkM_UpdateGuildHistory(sGuildName, sDate, sName);
        end

        -- 09/04/2005 16:46:43 End of modification

        SkM_UpdateEnemy_SetLastView(sName, sDate);  -- 09/04/2005 18:30:05 PIng: Moved in order to have the information available for guild update

end


function SkM_UpdateEnemyRank(sName, sRank)
        SkM_UpdateEnemyHistory();

        SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._rank] = sRank;
end


function SkM_UpdateEnemy_SetGuild(sName, sGuild)
        SkM_UpdateEnemyHistory();

        --if (not SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._guild]) then
                SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._guild] = sGuild;
        --end
end

function SkM_UpdateGuild_SetLastView(sGuildName, sDate, sPlayerName)
        SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName][_SKM._lastView] = sDate;
        SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName][_SKM._lastPlayerViewed] = sPlayerName;
end

function SkM_UpdateGuild_IncrCounter(sGuildName, sType, iValue)
        SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName][sType] = ifnil(SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName][sType], 0) + iValue;
end

function SkM_UpdateGuild_IncrPlayerKill(sGuildName, iValue)
        SkM_UpdateGuild_IncrCounter(sGuildName, _SKM._playerKill, iValue);
end

function SkM_UpdateGuild_IncrPlayerFullKill(sGuildName, iValue)
        SkM_UpdateGuild_IncrCounter(sGuildName, _SKM._playerFullKill, iValue);
end

function SkM_UpdateGuild_IncrPlayerAssistKill(sGuildName, iValue)
        SkM_UpdateGuild_IncrCounter(sGuildName, _SKM._playerAssistKill, iValue);
end

function SkM_UpdateGuild_IncrKillPlayer(sGuildName, iValue, bBattleground)
        local sType;
        if (bBattleground) then
                sType = _SKM._enemyKillBG;
        else
                sType = _SKM._enemyKillPlayer;
        end
        SkM_UpdateGuild_IncrCounter(sGuildName, sType, iValue);
end

function SkM_UpdateGuild_IncrHonorKill(sGuildName, iValue)
        SkM_UpdateGuild_IncrCounter(sGuildName, _SKM._honorKill, iValue);
end

function SkM_UpdateGuild_IncrMeet(sGuildName, iValue)
        SkM_UpdateGuild_IncrCounter(sGuildName, _SKM._meetCount, iValue);
end


-- --------------------------------------------------------------------------------------

-- SkM_UpdateGuildHistory
-- --------------------------------------------------------------------------------------
-- Update guild information when receiving an update of a enemy from this guild
-- --------------------------------------------------------------------------------------
function SkM_UpdateGuildHistory(sGuildName, sDate, sName)
        local FName = "SkM_UpdateGuildHistory";

        SkM_Trace(FName, 2, "Guild Name : "..sGuildName..", Date : ".. sDate.. ", Player Name : "..sName);

        if (not SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName]) then
                SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName] = { };
                SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName][_SKM._name] = sGuildName;
                SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName][_SKM._playerAssistKill] = 0;
                SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName][_SKM._playerKill] = 0;
                SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName][_SKM._playerFullKill] = 0;
                SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName][_SKM._enemyKillPlayer] = 0;
                SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName][_SKM._enemyKillBG] = 0;
                SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName][_SKM._playerBGKill] = 0;
                SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName][_SKM._meetCount] = 1;
        else
                if  ((not SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName][_SKM._meetCount])
                  or (not SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._lastView])) then
                        SkM_Trace(FName, 2, "Increment Meet count for Guild for first time"..sGuildName);
                        SkM_UpdateGuild_IncrMeet(sGuildName, 1);
                else
                        -- increase number of time player met this guild if last viewed time for this enemy is older than
                        -- configurable value
                        SkM_Trace(FName, 2, "check if we need to increment");
                        local iDiffTime = SkM_DiffDate(sDate, SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._lastView]);
                        if (iDiffTime ~= nil) and (iDiffTime > SKM_Config.TimeRangeForNewMeeting) then
                                SkM_Trace(FName, 1, "Increment Meet count for Guild "..sGuildName);
                                SkM_UpdateGuild_IncrMeet(sGuildName, 1);
                        end
                end
                SkM_Trace(FName, 2, "Exit");

        end

        SkM_UpdateGuild_SetLastView(sGuildName, sDate, sName);
end



-- --------------------------------------------------------------------------------------
-- SkM_UpdateGuild_SetWar
-- --------------------------------------------------------------------------------------
-- Update guild 'at war' status and war date
-- --------------------------------------------------------------------------------------
function SkM_UpdateGuild_SetWar(sName, bWar, sDate)
        SKM_Data[_RealmName][_PlayerName].GuildHistory[sName][_SKM._atWar] = bWar;
        if (bWar) then
                SKM_Data[_RealmName][_PlayerName].GuildHistory[sName][_SKM._warDate] = sDate;
        else
                SKM_Data[_RealmName][_PlayerName].GuildHistory[sName][_SKM._warDate] = nil;
        end
end


function SkM_GetRecentEnemyKill(sName)
        local bFound = false;
        local iNoteIndex;
        local sDate = SkM_GetDate();
        local i;
        for i=table.getn(SKM_Context.RecentEnemyKill), 1, -1 do
                if (SKM_Context.RecentEnemyKill[i][_SKM._name] == sName) then
                        local iDiffTime = SkM_DiffDate(sDate, SKM_Context.RecentEnemyKill[i][_SKM._date]);
                        if (iDiffTime ~= nil) and (iDiffTime <= SKM_Config.RecentEnemyKillDelay) then
                                bFound = true;
                                iNoteIndex = SKM_Context.RecentEnemyKill[i][_SKM._noteIndex];
                                return bFound, iNoteIndex;
                        end
                end
        end
        return bFound, iNoteIndex;
end


function SkM_AddRecentEnemyKill(sName, sDate, iGlobalNote)
        local FName = "SkM_AddRecentEnemyKill";

        SkM_Trace(FName, 2, "Name = "..snil(sName)..", G. Note = "..snil(iGlobalNote));

        local Record = { };
        Record[_SKM._name] = sName;
        Record[_SKM._date] = sDate;
        Record[_SKM._noteIndex] = iGlobalNote;
        table.insert(SKM_Context.RecentEnemyKill, Record);

        while (table.getn(SKM_Context.RecentEnemyKill) > SKM_Config.MaxRecentEnemyKill) do
                table.remove(SKM_Context.RecentEnemyKill, 1);
        end
end


function SkM_GetRecentWarWarning(sName)
        local bFound = false;
        local sDate = SkM_GetDate();
        local i;
        for i=table.getn(SKM_Context.RecentWarWarning), 1, -1 do
                if (SKM_Context.RecentWarWarning[i][_SKM._name] == sName) then
                        local iDiffTime = SkM_DiffDate(sDate, SKM_Context.RecentWarWarning[i][_SKM._date]);
                        if (iDiffTime ~= nil) and (iDiffTime <= SKM_Config.RecentWarWarningDelay) then
                                bFound = true;
                                return bFound;
                        end
                end
        end
        return bFound;
end

function SkM_AddRecentWarWarning(sName, sDate)
        local Record = { };
        Record[_SKM._name] = sName;
        Record[_SKM._date] = sDate;
        table.insert(SKM_Context.RecentWarWarning, Record);

        while (table.getn(SKM_Context.RecentWarWarning) > SKM_Config.MaxRecentWarWarning) do
                table.remove(SKM_Context.RecentWarWarning, 1);
        end
end



-- --------------------------------------------------------------------------------------
-- SkM_PvpEnemyDeath
-- --------------------------------------------------------------------------------------
-- An enemy dies...
-- Check from his name if we know him as an enemy player. If it's the case, check how
-- much relative damage did the group to this enemy, and record appropriate kill event.
-- --------------------------------------------------------------------------------------
function SkM_PvpEnemyDeath(sName, bHonorKill, sRank)
        local FName = "SkM_PvpEnemyDeath";

        SkM_Trace(FName, 3, "Name = "..snil(sName));
        if (not sName) then
                SkM_Trace(FName, 1, "nil parameter received !");
                return;
        end

        SkM_CheckForgetEnemy(sName);

--      if (not SkM_GetOption("RecordPlayerKill")) then
--              return;
--      end


        -- check if in battleground
        local bBattleground = SkM_IsInBattleground();

        if (not SKM_Context.EnemyCombat[sName]) and (not bHonorKill) then
                -- no information about this enemy
                return;
        end

        if (not SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName]) then
                -- if this is a honor kill, then we *know* it is a player
                -- also check that "store enemy players" is enabled.

                if (not bHonorKill) or (not SkM_GetOption("StoreEnemyPlayers")) then
                        -- we have no information about the enemy just killed.
                        -- maybe he's a player that has never been targeted, but maybe not...
                        -- in doubt we do nothing
                        SkM_Trace(FName, 3, sName.." : type unknown...");

                        SKM_Context.EnemyCombat[sName] = nil;
                        return;
                end

        end

        local sDate1, sDate2 = SkM_GetDate();

        SkM_UpdateEnemyLastView(sName, sDate1, false);
        if (sRank) then
                SkM_UpdateEnemyRank(sName, sRank);
        end

        -- check if we have already recorded this kill
        local bRecorded, iNoteIndex;
        bRecorded, iNoteIndex = SkM_GetRecentEnemyKill(sName);
        if (bRecorded) then
                -- already recorded
                SkM_Trace(FName, 3, "Kill already recorded : idx_gn = "..snil(iNoteIndex));

                if (bHonorKill) then
                        SkM_Trace(FName, 3, "Adjust as honor kill");

                        -- we received an "honor kill", it means the kill was previously recorded due to
                        -- a event that was not an "honor kill" : now take honor info into account
--                      SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._honorKill] = ifnil(SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._honorKill], 0) + 1;
                        SkM_UpdateEnemy_IncrHonorKill(sName, 1);

                        SkM_SetHonorFlags(sName);

--                      local sGuildName = SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._guild];
--                      if ((sGuildName ~= nil) and (sGuildName ~= "")) then
--                              SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName][_SKM._honorKill] = ifnil(SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName][_SKM._honorKill], 0) + 1;
--                      end

                        if (iNoteIndex) then
                                SKM_Data[_RealmName][_PlayerName].GlobalMapData[iNoteIndex][_SKM._storedInfo][_SKM._honorKill] = true;
                        end

                end

                return;
        end

        SkM_Trace(FName, 3, "Kill not yet recorded");

        local KillType;

        if (not SKM_Context.EnemyCombat[sName]) then
                -- did not receive any combat information about this player, but
                -- we received an "honor" kill : award an "assist kill" by default
                SkM_Trace(FName, 3, "No combat info, but honor kill : award Assist Kill");

                KillType = _SKM._playerAssistKill;

        elseif (SKM_Context.EnemyCombat[sName][_SKM._groupDamage] == 0) then
                SkM_Trace(FName, 3, "Group didn't do any damage");

                if (bHonorKill) then
                        -- no damage recorded by our group, but we received an "honor kill" :
                        -- award an "assist kill" by default
                        KillType = _SKM._playerAssistKill;
                end

        else
                local iPercent = SKM_Context.EnemyCombat[sName][_SKM._groupDamage] / SKM_Context.EnemyCombat[sName][_SKM._totalDamage];

                if (iPercent < 0.50) then
                        SkM_Trace(FName, 3, "Group damage < 50% : award Assist Kill");
                        KillType = _SKM._playerAssistKill;
                elseif (iPercent < 1.00) then
                        SkM_Trace(FName, 3, "Group damage >= 50% and < 100% : award Kill");
                        KillType = _SKM._playerKill;
                else
                        SkM_Trace(FName, 3, "Group damage = 100% : award Full Kill");
                        KillType = _SKM._playerFullKill;
                end

        end

        if (KillType ~= nil) then

                if (bBattleground == true) then
                        KillType = _SKM._playerBGKill;
                end

                if (SkM_GetOption("DisplayKillRecord")) then
                        if (KillType == _SKM._playerAssistKill) then
                                SkM_ChatMessageColP(string.format(SKM_UI_STRINGS.RecordMessage_AssistKill, sName), SKM_Config.RGB_PlayerAssistKill);
                        elseif (KillType == _SKM._playerKill) then
                                SkM_ChatMessageColP(string.format(SKM_UI_STRINGS.RecordMessage_Kill, sName), SKM_Config.RGB_PlayerKill);
                        elseif (KillType == _SKM._playerFullKill) then
                                SkM_ChatMessageColP(string.format(SKM_UI_STRINGS.RecordMessage_FullKill, sName), SKM_Config.RGB_PlayerFullKill);
                        end
                end

                --SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][KillType] = ifnil(SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][KillType], 0) + 1;
                SkM_UpdateEnemy_IncrCounter(sName, KillType, 1, true);

                if (bBattleground == true) then
                        SkM_BGStats_AddKill();
                end


                if (bHonorKill) then
                        --SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._honorKill] = ifnil(SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._honorKill], 0) + 1;
                        SkM_UpdateEnemy_IncrHonorKill(sName, 1);

                        SkM_SetHonorFlags(sName);
                end

                -- asked by Fumus : record special count for solo kills for which player did full damage
                if (KillType == _SKM._playerFullKill) and (getn(SKM_Context.GroupList) == 1) then
                        SkM_Trace(FName, 3, "Group damage = 100% + solo : lone wolf kill");
                        --SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._loneWolfKill] = ifnil(SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._loneWolfKill], 0) + 1;
                        SkM_UpdateEnemy_IncrLoneWolfKill(sName, 1);
                end

--              local sGuildName = SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._guild];
--              if ((sGuildName ~= nil) and (sGuildName ~= "")) then
--                      SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName][KillType] = ifnil(SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName][KillType], 0) + 1;
--
--                      if (bHonorKill) then

--                              SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName][_SKM._honorKill] = ifnil(SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName][_SKM._honorKill], 0) + 1;
--                      end
--
--                      -- asked by Fumus : record special count for solo kills for which player did full damage
--                      if (KillType == _SKM._playerFullKill) and (getn(SKM_Context.GroupList) == 1) then
--                              SkM_Trace(FName, 3, "Group damage = 100% + solo : lone wolf kill");
--                              SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName][_SKM._loneWolfKill] = ifnil(SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName][_SKM._loneWolfKill], 0) + 1;
--                      end
--              end

                --local Enemy = SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName];

                local StoreInfo = { };

                StoreInfo[_SKM._type] = KillType;
                StoreInfo[_SKM._name] = sName;
                StoreInfo[_SKM._date] = sDate1;
                --StoreInfo[_sortdate] = sDate2;

                -- asked by Fumus : record special count for solo kills for which player did full damage
                if (KillType == _SKM._playerFullKill) and (getn(SKM_Context.GroupList) == 1) then
                        StoreInfo[_SKM._loneWolfKill] = true;
                end

                if (bHonorKill) then
                        StoreInfo[_SKM._honorKill] = true;
                end

                StoreInfo[_SKM._enemyType] = _SKM._enemyPlayer;

                if (SkM_GetOption("RecordPlayerKill")) then
                        local idx_gn;
                        if (SkM_AddMapData(StoreInfo)) then
                                idx_gn = table.getn(SKM_Data[_RealmName][_PlayerName].GlobalMapData);
                        end

                        SkM_AddRecentEnemyKill(sName, sDate1, idx_gn);
                end

                -- update target info if needed
                SkM_SetTargetInfo();
        end

        SKM_Context.EnemyCombat[sName] = nil;
end


function SkM_SetHonorFlags(sName)
        local FName = "SkM_SetHonorFlags";

        if (not sName) then
                SkM_Trace(FName, 1, "Name is nil");
                return;
        end
        if (not SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName]) then
                SkM_Trace(FName, 1, "Enemy "..snil(sName).." not known");
                return;
        end

        local sDate = SkM_GetDate();
        local sDateLast = SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._honorLastKill];

        if (not sDateLast) then
                SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._honorLastKill] = sDate;
                SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._honorCount] = 1;
        elseif (string.sub(sDate, 1, 10) ~= string.sub(sDateLast, 1, 10)) then
                SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._honorLastKill] = sDate;
                SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._honorCount] = 1;
        else
                SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._honorCount] = ifnil(SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._honorCount], 0) + 1;
        end

end


function SkM_GetHonorRemainingKills(sName)
        local FName = "SkM_GetHonorRemainingKills";

        if (not sName) then
                SkM_Trace(FName, 1, "Name is nil");
                return;
        end
        if (not SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName]) then
                SkM_Trace(FName, 1, "Enemy "..snil(sName).." not known");
                return;
        end

        local iCount;
        local sDate = SkM_GetDate();
        local sDateLast = SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._honorLastKill];

        if (not sDateLast) then
                iCount = SKM_HonorKillPerDay;
        elseif (string.sub(sDate, 1, 10) ~= string.sub(sDateLast, 1, 10)) then
                iCount = SKM_HonorKillPerDay;
        else
                iCount = SKM_HonorKillPerDay - ifnil(SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName][_SKM._honorCount], 0);
                if (iCount < 0) then
                        iCount = 0;
                end
        end
        return iCount;
end


function SkM_StoreDuelEnemyInfo(sUnit)
        local FName = "SkM_StoreDuelEnemyInfo";

        local sName = UnitName(sUnit);
        if (not sName) then
                return;
        end

        if (SKM_Context.DuelEnemy) and (SKM_Context.DuelEnemy[_SKM._name] == sName) then
                -- already stored
                return;
        end

        SKM_Context.DuelEnemy = { };
        SKM_Context.DuelEnemy[_SKM._name] = sName;
        SKM_Context.DuelEnemy[_SKM._race] = SkM_GetRaceIndex(UnitRace(sUnit));
        SKM_Context.DuelEnemy[_SKM._class] = SkM_GetClassIndex(UnitClass(sUnit));
        SKM_Context.DuelEnemy[_SKM._level] = UnitLevel(sUnit);
        SKM_Context.DuelEnemy[_SKM._guild] = GetGuildInfo(sUnit);

end


-- --------------------------------------------------------------------------------------
-- SkM_UpdateUnitData
-- --------------------------------------------------------------------------------------
-- Target unit has been updated. If it's an enemy, update that player information.
-- If it's a creep, store creature information.
-- --------------------------------------------------------------------------------------
function SkM_UpdateUnitData()
        local FName = "SkM_UpdateUnitData";

        local sName = SkM_UnitName(SKM_UNIT_TARGET);

        if (not sName) then
                -- clear target info
                SKM_Context.TargetInfo = nil;
        else
                if (SkM_UnitIsEnemyPlayer(SKM_UNIT_TARGET)) then
                        local sDate1, sDate2 = SkM_GetDate();
                        SkM_UpdateEnemyLastView(sName, sDate1, true, SKM_UNIT_TARGET);
                end

                if (not UnitIsPlayer(SKM_UNIT_TARGET)) then
                        SkM_StoreTargetInfo(_SKM._enemyCreature);
                elseif (SkM_UnitIsEnemyPlayer(SKM_UNIT_TARGET)) then
                        SkM_StoreTargetInfo(_SKM._enemyPlayer);
                else
                        SKM_Context.TargetInfo = nil;
                end

                if (SkM_GetOption("StoreDuels")) then
                        if (SkM_UnitIsDuelingPlayer(SKM_UNIT_TARGET)) then
                                SkM_StoreDuelEnemyInfo(SKM_UNIT_TARGET);
                        end
                end

        end


end


-- --------------------------------------------------------------------------------------
-- SkM_MouseOverUnitData
-- --------------------------------------------------------------------------------------
-- Get information on enemy player on mouse-over
-- --------------------------------------------------------------------------------------
function SkM_MouseOverUnitData()
        local FName = "SkM_MouseOverUnitData";

        local sName = SkM_UnitName(SKM_UNIT_MOUSEOVER);

        if (sName) then
                if (SkM_UnitIsEnemyPlayer(SKM_UNIT_MOUSEOVER)) then
                        local sDate1, sDate2 = SkM_GetDate();
                        SkM_UpdateEnemyLastView(sName, sDate1, true, SKM_UNIT_MOUSEOVER);

                        --local iLevel = UnitLevel(SKM_UNIT_MOUSEOVER);
                        --local sClass = UnitClass(SKM_UNIT_MOUSEOVER);
                        --local sRace = UnitRace(SKM_UNIT_MOUSEOVER);
                        --local sGuild = GetGuildInfo(SKM_UNIT_MOUSEOVER);
                        --SkM_Trace(FName, 1, "Name = "..snil(sName)..", Level = "..snil(iLevel)..", Class = "..snil(sClass)..", Race = "..snil(sRace)..", Guild = "..snil(sGuild));

                elseif (SkM_GetOption("StoreDuels")) and (SkM_UnitIsDuelingPlayer(SKM_UNIT_MOUSEOVER)) then
                        SkM_StoreDuelEnemyInfo(SKM_UNIT_MOUSEOVER);
                end

        end

end


-- --------------------------------------------------------------------------------------
-- SkM_ForgetPlayerHate
-- --------------------------------------------------------------------------------------
-- For all enemies in player hate list, recompute hate level from time elapsed since
-- last update.
-- "forget" enemy if hate reaches zero or if no update has been
-- received for a given time.
-- --------------------------------------------------------------------------------------
function SkM_ForgetPlayerHate()
        local FName = "SkM_ForgetPlayerHate";

        local curTime = GetTime();

        for sName, Enemy in SKM_Context.PlayerCombat do

                if (curTime - Enemy[_SKM._lastUpdate] > SKM_Config.ForgetAggressorTimer) then
                        -- no  update since given time interval, forget, whatever the hate level may be
                        SKM_Context.PlayerCombat[sName] = nil;
                else
                        -- don't forget, but reduce hate from time elapsed
                        local iTime = curTime - Enemy[_SKM._lastHateUpdate];

                        nHateReduction = Enemy[_SKM._hateLevel] * (iTime / 100) * SKM_Config.HateReductionCoeff;

                        SKM_Context.PlayerCombat[sName][_SKM._hateLevel] = Enemy[_SKM._hateLevel] - nHateReduction;
                        SKM_Context.PlayerCombat[sName][_SKM._lastHateUpdate] = curTime;

                        -- if hate is reduced to zero (or less), forget
                        if (SKM_Context.PlayerCombat[sName][_SKM._hateLevel] <= 0) then
                                SKM_Context.PlayerCombat[sName] = nil;
                        end
                end
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_LogDamage_OnSelf
-- --------------------------------------------------------------------------------------
-- Keep track of damage done on self and update "hate level" associated to the damage
-- dealer.
-- --------------------------------------------------------------------------------------
function SkM_LogDamage_OnSelf(iDamage, sName, EnemyType)
        local FName = "SkM_LogDamage_OnSelf";

        local curTime = GetTime();

        SkM_ForgetPlayerHate();

        if (not SKM_Context.PlayerCombat[sName]) then
                SKM_Context.PlayerCombat[sName] = { };
                SKM_Context.PlayerCombat[sName][_SKM._name] = sName;
                SKM_Context.PlayerCombat[sName][_SKM._enemyType] = EnemyType;
                SKM_Context.PlayerCombat[sName][_SKM._hateLevel] = 0;
                SKM_Context.PlayerCombat[sName][_SKM._damage] = 0;
                SKM_Context.PlayerCombat[sName][_SKM._lastHateUpdate] = curTime;
                SKM_Context.PlayerCombat[sName][_SKM._lastUpdate] = curTime;
        end

        -- store enemy type if we have it now and it was not previously stored
        if (not SKM_Context.PlayerCombat[sName][_SKM._enemyType]) and (EnemyType ~= nil) then
                SKM_Context.PlayerCombat[sName][_SKM._enemyType] = EnemyType;
        end

        SKM_Context.PlayerCombat[sName][_SKM._damage] = SKM_Context.PlayerCombat[sName][_SKM._damage] + iDamage;
        SKM_Context.PlayerCombat[sName][_SKM._hateLevel] = SKM_Context.PlayerCombat[sName][_SKM._hateLevel] + iDamage;

        SkM_Trace(FName, 3, "PlayerCombat updated for "..sName.." : damage = ".. SKM_Context.PlayerCombat[sName][_SKM._damage] ..", hate level = ".. SKM_Context.PlayerCombat[sName][_SKM._hateLevel]);
end


-- --------------------------------------------------------------------------------------
-- SkM_HateList_Dump
-- --------------------------------------------------------------------------------------
-- Debug function. Dump content of "PlayerCombat" (hate list).
-- --------------------------------------------------------------------------------------
function SkM_HateList_Dump()
        local FName = "SkM_HateList_Dump";

        local curTime = GetTime();


        SkM_ForgetPlayerHate();

        for idx, Elem in SKM_Context.PlayerCombat do
                local iTime = math.ceil(curTime - Elem[_SKM._lastUpdate]);
                local sReport = Elem[_SKM._name].." (last update : "..iTime.." s ago) -> damage = "..Elem[_SKM._damage]..", hate = "..Elem[_SKM._hateLevel];

                SkM_ChatMessageCol(sReport);
        end
end


-- --------------------------------------------------------------------------------------
-- SkM_SortPlayerCombat_ByHate
-- --------------------------------------------------------------------------------------
-- Sort "PlayerCombat" list by hate level.
-- --------------------------------------------------------------------------------------
function SkM_SortPlayerCombat_ByHate(e1, e2)
        if (not e2[_SKM._hateLevel]) then
                return true;
        end
        if (not e1[_SKM._hateLevel]) then
                return false;
        end
        return (e1[_SKM._hateLevel] > e2[_SKM._hateLevel]);
end


-- --------------------------------------------------------------------------------------
-- SkM_PlayerDeathResp
-- --------------------------------------------------------------------------------------
-- Find who is responsible for your death, if any. This is the most hated enemy player
-- or creature from "PlayerCombat" list.
-- Also reset hate list afterwards.
-- --------------------------------------------------------------------------------------
function SkM_PlayerDeathResp()
        local FName = "SkM_PlayerDeathResp";

        SkM_ForgetPlayerHate();

        local HateList = {};
        local EnemyName, EnemyType;
        local iTotal = 0;

        local idx, val;
        for idx, val in SKM_Context.PlayerCombat do
                local Elem = copytable(val);
                iTotal = iTotal + Elem[_SKM._hateLevel];
                table.insert(HateList, Elem);
        end

        table.sort(HateList, SkM_SortPlayerCombat_ByHate);

        if (table.getn(HateList) > 0) then
                EnemyName = HateList[1][_SKM._name];
                EnemyType = HateList[1][_SKM._enemyType];
        end

        for idx, val in HateList do
                val[_SKM._hatePercent] = math.floor(100 * val[_SKM._hateLevel] / iTotal);
        end

        SkM_Trace(FName, 2, "Most hated = "..snil(EnemyName)..", type = "..snil(EnemyType));
        
        SKM_Context.PlayerCombat = { };

        return EnemyName, EnemyType, HateList;
end


-- --------------------------------------------------------------------------------------
-- SkM_DumpEnemyCombat
-- --------------------------------------------------------------------------------------
-- Debug function. Dump content of "EnemyCombat" list.
-- --------------------------------------------------------------------------------------
function SkM_DumpEnemyCombat()
        local FName = "SkM_DumpEnemyCombat";
        local curTime = GetTime();

        for idx, val in SKM_Context.EnemyCombat do
                local iTime = math.ceil(curTime - val[_SKM._lastUpdate]);

                sReport = val[_SKM._name] .. " (last update : "..iTime.." s ago) -> total damage = ".. val[_SKM._totalDamage] .. ", group damage = " .. val[_SKM._groupDamage];
                SkM_ChatMessageCol(sReport);
        end
end


-- --------------------------------------------------------------------------------------
-- SkM_IsLeapYear
-- --------------------------------------------------------------------------------------
-- Is given year a leap year ?
-- --------------------------------------------------------------------------------------
function SkM_IsLeapYear(iYear)
        return intable(iYear, LeapYears);
end


-- --------------------------------------------------------------------------------------
-- SkM_DaysInYear
-- --------------------------------------------------------------------------------------
-- Return number of days in given year
-- --------------------------------------------------------------------------------------
function SkM_DaysInYear(iYear)
        if (SkM_IsLeapYear(iYear)) then
                return 366;
        else
                return 365;
        end
end


-- --------------------------------------------------------------------------------------
-- SkM_DaysInMonth
-- --------------------------------------------------------------------------------------
-- Return number of days in given month of a given year
-- --------------------------------------------------------------------------------------
function SkM_DaysInMonth(iMonth, iYear)
        local iDays = DaysInMonth[iMonth];
        if (iMonth == 2) and SkM_IsLeapYear(iYear) then
                iDays = 29;
        end
        return iDays;
end


-- --------------------------------------------------------------------------------------
-- SkM_TimeSinceEpoch
-- --------------------------------------------------------------------------------------
-- Return the number of seconds since "epoch", fixed at 01/01/2004 00:00:00.
-- (we do not need to handle time periods prior to WoW release, so this does not need
-- to be less than this time)
-- --------------------------------------------------------------------------------------
function SkM_TimeSinceEpoch(sDate)
        local FName = "SkM_TimeSinceEpoch";

        SkM_Trace(FName, 3, "Date = "..snil(sDate));

        local iDay, iMonth, iYear, iHour, iMin, iSec;

        iDay = tonumber( string.sub(sDate, 1, 2) );
        iMonth = tonumber( string.sub(sDate, 4, 5) );
        iYear = tonumber( string.sub(sDate, 7, 10) ) ;

        iHour = tonumber( string.sub(sDate, 12, 13) );
        iMin = tonumber( string.sub(sDate, 15, 16) );
        iSec = tonumber( string.sub(sDate, 18, 19) );

        if (iDay == nil) or (iMonth == nil) or (iYear == nil) or (iHour == nil) or (iMin == nil) or (iSec == nil) then
                return nil;
        end

        local iTime = 0;

        iIndYear = 2004;
        while (iIndYear < iYear) do
                iTime = iTime + SkM_DaysInYear(iIndYear); iIndYear = iIndYear + 1;
        end

        iIndMonth = 1;
        while (iIndMonth < iMonth) do
                iTime = iTime + SkM_DaysInMonth(iIndMonth, iYear); iIndMonth = iIndMonth + 1;
        end

        iTime = (iTime + (iDay - 1)) * 24;
        iTime = (iTime + (iHour - 1)) * 60;
        iTime = (iTime + (iMin - 1)) * 60;
        iTime = iTime + iSec;

        SkM_Trace(FName, 3, "Sec num since 01/01/2004 = "..snil(iTime));
        return iTime;
end


-- --------------------------------------------------------------------------------------
-- SkM_DiffDate
-- --------------------------------------------------------------------------------------
-- Return the difference in seconds between two dates in format DD/MM/YYYY HH:MI:SS
-- --------------------------------------------------------------------------------------
function SkM_DiffDate(sDate1, sDate2)
        local FName = "SkM_DiffDate";

        if (sDate1 == nil) or (sDate2 == nil) then
                return nil;
        end

        local iTime1, iTime2, iTime;

        iTime1 = SkM_TimeSinceEpoch(sDate1);
        iTime2 = SkM_TimeSinceEpoch(sDate2);

        if (iTime1 == nil) or (iTime2 == nil) then
                return nil;
        end

        iTime = iTime1 - iTime2;
        SkM_Trace(FName, 3, "Time difference = "..snil(iTime));
        return iTime;
end


-- --------------------------------------------------------------------------------------
-- SkM_SetTargetInfoText
-- --------------------------------------------------------------------------------------
-- Set a line of text of SKMapTargetInfoButton
-- --------------------------------------------------------------------------------------
function SkM_SetTargetInfoText(id, sLabel, sValue, sDetail)
        local FName = "SkM_SetTargetInfoText";

        local TextLabe, ValueLabel, DetailLabel;
        if (sLabel) then
                TextLabel = getglobal("SKMapTargetInfoButton"..id.."Label");
                if (not TextLabel) then return false; end
        end
        if (sValue) then
                ValueLabel = getglobal("SKMapTargetInfoButton"..id.."Value");
                if (not ValueLabel) then return false; end
        end
        if (sDetail) then
                DetailLabel = getglobal("SKMapTargetInfoButton"..id.."Detail");
                if (not DetailLabel) then return false; end
        end

        if (sLabel) then
                TextLabel:SetText(sLabel);
        end
        if (sValue) then
                ValueLabel:SetText(sValue);
        end
        if (sDetail) then
                DetailLabel:SetText(sDetail);
        end

        return true;
end


-- --------------------------------------------------------------------------------------
-- SkM_SetSmallTargetInfoText
-- --------------------------------------------------------------------------------------
-- Set a line of text of SKMapSmallTargetInfoButton
-- --------------------------------------------------------------------------------------
function SkM_SetSmallTargetInfoText(id, sLabel, sValue)
        local FName = "SkM_SetTargetInfoText";

        local TextLabe, ValueLabel;
        if (sLabel) then
                TextLabel = getglobal("SKMapSmallTargetInfoButton"..id.."Label");
                if (not TextLabel) then return false; end
        end
        if (sValue) then
                ValueLabel = getglobal("SKMapSmallTargetInfoButton"..id.."Value");
                if (not ValueLabel) then return false; end
        end

        if (sLabel) then
                TextLabel:SetText(sLabel);
        end
        if (sValue) then
                ValueLabel:SetText(sValue);
        end

        return true;
end


-- --------------------------------------------------------------------------------------
-- SkM_ShowTargetGuildInfo
-- --------------------------------------------------------------------------------------
-- Display or hide guild info mini frame
-- --------------------------------------------------------------------------------------
function SkM_ShowTargetGuildInfo(bShow, sGuildName, bGuildWar)

        if (not SkM_GetOption("ShowTargetGuildInfo")) then
                SKMapTargetGuildInfo:Hide();
        else
                if (bShow ~= true) or (sGuildName == nil) or (sGuildName == "") then
                        SKMapTargetGuildInfo:Hide();
                else
                        local id=1;
                        TextValue = getglobal("SKMapTargetGuildInfoButton"..id.."Value");

                        local sText;
                        if (bGuildWar) then
                                sText = SKM_Config.Col_PlayerWar;
                        else
                                sText = SKM_Config.Col_Label;
                        end
                        sText = sText..sGuildName;
                        TextValue:SetText(sText);

                        SKMapTargetGuildInfo:Show();
                end
        end
end


-- --------------------------------------------------------------------------------------
-- SkM_ShowTargetClassInfo
-- --------------------------------------------------------------------------------------
-- Display or hide class info mini frame
-- --------------------------------------------------------------------------------------
function SkM_ShowTargetClassInfo(bShow, sClass)

        if (not SkM_GetOption("ShowTargetClassInfo")) then
                SKMapTargetClassInfo:Hide();
        else
                if (bShow ~= true) or (sClass == nil) or (sClass == "") then
                        SKMapTargetClassInfo:Hide();
                else
                        local id=1;
                        TextValue = getglobal("SKMapTargetClassInfoButton"..id.."Value");

                        sText = SKM_Config.Col_Label;
                        sText = sText..sClass;
                        TextValue:SetText(sText);

                        SKMapTargetClassInfo:Show();
                end
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_SetTargetInfo
-- --------------------------------------------------------------------------------------
-- Fill and display or hidel TargetInfo frame(s) according to current target
-- --------------------------------------------------------------------------------------
function SkM_SetTargetInfo()
        local FName = "SkM_SetTargetInfo";

        local sName = SkM_UnitName(SKM_UNIT_TARGET);
--local sName = "Abaddon";
        if (not sName) then
                -- no target
                SkM_HideTargetInfoFrame();
                SkM_ShowTargetGuildInfo(false, nil, nil);
                SkM_ShowTargetClassInfo(false, nil);
                SkM_SetWarDragon(false, false);
                return false;
        end

        local sClass = UnitClass(SKM_UNIT_TARGET);

        if (SkM_UnitIsEnemyPlayer(SKM_UNIT_TARGET)) then
--if (true) then

                local bWar = false;
                local bGuildWar = false;

                local Enemy = SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName];
                if (not Enemy) then
                        SkM_HideTargetInfoFrame();
                        SkM_SetWarDragon(false, false);
                        return false;
                end

                -- 09/04/2005 17:23:14 PIng Add guild support
                local sGuildName =  Enemy[_SKM._guild];
                if (sGuildName == "") then sGuildName = nil; end
                local Guild = SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName];
                -- 09/04/2005 17:24:33 End of modification

                if (Enemy[_SKM._atWar]) then
                        bWar = true;
                end
                if (Guild) and (Guild[_SKM._atWar]) then
                        bGuildWar = true;
                end

                SkM_ShowTargetGuildInfo(true, sGuildName, bGuildWar);
                SkM_ShowTargetClassInfo(true, sClass);

                local iKill = ifnil(Enemy[_SKM._playerKill], 0);
                local iAssistKill = ifnil(Enemy[_SKM._playerAssistKill], 0);
                local iFullKill = ifnil(Enemy[_SKM._playerFullKill], 0);
                local iTotalKill = iKill + iAssistKill + iFullKill;
                local iHonorKill = ifnil(Enemy[_SKM._honorKill], 0);

                local iRemaining = SkM_GetHonorRemainingKills(sName);

                -- 09/04/2005 17:25:03 PIng Add guild support
                local gKill = "";
                local gAssistKill = "";
                local gFullKill = "";
                local gTotalKill = "";
                local gDeath = "";
                local gMet = "";
                if (sGuildName ~= nil) then
                        gKill = ifnil(Guild[_SKM._playerKill], 0);
                        gAssistKill = ifnil(Guild[_SKM._playerAssistKill], 0);
                        gFullKill = ifnil(Guild[_SKM._playerFullKill], 0);
                        gTotalKill = gKill + gAssistKill + gFullKill;
                        gDeath = ifnil(Guild[_SKM._enemyKillPlayer], 0);
                        gMet = ifnil(Guild[_SKM._meetCount], 0);
                end
                -- 09/04/2005 17:25:12 End of modification

                local iDeath = ifnil(Enemy[_SKM._enemyKillPlayer], 0);
                local iMet = ifnil(Enemy[_SKM._meetCount], 0);

                SkM_Trace(FName, 3, "Kill = "..iKill..", AssistKill = "..iAssistKill..", FullKill = "..iFullKill..", Met = "..iMet);
                -- 09/04/2005 17:25:03 PIng Add guild support
                if (sGuildName ~= nil) then
                        SkM_Trace(FName, 3, "Guild Kill = "..gKill..", Guild AssistKill = "..gAssistKill..", Guild FullKill = "..gFullKill..", Guild Met = "..gMet);
                end
                -- 09/04/2005 17:25:12 End of modification

                local sKill = SKM_Config.Col_PlayerTotalKill .. iTotalKill;
                local sKillDetail;
                -- only display detail kill count if there's at least one kill
--              if (iTotalKill == 0) then
--                      sKillDetail = "";
--              else
--                      sKillDetail = SKM_Config.Col_Label .. "( " .. SKM_Config.Col_PlayerFullKill .. iFullKill .. SKM_Config.Col_Label .. " + " .. SKM_Config.Col_PlayerKill .. iKill .. SKM_Config.Col_Label .. " + " .. SKM_Config.Col_PlayerAssistKill .. iAssistKill .. SKM_Config.Col_Label .. " )";
--              end

                sKillDetail = SKM_UI_STRINGS.Small_Target_Honor..SKM_Config.Col_HonorKill..iHonorKill.."  ";
                --local FrameColor;
                local LabelColor;
                if (iRemaining == 0) then

                        sKillDetail = sKillDetail..SKM_Config.Col_Label.."( "..SKM_Config.Col_Honorless..SKM_UI_STRINGS.Small_Target_NoHonor..SKM_Config.Col_Label.." )";
                        --FrameColor = { r = 0.0, g = 0.0, b = 0.0 };
                        LabelColor = SKM_Config.Col_LabelTitle;
                else
                        sKillDetail = sKillDetail..SKM_Config.Col_Label.."( "..SKM_Config.Col_HonorKill..iRemaining..SKM_Config.Col_Label.." )";
                        --FrameColor = { r = 0.3, g = 1.0, b = 1.0 };
                        LabelColor = SKM_Config.Col_HonorKill;
                end

                local sDeath = SKM_Config.Col_PlayerDeath .. iDeath;
                local sMet = SKM_Config.Col_PlayerMet .. iMet;

                local sWar1 = "";
                local sWar2 = "";

                if (Enemy[_SKM._atWar] == true) then
                        sWar1 = SKM_Config.Col_PlayerWar.. SKM_UI_STRINGS.Small_Target_War;
                        sWar2 = "";
                        if (Enemy[_SKM._warDate]) then
                                local sDisplayDate = string.sub(Enemy[_SKM._warDate], 1, 10);
                                sWar2 = SKM_Config.Col_PlayerWar .. SKM_UI_STRINGS.Since .. sDisplayDate;
                        end
                end

                local bRes1, bRes2, bRes3, bRes4;

                if (SkM_GetOption("SmallTargetInfo")) then
                        bRes1 = SkM_SetSmallTargetInfoText(1, LabelColor..SKM_UI_STRINGS.Small_Target_Info_Kill, sKill);
                        bRes2 = SkM_SetSmallTargetInfoText(2, SKM_UI_STRINGS.Small_Target_Info_Death, sDeath);
                        bRes3 = SkM_SetSmallTargetInfoText(3, SKM_UI_STRINGS.Small_Target_Info_Met, sMet);
                        bRes4 = SkM_SetSmallTargetInfoText(4, sWar1);
                else
                        bRes1 = SkM_SetTargetInfoText(1, SKM_UI_STRINGS.Small_Target_Info_Kill, sKill, sKillDetail);
                        bRes2 = SkM_SetTargetInfoText(2, SKM_UI_STRINGS.Small_Target_Info_Death, sDeath, "");
                        bRes3 = SkM_SetTargetInfoText(3, SKM_UI_STRINGS.Small_Target_Info_Met, sMet, "");
                        bRes4 = SkM_SetTargetInfoText(4, sWar1, "", sWar2);
                end

                SkM_ShowTargetFrameWarButtons(Enemy[_SKM._atWar]);

                SkM_SetWarDragon(bWar, bGuildWar);

                if (bRes1 and bRes2 and bRes3 and bRes4) then
                        SkM_ShowTargetInfoFrame();
                        return true;
                else
                        SkM_HideTargetInfoFrame();
                        return false;
                end

        else
                SkM_HideTargetInfoFrame();
                SkM_SetWarDragon(false, false);

                if (UnitIsPlayer(SKM_UNIT_TARGET)) then
                        local sGuildName = GetGuildInfo(SKM_UNIT_TARGET);
                        SkM_ShowTargetGuildInfo(true, sGuildName, false);
                        SkM_ShowTargetClassInfo(true, sClass);
                else
                        SkM_ShowTargetGuildInfo(false, nil, nil);
                        SkM_ShowTargetClassInfo(false, nil);
                end



                -- if we switch from a player dragon to an elite mob, we must keep the "elite" frame !
                --if (UnitIsPlusMob(SKM_UNIT_TARGET)) then
                local sUnitClassification = UnitClassification(SKM_UNIT_TARGET);
                if     (UnitIsPlusMob(SKM_UNIT_TARGET))
                  or ( (sUnitClassification ~= nil) and (sUnitClassification ~= "") and (sUnitClassification ~= "normal") )
                then
                        TargetFrameTexture:SetTexture("Interface\\TargetingFrame\\UI-TargetingFrame-Elite");
                end

                return false;
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_SetWarDragon
-- --------------------------------------------------------------------------------------
-- Display or hide the red "elite dragon" around target portrait.
-- If this enemy or his guild is set "at war", then the dragon is shown
-- Otherwise, color is reset and the "elite dragon" is hidden
-- --------------------------------------------------------------------------------------
function SkM_SetWarDragon(bWar, bGuildWar)
        local FName = "SkM_SetWarDragon";

        if (bWar) or (bGuildWar) then
                --Set Red Dragon Overlay on Texture to Target
                TargetFrameTexture:SetVertexColor(1.0, 200.0, 1.0, TargetFrameTexture:GetAlpha());
                TargetFrameTexture:SetTexture("Interface\\TargetingFrame\\UI-TargetingFrame-Elite");

        else
                -- not at war with target
                TargetFrameTexture:SetVertexColor(1.0, 1.0, 1.0, TargetFrameTexture:GetAlpha());
                TargetFrameTexture:SetTexture("Interface\\TargetingFrame\\UI-TargetingFrame");
        end
end


-- --------------------------------------------------------------------------------------
-- SkM_EnemyWar
-- --------------------------------------------------------------------------------------
-- Declare war to an enemy player, or call a truce with this player
-- --------------------------------------------------------------------------------------
function SkM_EnemyWar(bWar, sEnemyName)
        local FName = "SkM_EnemyWar";

        SkM_Trace(FName, 3, "War button pressed");

        local sName;
        if (sEnemyName) then
                sName = sEnemyName;
        else
                local sTargetName = SkM_UnitName(SKM_UNIT_TARGET);
                sName = sTargetName;
        end

        if (not sName) then
                SkM_Trace(FName, 1, "No enemy specified");
                return;
        end

        local Enemy = SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName];
        if (not Enemy) then
                SkM_Trace(FName, 1, "Enemy not found : "..snil(sName));
                return;
        end

        local sGuildName = Enemy[_SKM._guild];
        local Guild;
        local bGuildWar = false;
        if (sGuildName) and (sGuildName ~= "") then
                Guild = SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName];
        end
        if (Guild) and (Guild[_SKM._atWar]) then
                bGuildWar = true;
        end

        local sDate = SkM_GetDate();

        SkM_UpdateEnemy_SetWar(sName, bWar, sDate);

        SkM_ShowTargetFrameWarButtons(Enemy[_SKM._atWar]);

        -- 09/04/2005 16:18:34 PIng: Update war info on target frame when status is changed
        local sWar1 = "";
        local sWar2 = "";

        if (Enemy[_SKM._atWar] == true) then
                sWar1 = SKM_Config.Col_PlayerWar.. SKM_UI_STRINGS.Small_Target_War;
                sWar2 = "";
                if (Enemy[_SKM._warDate]) then
                        local sDisplayDate = string.sub(Enemy[_SKM._warDate], 1, 10);
                        sWar2 = SKM_Config.Col_PlayerWar .. SKM_UI_STRINGS.Since .. sDisplayDate;
                end
        end

        local bRes4;

        if (SkM_GetOption("SmallTargetInfo")) then
                bRes4 = SkM_SetSmallTargetInfoText(4, sWar1);
        else
                bRes4 = SkM_SetTargetInfoText(4, sWar1, "", sWar2);
        end
        -- 09/04/2005 16:18:31 End of modification

        if (sName == sTargetName) then
                SkM_SetWarDragon(bWar, bGuildWar);
        end
end


-- --------------------------------------------------------------------------------------
-- SkM_UnknownEnemyWar
-- --------------------------------------------------------------------------------------
-- Declare war to an enemy player that can is potentially not known yet
-- --------------------------------------------------------------------------------------
function SkM_UnknownEnemyWar(sName, bWar, bDisplay)
        local FName = "SkM_UnknownEnemyWar";


        SkM_Trace(FName, 3, "Enemy player = "..snil(sName));

        local sDate = SkM_GetDate();

        if (sName) then
                local Enemy = SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName];
                if (Enemy) then
                        local bWarPrev = Enemy[_SKM._atWar];
                        SkM_UpdateEnemy_SetWar(sName, bWar, sDate);
                        if (bDisplay) then
                                if (bWar) and (not bWarPrev) then
                                        SkM_ChatMessageCol("Now at WAR with "..sName);
                                elseif (not bWar) and (bWarPrev) then
                                        SkM_ChatMessageCol("No longer at WAR with "..sName);
                                end
                        end

                        -- update target info if needed
                        SkM_SetTargetInfo();

                        -- update interface enemy list if needed
                        local bVisible, sFrame = SKMap_IsUIVisible();

                        if (bVisible) and (sFrame == "SKMap_ListFrame") and (SKM_List_ActiveList == _SKM._players) then
                                SKMap_ListFrame_UpdateList();
                                if (SKM_List_SelectedPlayer) then
                                        SKMap_ListFrame_ShowWarButton(bWar);
                                end
                        end
                        if (SKM_List_ActiveList == _SKM._players) and (SKM_List_SelectedPlayer) then
                                SKMap_ListFrame_SelectElement(SKM_List_SelectedPlayer);
                        end

                else
                        -- unknown enemy
                        if (bWar) then
                                SKM_Data[_RealmName][_PlayerName].UnknownEnemy[sName] = 1;
                                if (bDisplay) then
                                        SkM_ChatMessageCol("Now at WAR with "..sName.." (not yet known)");
                                end
                        else
                                if (SKM_Data[_RealmName][_PlayerName].UnknownEnemy[sName]) then
                                        SKM_Data[_RealmName][_PlayerName].UnknownEnemy[sName] = nil;
                                        if (bDisplay) then
                                                SkM_ChatMessageCol("No longer at WAR with "..sName.." (not yet known)");
                                        end
                                end
                        end

                end

        end
end


function SkM_ShowUnknownEnemyWar()
        local sMsg = "";
        local iCount = 0;

        local idx, val;
        for idx, val in SKM_Data[_RealmName][_PlayerName].UnknownEnemy do
                sMsg = sMsg..idx.."  ";
                iCount = iCount + 1;
        end

        if (iCount == 0) then
                SkM_ChatMessageCol("Unknown players WAR list is empty");
        else
                SkM_ChatMessageCol("Unknown players WAR list ("..iCount..") :");
                SkM_ChatMessageCol(sMsg);
        end

end


function SkM_ClearUnknownEnemyWar()
        SKM_Data[_RealmName][_PlayerName].UnknownEnemy = { };
        SkM_ChatMessageCol("Unknown players WAR list cleared");
end


function SkM_FindSharedEnemyWar(sName)
        local FName = "SkM_FindSharedEnemyWar";

        -- parse all characters of the same realm
        local idx_char, val_char;
        for idx_char, val_char in SKM_Data[_RealmName] do
                if (SKM_Data[_RealmName][idx_char].PlayerName == idx_char) then
                        local Enemy = SKM_Data[_RealmName][idx_char].EnemyHistory[sName];

                        -- if enemy known to this player, and set "at war", return him
                        if (Enemy) and (Enemy[_SKM._atWar]) then
                                return Enemy, idx_char;
                        end
                end
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_MouseOverUnit
-- --------------------------------------------------------------------------------------
-- Handle "mouseover unit" event.
-- If that unit is an enemy player that is "at war", or whose guild is "at war" :
--   Display floating message (if configured)
--   Play warning sound (if configured)
-- If unit is an unknown enemy and we have no current target, target him to store
-- information, then clear target again.
-- --------------------------------------------------------------------------------------
function SkM_MouseOverUnit()
        local FName = "SkM_MouseOverUnit";

        local sDate = SkM_GetDate();

        -- store information related to mouse-overed unit
        SkM_MouseOverUnitData();

        if (SkM_UnitIsEnemyPlayer(SKM_UNIT_MOUSEOVER)) then
--      if (true) then
                -- Grab the name of who we've moused-over
                local sName = SkM_UnitName(SKM_UNIT_MOUSEOVER);
                --local sName = "Ledieu";
                if (not sName) then
                        return;
                end

                local sGuildName = GetGuildInfo(SKM_UNIT_MOUSEOVER);

                local bWar = false;
                local bGuildWar = false;
                local bSharedWar = false;

                local Enemy = SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName];
                local SharedEnemy, SharedChar;
                local Guild;

                if (Enemy) and (Enemy[_SKM._atWar]) then
                        bWar = true;
                end

                if (sGuildName ~= nil) and (sGuildName ~= "") then
                        Guild = SKM_Data[_RealmName][_PlayerName].GuildHistory[sGuildName];
                        if (Guild ~= nil) and (Guild[_SKM._atWar]) then
                                bGuildWar = true;
                        end
                end

                if not (bWar or bGuildWar) then
                        if (SkM_GetOption("SharedWarMode")) then
                                SharedEnemy, SharedChar = SkM_FindSharedEnemyWar(sName);
                                if (SharedEnemy) and (SharedEnemy[_SKM._atWar]) then
                                        bSharedWar = true;
                                end
                        end
                end

                if (bWar) or (bGuildWar) or (bSharedWar) then
                        local bFilter = false;

                        if (SkM_GetOption("WarEnableFilter")) then
                                -- check against global "last warning date"
                                if (SKM_Context.LastWarWarning) then
                                        local iDiffTime = SkM_DiffDate(sDate, SKM_Context.LastWarWarning);
                                        if (iDiffTime ~= nil) and (iDiffTime <= SkM_GetOption("WarFilterDelay")) then
                                                -- Filter out to reduce spam
                                                bFilter = true;
                                        end
                                end

                        end

                        -- check against last warning for given enemy
                        -- (independently of filter option)
                        if (SkM_GetRecentWarWarning(sName)) then
                                -- Filter out to reduce spam
                                bFilter = true;
                        end

                        if (not bFilter) then
                                SKM_Context.LastWarWarning = sDate;
                                SkM_AddRecentWarWarning(sName, sDate);

                                SkM_Trace(FName, 3, "Last War Warning = "..snil(SKM_Context.LastWarWarning));

                                if (SkM_GetOption("WarSoundWarning")) then
                                        --PlaySound("AuctionWindowClose");
                                        PlaySoundFile(SKM_Config.WarSoundFile);
                                end

                                if (SkM_GetOption("WarFloatingMessage")) then
                                        local sWarMessage = "";
                                        if (bWar) and (bGuildWar) then
                                                sWarMessage = sName.." / "..sGuildName;
                                        elseif (bWar) then
                                                sWarMessage = sName;
                                        elseif (bGuildWar) then
                                                sWarMessage = sGuildName;
                                        elseif (bSharedWar) then
                                                sWarMessage = sName..SKM_Config.Col_SharedWar.." ["..SharedChar.."]".."|r";
                                        end
                                        UIErrorsFrame:AddMessage(SKM_UI_STRINGS.War_Floating_Message..sWarMessage, 1.0, 0.0, 0.0, 1.0, UIERRORS_HOLD_TIME);
                                end

                                if (SkM_GetOption("WarChatMessage")) then
                                        local sWarMessage = "";
                                        if (bWar) and (bGuildWar) then
                                                sWarMessage = sName.." / "..sGuildName;
                                        elseif (bWar) then
                                                sWarMessage = sName;
                                        elseif (bGuildWar) then
                                                sWarMessage = sGuildName;
                                        elseif (bSharedWar) then
                                                sWarMessage = sName..SKM_Config.Col_SharedWar.." ["..SharedChar.."]".."|r";
                                        end
                                        if (SkM_GetOption("WarShowNote")) then
                                                if (not bSharedWar) then
                                                        if (Enemy) and (Enemy[_SKM._playerNote] ~= nil) and (Enemy[_SKM._playerNote] ~= "") then
                                                                sWarMessage = sWarMessage .. SKM_Config.Col_Label.." - " .. SKM_Config.Col_PlayerNote.. Enemy[_SKM._playerNote];
                                                        end
                                                else
                                                        if (SharedEnemy) and (SharedEnemy[_SKM._playerNote]) and (SharedEnemy[_SKM._playerNote] ~= "") then
                                                                sWarMessage = sWarMessage .. SKM_Config.Col_Label.." - " .. SKM_Config.Col_PlayerNote .. SharedEnemy[_SKM._playerNote];
                                                        end
                                                end
                                        end
                                        SkM_PrintMessage(SKM_UI_STRINGS.War_Floating_Message..sWarMessage, 1.0, 0.0, 0.0);
                                end

                        end

                        if (SkM_GetOption("WarAutoTarget") and (not UnitName(SKM_UNIT_TARGET))) then
                                TargetByName(sName);
                        end
                end

                -- Add information to tooltip (if option enabled)
                if (SkM_GetOption("TooltipTargetInfo")) then
                        SKMap_TooltipEnemyInfo(sName);
                end

                -- Add player note to tooltip (if option enabled)
                if (SkM_GetOption("TooltipPlayerNote")) then
                        SKMap_TooltipEnemyNote(sName);
                end

        end

        --if (SkM_GetOption("TooltipTargetInfo")) then
        --      SKMap_TooltipEnemyInfo("Ledieu");
        --end
        --if (SkM_GetOption("TooltipPlayerNote")) then
        --      SKMap_TooltipEnemyNote("Ledieu");
        --end


end


-- --------------------------------------------------------------------------------------
-- SkM_ShowTargetInfoFrame
-- --------------------------------------------------------------------------------------
-- Show one of the SKMap TargetInfo frames (small or normal), and hide the other.
-- --------------------------------------------------------------------------------------
function SkM_ShowTargetInfoFrame()
        local FName = "SkM_ShowTargetInfoFrame";

        local bShow = SkM_GetOption("ShowTargetInfo");

        if (SkM_GetOption("SmallTargetInfo")) then
                SkM_DisplayTargetInfoFrames(false, bShow);
        else
                SkM_DisplayTargetInfoFrames(bShow, false);
        end
end


-- --------------------------------------------------------------------------------------
-- SkM_HideTargetInfoFrame
-- --------------------------------------------------------------------------------------
-- Hide both SKMap TargetInfo frames.
-- --------------------------------------------------------------------------------------
function SkM_HideTargetInfoFrame()
        local FName = "SkM_HideTargetInfoFrame";

        SkM_DisplayTargetInfoFrames(false, false);
end


-- --------------------------------------------------------------------------------------
-- SkM_ShowTargetFrameWarButtons
-- --------------------------------------------------------------------------------------
-- Show/hide war and truce button on the currently active SKMap TargetInfo frame,
-- according to war status.
-- --------------------------------------------------------------------------------------
function SkM_ShowTargetFrameWarButtons(bWar)
        local FName = "SkM_ShowTargetFrameWarButtons";

        if (SkM_GetOption("SmallTargetInfo")) then
                if (bWar == true) then
                        SKMapSmallPvPTruceButton:Show();
                        SKMapSmallPvPWarButton:Hide();
                else

                        SKMapSmallPvPTruceButton:Hide();
                        SKMapSmallPvPWarButton:Show();
                end

        else
                if (bWar == true) then
                        SKMapPvPTruceButton:Show();
                        SKMapPvPWarButton:Hide();
                else
                        SKMapPvPTruceButton:Hide();
                        SKMapPvPWarButton:Show();
                end
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_DisplayTargetInfoFrames
-- --------------------------------------------------------------------------------------
-- Show or Hide the large or small target info frame
-- --------------------------------------------------------------------------------------
function SkM_DisplayTargetInfoFrames(bLarge, bSmall)
        local FName = "SkM_DisplayTargetInfoFrames";

        if (bLarge == true) then
                SKMapTargetInfoFrame:Show();
        elseif (bLarge == false) then
                SKMapTargetInfoFrame:Hide();
        end

        if (bSmall == true) then
                SKMapSmallTargetInfoFrame:Show();
        elseif (bSmall == false) then
                SKMapSmallTargetInfoFrame:Hide();
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_TargetInfoResize
-- --------------------------------------------------------------------------------------
-- Switch from one size of the SKMap TargetInfo frame to the other
-- --------------------------------------------------------------------------------------
function SkM_TargetInfoResize()
        local FName = "SkM_TargetInfoResize";

        SkM_SetOption("SmallTargetInfo", not (SkM_GetOption("SmallTargetInfo")) );

        SkM_SetTargetInfo();
end


-- --------------------------------------------------------------------------------------
-- SkM_StoreTargetInfo
-- --------------------------------------------------------------------------------------
-- Store currently targetted creature information.
-- Remember if it's tapped and by who.
-- --------------------------------------------------------------------------------------
function SkM_StoreTargetInfo(sTargetType)
        local sName = SkM_UnitName(SKM_UNIT_TARGET);

        if (not sName) then
                SKM_Context.TargetInfo = nil;
        else
                if (not SKM_Context.TargetInfo) or (SKM_Context.TargetInfo[_SKM._name] ~= sName) then
                        SKM_Context.TargetInfo = { };
                        SKM_Context.TargetInfo[_SKM._name] = sName;
                end

                SKM_Context.TargetInfo[_SKM._type] = sTargetType;

                if (sTargetType == _SKM._enemyCreature) then

                        if (UnitIsTapped(SKM_UNIT_TARGET)) then
                                if (UnitIsTappedByPlayer(SKM_UNIT_TARGET)) then
                                        SKM_Context.TargetInfo[_SKM._owner] = _SKM._player;
                                else
                                        SKM_Context.TargetInfo[_SKM._owner] = _SKM._other;
                                end
                        end

                end

        end
end


-- --------------------------------------------------------------------------------------
-- SkM_TargetHealthUpdated
-- --------------------------------------------------------------------------------------
-- Target unit health has changed.
-- If this is a creature and if it just died, record creature kill if need be.
-- If this is a creature and it's still alive, update information.
-- --------------------------------------------------------------------------------------
function SkM_TargetHealthUpdated()
        local FName = "SkM_TargetHealthUpdated";

        local sName = SkM_UnitName(SKM_UNIT_TARGET);
        if (not sName) then
                return;
        end

        if (UnitHealth(SKM_UNIT_TARGET) == 0 or UnitIsCorpse(SKM_UNIT_TARGET) or UnitIsDeadOrGhost(SKM_UNIT_TARGET)) then

                -- we use this event to track pve kills for unit currently targetted
                -- (player kills are tracked by damage done)

                if (not UnitIsPlayer(SKM_UNIT_TARGET)) then

                        -- award kill to player if : we have previously stored the creature information
                        -- in context, and if it was tapped by player
                        -- in all cases, clear target info.
                        if (SKM_Context.TargetInfo) and (SKM_Context.TargetInfo[_SKM._name] == sName) then

                                if (SKM_Context.TargetInfo[_SKM._owner] == _SKM._player) then

                                        SkM_Trace(FName, 3, "Target creature (".. sName ..") kill detected - by player");

                                        local iLevel = UnitLevel(SKM_UNIT_TARGET);
                                        local bElite = UnitIsPlusMob(SKM_UNIT_TARGET);
                                        local sClassification = UnitClassification(SKM_UNIT_TARGET);

                                        SkM_RecordCreatureKill_Target(sName, iLevel, sClassification);
                                else
                                        SkM_Trace(FName, 3, "Target creature (".. sName ..") kill detected, but by other");
                                end
                        end

                elseif (SkM_UnitIsEnemyPlayer(SKM_UNIT_TARGET)) then
                        SkM_Trace(FName, 2, "Enemy player death detected (from target) : "..snil(sName));
                        SkM_PvpEnemyDeath(sName);
                end

                SKM_Context.TargetInfo = nil;

        else
                -- unit is alive
                if (not UnitIsPlayer(SKM_UNIT_TARGET)) then
                        -- not a player, update owner information
                        SkM_StoreTargetInfo(_SKM._enemyCreature);
                end
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_PlayerLevelUp
-- --------------------------------------------------------------------------------------
-- Handle "player level up" event : record event, and store new level.
-- --------------------------------------------------------------------------------------
function SkM_PlayerLevelUp()
        local FName = "SkM_PlayerLevelUp";

        local StoreInfo = { };

        StoreInfo[_SKM._type] = _SKM._levelUp;

        local sDate1, sDate2 = SkM_GetDate();
        StoreInfo[_SKM._date] = sDate1;
        --StoreInfo[_sortdate] = sDate2;

        StoreInfo[_SKM._name] = _PlayerName;

        local iNewLevel;
        if (SKM_Context.PlayerLevel) then
                iNewLevel = SKM_Context.PlayerLevel + 1;
        else
                iNewLevel = UnitLevel(SKM_UNIT_PLAYER) + 1;
        end
        StoreInfo[_SKM._level] = iNewLevel;
        SKM_Context.PlayerLevel = iNewLevel;

        if (not SkM_AddMapData(StoreInfo)) then
                return;
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_CountGuildMembers
-- --------------------------------------------------------------------------------------
-- Count number of members known for a given guild.
-- --------------------------------------------------------------------------------------
function SkM_CountGuildMembers(sGuild, sRealm, sPlayer)
        local FName = "SkM_CountGuildMembers";

        local sRealmName = sRealm;
        local sPlayerName = sPlayer;

        SkM_Trace(FName, 4, "Get member count for guild : "..snil(sGuild));

        if (sRealmName == nil) then
                sRealmName = _RealmName;
        end

        if (sPlayerName == nil) then
                sPlayerName = _PlayerName;
        end

        if (SKM_Data[sRealmName] == nil) then
                return nil;
        end
        if (SKM_Data[sRealmName][sPlayerName] == nil) then
                return nil;
        end

        local iCount = 0;

        for idx, val in SKM_Data[sRealmName][sPlayerName].EnemyHistory do
                local sName = val[_SKM._name];
                if (sName) then
                        if (val[_SKM._guild] == sGuild) then
                                iCount = iCount + 1;
                        end
                end
        end

        return iCount;
end


-- --------------------------------------------------------------------------------------
-- SkM_ComputeStatistics
-- --------------------------------------------------------------------------------------
-- Compute various statistics that will be used in the report frame.
-- --------------------------------------------------------------------------------------
function SkM_ComputeStatistics()
        local FName = "SkM_ComputeStatistics";

        SKM_Context.Statistics = { };

        SKM_Context.Statistics.Globals = { };
        SKM_Context.Statistics.Race = { };
        SKM_Context.Statistics.Class = { };
        SKM_Context.Statistics.Zone = { };
        SKM_Context.Statistics.Date = { };
        SKM_Context.Statistics.Enemy = { };
        SKM_Context.Statistics.Guild = { };

        SKM_Context.Statistics.BGZone = { };
        SKM_Context.Statistics.BGDate = { };
        SKM_Context.Statistics.BGDateZone = { };

        local iDeathForLevel = 0;
        local iKillForLevel = 0;
        local iTotalLevelDeath = 0;
        local iTotalLevelKill = 0;

        -- compute : global, by race, by class, by enemy
        -- statistics from EnemyHistory map
        for idx, val in SKM_Data[_RealmName][_PlayerName].EnemyHistory do

                SKM_Context.Statistics.Globals.EnemyPlayers = ifnil(SKM_Context.Statistics.Globals.EnemyPlayers, 0) + 1;

                local iDeath = ifnil(val[_SKM._enemyKillPlayer], 0);
                --local iKill = ifnil(val[_SKM._playerAssistKill], 0) + ifnil(val[_SKM._playerKill], 0) + ifnil(val[_SKM._playerFullKill], 0);
                local iKill = ifnil(val[_SKM._playerKill], 0) + ifnil(val[_SKM._playerFullKill], 0);
                if (SkM_GetOption("AssistKillStat")) then
                        iKill = iKill + ifnil(val[_SKM._playerAssistKill], 0)
                end


                SKM_Context.Statistics.Globals.Death = ifnil(SKM_Context.Statistics.Globals.Death, 0) + iDeath;
                SKM_Context.Statistics.Globals.Kill = ifnil(SKM_Context.Statistics.Globals.Kill, 0) + iKill;

                -- for computing averages
                if (val[_SKM._level]) and (val[_SKM._level] ~= -1) then
                        iDeathForLevel = iDeathForLevel + iDeath;
                        iKillForLevel = iKillForLevel + iKill;
                        iTotalLevelDeath = iTotalLevelDeath + ( val[_SKM._level] * iDeath );
                        iTotalLevelKill = iTotalLevelKill + ( val[_SKM._level] * iKill );
                end

                local sRace = SkM_GetRaceText(val[_SKM._race]);
                if (sRace) then
                        if (SKM_Context.Statistics.Race[sRace] == nil) then
                                SKM_Context.Statistics.Race[sRace] = { };
                        end
                        SKM_Context.Statistics.Race[sRace].Death = ifnil(SKM_Context.Statistics.Race[sRace].Death, 0) + iDeath;
                        SKM_Context.Statistics.Race[sRace].Kill = ifnil(SKM_Context.Statistics.Race[sRace].Kill, 0) + iKill;
                end

                local sClass = SkM_GetClassText(val[_SKM._class]);
                if (sClass) then
                        if (SKM_Context.Statistics.Class[sClass] == nil) then
                                SKM_Context.Statistics.Class[sClass] = { };
                        end
                        SKM_Context.Statistics.Class[sClass].Death = ifnil(SKM_Context.Statistics.Class[sClass].Death, 0) + iDeath;
                        SKM_Context.Statistics.Class[sClass].Kill = ifnil(SKM_Context.Statistics.Class[sClass].Kill, 0) + iKill;
                end

                sEnemyName = val[_SKM._name];
                if (sEnemyName) and ( (iDeath > 0) or (iKill > 0) ) then
                        SKM_Context.Statistics.Enemy[sEnemyName] = { };
                        SKM_Context.Statistics.Enemy[sEnemyName].Death = iDeath;
                        SKM_Context.Statistics.Enemy[sEnemyName].Kill = iKill;
                end

        end


        -- compute : global, by race, by class, by enemy
        -- statistics from EnemyHistory map
        for idx, val in SKM_Data[_RealmName][_PlayerName].GuildHistory do
        --for idx, val in SkM_GetPlayerData("GuildHistory") do

                SKM_Context.Statistics.Globals.EnemyGuilds = ifnil(SKM_Context.Statistics.Globals.EnemyGuilds, 0) + 1;

                local iDeath = ifnil(val[_SKM._enemyKillPlayer], 0);
                local iKill = ifnil(val[_SKM._playerAssistKill], 0) + ifnil(val[_SKM._playerKill], 0) + ifnil(val[_SKM._playerFullKill], 0);

                local sGuildName = val[_SKM._name];
                if (sGuildName) and ( (iDeath > 0) or (iKill > 0) ) then
                        SKM_Context.Statistics.Guild[sGuildName] = { };
                        SKM_Context.Statistics.Guild[sGuildName].Death = iDeath;
                        SKM_Context.Statistics.Guild[sGuildName].Kill = iKill;
                end
        end


        -- compute averages
        if (iDeathForLevel > 0) then
                SkM_Trace(FName, 3, "iDeathForLevel = "..iDeathForLevel..", iTotalLevelDeath = "..iTotalLevelDeath);
                SKM_Context.Statistics.Globals.DeathAverageLevel = math.floor (iTotalLevelDeath / iDeathForLevel);
        end
        if (iKillForLevel > 0) then
                SkM_Trace(FName, 3, "iKillForLevel = "..iKillForLevel..", iTotalLevelKill = "..iTotalLevelKill);
                SKM_Context.Statistics.Globals.KillAverageLevel = math.floor (iTotalLevelKill / iKillForLevel);
        end

        SkM_Trace(FName, 3, "Average level of victims : "..snil(SKM_Context.Statistics.Globals.KillAverageLevel));
        SkM_Trace(FName, 3, "Average level of executioners : "..snil(SKM_Context.Statistics.Globals.DeathAverageLevel));


        -- compute : by zone, by date
        -- statistics from GlobalMapData map
        local i;
        local iNbNotes = getn(SKM_Data[_RealmName][_PlayerName].GlobalMapData);

        SKM_Context.Statistics.Globals.MapRecords = iNbNotes;

        SkM_Trace(FName, 3, "Global notes count = "..iNbNotes);
        for i=1, iNbNotes, 1 do
                local Note = SKM_Data[_RealmName][_PlayerName].GlobalMapData[i];

                local StoredInfo = Note[_SKM._storedInfo];
                if (StoredInfo) then
        
                        local iKill = 0;
                        local iDeath = 0;
        
                        -- if   (StoredInfo[_SKM._type] == _SKM._playerAssistKill)
                        if   (StoredInfo[_SKM._type] == _SKM._playerAssistKill) and (SkM_GetOption("AssistKillStat"))
                          or (StoredInfo[_SKM._type] == _SKM._playerKill)
                          or (StoredInfo[_SKM._type] == _SKM._playerFullKill) then
                                iKill = 1;
                                SkM_Trace(FName, 3, "PvP Kill : global note = "..i);
        
                        elseif (StoredInfo[_SKM._type] == _SKM._playerDeathPvP) then
                                iDeath = 1;
                                SkM_Trace(FName, 3, "PvP Death : global note = "..i);
                        end
        
                        if (iKill > 0) or (iDeath > 0) then
        
                                --local sZoneText = SKM_Context.Zones[Note[_SKM._continent]][Note[_SKM._zone]];
                                local sZoneText = SkM_GetZoneTextFromIndex(Note[_SKM._continent], Note[_SKM._zone]);
                                if (sZoneText) then
                                        if (SKM_Context.Statistics.Zone[sZoneText] == nil) then
                                                SKM_Context.Statistics.Zone[sZoneText] = { };
                                        end
                                        SKM_Context.Statistics.Zone[sZoneText].Death = ifnil(SKM_Context.Statistics.Zone[sZoneText].Death, 0) + iDeath;
                                        SKM_Context.Statistics.Zone[sZoneText].Kill = ifnil(SKM_Context.Statistics.Zone[sZoneText].Kill, 0) + iKill;
                                end
        
                                local sDate = string.sub(StoredInfo[_SKM._date], 1, 10);
                                if (sDate) then
                                        if (SKM_Context.Statistics.Date[sDate] == nil) then
                                                SKM_Context.Statistics.Date[sDate] = { };
                                        end
                                        SKM_Context.Statistics.Date[sDate].Death = ifnil(SKM_Context.Statistics.Date[sDate].Death, 0) + iDeath;
                                        SKM_Context.Statistics.Date[sDate].Kill = ifnil(SKM_Context.Statistics.Date[sDate].Kill, 0) + iKill;
                                end
                                
                        end                     
                end
        end

        -- compute : battlegrounds by zone, by date, by date and zone
        for idx1, val1 in SKM_Data[_RealmName][_PlayerName].BGStats do
                local sZone = idx1;
                if (SKM_Context.Statistics.BGZone[sZone] == nil) then
                        SKM_Context.Statistics.BGZone[sZone] = { };
                end

                for idx2, val2 in val1 do
                        local sDate = idx2;
                        local iDeath = ifnil(val2[_SKM._enemyKillBG], 0);
                        local iKill = ifnil(val2[_SKM._playerBGKill], 0);

                        SKM_Context.Statistics.BGZone[sZone].Death = ifnil(SKM_Context.Statistics.BGZone[sZone].Death, 0) + iDeath;
                        SKM_Context.Statistics.BGZone[sZone].Kill = ifnil(SKM_Context.Statistics.BGZone[sZone].Kill, 0) + iKill;

                        if (SKM_Context.Statistics.BGDate[sDate] == nil) then
                                SKM_Context.Statistics.BGDate[sDate] = { };
                        end
                        SKM_Context.Statistics.BGDate[sDate].Death = ifnil(SKM_Context.Statistics.BGDate[sDate].Death, 0) + iDeath;
                        SKM_Context.Statistics.BGDate[sDate].Kill = ifnil(SKM_Context.Statistics.BGDate[sDate].Kill, 0) + iKill;

                        local sDateZone = idx2.." - "..idx1;
                        if (SKM_Context.Statistics.BGDateZone[sDateZone] == nil) then
                                SKM_Context.Statistics.BGDateZone[sDateZone] = { };
                                SKM_Context.Statistics.BGDateZone[sDateZone].Zone = idx1;
                                SKM_Context.Statistics.BGDateZone[sDateZone].Date = idx2;
                        end
                        SKM_Context.Statistics.BGDateZone[sDateZone].Death = ifnil(SKM_Context.Statistics.BGDateZone[sDateZone].Death, 0) + iDeath;
                        SKM_Context.Statistics.BGDateZone[sDateZone].Kill = ifnil(SKM_Context.Statistics.BGDateZone[sDateZone].Kill, 0) + iKill;
                end
        end



        -- provide sortable lists
        SKM_Context.Statistics.ClassList = {};
        for idx, val in SKM_Context.Statistics.Class do
                val.SortKey = idx;
                val.Key = idx;
                table.insert(SKM_Context.Statistics.ClassList, val);
        end
        table.sort(SKM_Context.Statistics.ClassList, SkM_SortStatList);

        SKM_Context.Statistics.RaceList = {};
        for idx, val in SKM_Context.Statistics.Race do
                val.SortKey = idx;
                val.Key = idx;
                table.insert(SKM_Context.Statistics.RaceList, val);
        end

        table.sort(SKM_Context.Statistics.RaceList, SkM_SortStatList);

        SKM_Context.Statistics.EnemyList = {};
        for idx, val in SKM_Context.Statistics.Enemy do
                --val.SortKey = string.upper(idx);
                val.SortKey = SkM_NormalizeString(idx);
                val.Key = idx;
                table.insert(SKM_Context.Statistics.EnemyList, val);
        end
        table.sort(SKM_Context.Statistics.EnemyList, SkM_SortStatList);

        SKM_Context.Statistics.GuildList = {};
        for idx, val in SKM_Context.Statistics.Guild do
                --val.SortKey = string.upper(idx);
                val.SortKey = SkM_NormalizeString(idx);
                val.Key = idx;
                table.insert(SKM_Context.Statistics.GuildList, val);
        end
        table.sort(SKM_Context.Statistics.GuildList, SkM_SortStatList);

        SKM_Context.Statistics.ZoneList = {};
        for idx, val in SKM_Context.Statistics.Zone do
                val.SortKey = idx;
                val.Key = idx;
                table.insert(SKM_Context.Statistics.ZoneList, val);
        end
        table.sort(SKM_Context.Statistics.ZoneList, SkM_SortStatList);

        SKM_Context.Statistics.DateList = {};
        for idx, val in SKM_Context.Statistics.Date do
                val.SortKey = SkM_GetSortableDate(idx);
                val.Key = idx;
                table.insert(SKM_Context.Statistics.DateList, val);
        end
        table.sort(SKM_Context.Statistics.DateList, SkM_SortStatList);

        SKM_Context.Statistics.BGDateList = {};
        for idx, val in SKM_Context.Statistics.BGDate do
                val.SortKey = SkM_GetSortableDate(idx);
                val.Key = idx;
                table.insert(SKM_Context.Statistics.BGDateList, val);
        end
        table.sort(SKM_Context.Statistics.BGDateList, SkM_SortStatList);

        SKM_Context.Statistics.BGZoneList = {};
        for idx, val in SKM_Context.Statistics.BGZone do
                val.SortKey = idx;
                val.Key = idx;
                table.insert(SKM_Context.Statistics.BGZoneList, val);
        end
        table.sort(SKM_Context.Statistics.BGZoneList, SkM_SortStatList);

        SKM_Context.Statistics.BGDateZoneList = {};
        for idx, val in SKM_Context.Statistics.BGDateZone do
                val.SortKey = SkM_GetSortableDate(val.Date)..val.Zone;
                val.Key = idx;
                table.insert(SKM_Context.Statistics.BGDateZoneList, val);
        end
        table.sort(SKM_Context.Statistics.BGDateZoneList, SkM_SortStatList);


end


-- --------------------------------------------------------------------------------------
-- SkM_SortStatList
-- --------------------------------------------------------------------------------------
-- Statistics sorting function
-- --------------------------------------------------------------------------------------
function SkM_SortStatList(e1, e2)
        if (e1.SortKey < e2.SortKey) then
                return true;
        elseif (e2.SortKey < e1.SortKey) then
                return false;
        end
        return false;
end


-- --------------------------------------------------------------------------------------
-- SkM_GetUnitFaction
-- --------------------------------------------------------------------------------------
-- Find player faction for a given unit
-- --------------------------------------------------------------------------------------
function SkM_GetUnitFaction(sUnit)
        if (sUnit == nil) then
                return nil;
        end
        local sRace = UnitRace(sUnit);
        if (sRace == nil) then
                return nil;
        end
        local i;
        for i=1, getn(SKM_PlayerFaction), 1 do
                if ( intable(sRace, SKM_PlayerFaction[i].RaceList) ) then
                        return i, SKM_PlayerFaction[i].Faction;
                end
        end
        return nil;
end


-- --------------------------------------------------------------------------------------
-- SkM_UnitIsEnemyPlayer
-- --------------------------------------------------------------------------------------
-- Is unit an enemy player of the opposite faction ?
-- --------------------------------------------------------------------------------------
function SkM_UnitIsEnemyPlayer(sUnit)
        if (sUnit == nil) then
                return nil;
        end
        return (  (UnitIsPlayer(sUnit))
              and (UnitIsEnemy(SKM_UNIT_PLAYER, sUnit))
              and (SkM_UnitIsOppositeFaction(SKM_UNIT_PLAYER, sUnit))
        );
end


-- --------------------------------------------------------------------------------------
-- SkM_UnitIsDuelingPlayer
-- --------------------------------------------------------------------------------------
-- Is unit a player you are currently dueling ?
-- ie, if he is a player, tagged as "enemy" but of your faction.
-- --------------------------------------------------------------------------------------
function SkM_UnitIsDuelingPlayer(sUnit)
        if (sUnit == nil) then
                return nil;
        end
        return (  (UnitIsPlayer(sUnit))
              and (UnitIsEnemy(SKM_UNIT_PLAYER, sUnit))
              and (not SkM_UnitIsOppositeFaction(SKM_UNIT_PLAYER, sUnit))
        );
end


-- --------------------------------------------------------------------------------------
-- SkM_UnitIsOppositeFaction
-- --------------------------------------------------------------------------------------
-- Check if two units are on opposite faction or not, using their race.
-- Return true if it's the case, false if not, and nil if indeterminate
-- --------------------------------------------------------------------------------------
function SkM_UnitIsOppositeFaction(sUnit1, sUnit2)
        local FName = "SkM_UnitIsOppositeFaction";

        local Faction1 = SkM_GetUnitFaction(sUnit1);
        local Faction2 = SkM_GetUnitFaction(sUnit2);

        if (Faction1 == nil) then
                SkM_Trace(FName, 1, "Unknown faction for "..snil(sUnit1));
                return nil;
        end
        if (Faction2 == nil) then
                SkM_Trace(FName, 1, "Unknown faction for "..snil(sUnit2));
                return nil;
        end

        if (Faction1 == Faction2) then
                return false;
        else
                return true;
        end

end


-- --------------------------------------------------------------------------------------
-- SkM_DeleteEnemy
-- --------------------------------------------------------------------------------------
-- Delete an enemy player and all associated map records (kills or deaths).
-- --------------------------------------------------------------------------------------
function SkM_DeleteEnemy(sName)
        local FName = "SkM_DeleteEnemy";

        local Enemy = SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName];

        if (Enemy == nil) then
                SkM_Trace(FName, 1, "Enemy not found : "..snil(sName));
                return;
        end

        SkM_Trace(FName, 1, "Removing Enemy : "..sName);

        SkM_UpdateEnemyHistory();

        -- remove all recorded events associated to this enemy : kills and deaths
        local i;
        local iNbNotes = table.getn(SKM_Data[_RealmName][_PlayerName].GlobalMapData);
        for i=iNbNotes, 1, -1 do
                local Note = SKM_Data[_RealmName][_PlayerName].GlobalMapData[i];

                local StoredInfo = Note[_SKM._storedInfo];

                if (StoredInfo) and (StoredInfo[_SKM._name] == sName) then

                        -- remove from GlobalMapData and from MapData
                        SkM_Trace(FName, 3, "Removing note : global index = "..i);

                        SkM_DeleteNote(_RealmName, _PlayerName, i);
                end
        end

        -- finally, delete the enemy. Bye bye !
        SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName] = nil;
end


-- --------------------------------------------------------------------------------------
-- SkM_DeleteDuelEnemy
-- --------------------------------------------------------------------------------------
-- Delete all duel information associated to a given player
-- --------------------------------------------------------------------------------------
function SkM_DeleteDuelEnemy(sName)
        local FName = "SkM_DeleteDuelEnemy";

        SKM_Data[_RealmName][_PlayerName].DuelHistory[sName] = nil;
end


-- --------------------------------------------------------------------------------------
-- SkM_GetKnownEnemyType
-- --------------------------------------------------------------------------------------
-- Return _SKM._enemyPlayer if given name matches a known player, otherwise return nil
-- (ie, we do not know for now if it's a player or not, but we may get the information
-- later on)
-- --------------------------------------------------------------------------------------
function SkM_GetKnownEnemyType(sName)
        local EnemyType;
        local Enemy = SKM_Data[_RealmName][_PlayerName].EnemyHistory[sName];
        if (Enemy) then
                EnemyType = _SKM._enemyPlayer;
        end
        return EnemyType;
end


-- --------------------------------------------------------------------------------------
-- SkM_UnitName
-- --------------------------------------------------------------------------------------
-- Call UnitName to get unit name.
-- Return nil if we get "Unknown Entity".
-- --------------------------------------------------------------------------------------
function SkM_UnitName(sUnit)
        local sName = UnitName(sUnit);

        -- if we got "unknown entity", try again, maybe it will work this time
        if (sName == SKM_UNKNOWN_ENTITY) then
                sName = UnitName(sUnit);
        end

        if (sName == "") or (sName == SKM_UNKNOWN_ENTITY) then
                sName = nil;
        end
        return sName;
end


-- --------------------------------------------------------------------------------------
-- SkM_IsTotem
-- --------------------------------------------------------------------------------------
-- Check if given name matches a totem (and *not* a player)
-- --------------------------------------------------------------------------------------
function SkM_IsTotem(sName)
        local FName = "SkM_IsTotem";

        if (sName) then
                for sType in string.gfind(sName, SKM_Context.Pattern.Totem) do
                        if (sType) then
                                SkM_Trace(FName, 2, "Name = "..snil(sName).." : this is a totem. Type = "..sType);
                                return true;
                        end
                end
        end

        SkM_Trace(FName, 3, "Name = "..snil(sName).." : not a totem");
        return false;

        --  if (string.find(sName, SKM_Context.Pattern.Totem)) then
        --    SkM_Trace(FName, 2, "Name = "..snil(sName).." : this is a totem");
        --    return true;
        --  else
        --    SkM_Trace(FName, 3, "Name = "..snil(sName).." : not a totem");
        --    return false;
        --  end

end


function SkM_NormalizeString(sName)
        local FName = "SkM_NormalizeString";

        if (sName == nil) then
                return nil;
        end

        local sString = string.upper(sName);
        i=1;
        while (string.sub(sString, i, i) == " ") and (i < string.len(sString)) do
                i = i + 1;
        end
        if (i > 1 ) then
                sString = string.sub(sString, i, string.len(sString));
        end

        sString = SkM_NormString(sString, 2); -- only need to normalize the first two chars to provide an accurate sort
        return sString;
end


function SkM_LogDuel(sWinner, sLoser)
        local FName = "SkM_LogDuel";

        local sName;
        local sDate = SkM_GetDate();
        local bWin;

        if (sWinner == _PlayerName) then
                sName = sLoser;
                bWin = true;
        else
                sName = sWinner;
                bWin = false;
        end

        if (SKM_Context.DuelEnemy == nil) or (SKM_Context.DuelEnemy[_SKM._name] ~= sName) then
                -- rare case of a finished duel but we did not see at any time our enemy.
                -- okay, force target him then.
                -- I agree that messing with player target is a bad idea in most cases, but at the end
                -- of a duel it should not matter much.

                SkM_Trace(FName, 2, "End of duel but no info about enemy "..snil(sName).." : force target");

                TargetByName(sName);
                SkM_StoreDuelEnemyInfo(SKM_UNIT_TARGET);

                if (SKM_Context.DuelEnemy == nil) or (SKM_Context.DuelEnemy[_SKM._name] ~= sName) then
                        -- might potentially happen if enemy is too far away. Even more unlikely, but
                        -- just in case...
                        SkM_Trace(FName, 1, "Still no info about enemy !");
                        SKM_Context.DuelEnemy = nil;
                        return;
                end

        end

        if (not SKM_Data[_RealmName][_PlayerName].DuelHistory[sName]) then
                SKM_Data[_RealmName][_PlayerName].DuelHistory[sName] = { };
                SKM_Data[_RealmName][_PlayerName].DuelHistory[sName][_SKM._name] = sName;

                SKM_Data[_RealmName][_PlayerName].DuelHistory[sName][_SKM._race] = SKM_Context.DuelEnemy[_SKM._race];
                SKM_Data[_RealmName][_PlayerName].DuelHistory[sName][_SKM._class] = SKM_Context.DuelEnemy[_SKM._class];
                SKM_Data[_RealmName][_PlayerName].DuelHistory[sName][_SKM._win] = 0;
                SKM_Data[_RealmName][_PlayerName].DuelHistory[sName][_SKM._loss] = 0;
                SKM_Data[_RealmName][_PlayerName].DuelHistory[sName][_SKM._duel] = 0;
        else
                if (not SKM_Data[_RealmName][_PlayerName].DuelHistory[sName][_SKM._race]) then
                        SKM_Data[_RealmName][_PlayerName].DuelHistory[sName][_SKM._race] = SKM_Context.DuelEnemy[_SKM._race];
                end
                if (not SKM_Data[_RealmName][_PlayerName].DuelHistory[sName][_SKM._class]) then
                        SKM_Data[_RealmName][_PlayerName].DuelHistory[sName][_SKM._class] = SKM_Context.DuelEnemy[_SKM._class];
                end
        end

        SKM_Data[_RealmName][_PlayerName].DuelHistory[sName][_SKM._level] = SKM_Context.DuelEnemy[_SKM._level];
        SKM_Data[_RealmName][_PlayerName].DuelHistory[sName][_SKM._guild] = SKM_Context.DuelEnemy[_SKM._guild];
        SKM_Data[_RealmName][_PlayerName].DuelHistory[sName][_SKM._lastDuel] = sDate;

        SKM_Data[_RealmName][_PlayerName].DuelHistory[sName][_SKM._duel] = ifnil(SKM_Data[_RealmName][_PlayerName].DuelHistory[sName][_SKM._duel], 0) + 1;
        if (bWin) then
                SKM_Data[_RealmName][_PlayerName].DuelHistory[sName][_SKM._win] = ifnil(SKM_Data[_RealmName][_PlayerName].DuelHistory[sName][_SKM._win], 0) + 1;
                SkM_Trace(FName, 1, "Duel won vs "..sName..", Win = "..SKM_Data[_RealmName][_PlayerName].DuelHistory[sName][_SKM._win]..", Loss = "..SKM_Data[_RealmName][_PlayerName].DuelHistory[sName][_SKM._loss]..", Total = "..SKM_Data[_RealmName][_PlayerName].DuelHistory[sName][_SKM._duel]);
        else
                SKM_Data[_RealmName][_PlayerName].DuelHistory[sName][_SKM._loss] = ifnil(SKM_Data[_RealmName][_PlayerName].DuelHistory[sName][_SKM._loss], 0) + 1;
                SkM_Trace(FName, 1, "Duel lost vs "..sName..", Win = "..SKM_Data[_RealmName][_PlayerName].DuelHistory[sName][_SKM._win]..", Loss = "..SKM_Data[_RealmName][_PlayerName].DuelHistory[sName][_SKM._loss]..", Total = "..SKM_Data[_RealmName][_PlayerName].DuelHistory[sName][_SKM._duel]);
        end

        -- clear duel information
        SKM_Context.DuelEnemy = nil;
end


function SkM_ParseDuelResult(sMsg)
        local FName = "SkM_ParseDuelResult";

        local sWinner, sLoser;

        if (not SkM_GetOption("StoreDuels")) then
                return;
        end

        for sWinner, sLoser in string.gfind(sMsg, SKM_Context.Pattern.Duel_Won) do
                if (sWinner and sLoser) then
                        if (sWinner == _PlayerName) or (sLoser == _PlayerName) then
                                SkM_Trace(FName, 3, "Duel_Won : Winner = "..sWinner..", Loser = "..sLoser);

                                SkM_LogDuel(sWinner, sLoser);
                                return;
                        end
                end
        end

end


function SkM_UpdateBGStats(sZoneName, sDate, iDeath, iKill)

        if (not SKM_Data[_RealmName][_PlayerName].BGStats[sZoneName]) then
                SKM_Data[_RealmName][_PlayerName].BGStats[sZoneName] = { };
        end

        if (not SKM_Data[_RealmName][_PlayerName].BGStats[sZoneName][sDate]) then
                SKM_Data[_RealmName][_PlayerName].BGStats[sZoneName][sDate] = { };
                SKM_Data[_RealmName][_PlayerName].BGStats[sZoneName][sDate][_SKM._enemyKillBG] = 0;
                SKM_Data[_RealmName][_PlayerName].BGStats[sZoneName][sDate][_SKM._playerBGKill] = 0;
        end

        if (iDeath) then
                SKM_Data[_RealmName][_PlayerName].BGStats[sZoneName][sDate][_SKM._enemyKillBG] = ifnil(SKM_Data[_RealmName][_PlayerName].BGStats[sZoneName][sDate][_SKM._enemyKillBG], 0) + iDeath;
        end

        if (iKill) then
                SKM_Data[_RealmName][_PlayerName].BGStats[sZoneName][sDate][_SKM._playerBGKill] = ifnil(SKM_Data[_RealmName][_PlayerName].BGStats[sZoneName][sDate][_SKM._playerBGKill], 0) + iKill;
        end
end


function SkM_BGStats_AddDeath()
        local sZone = SkM_GetZoneText();
        local sDate = SkM_GetDay();
        SkM_UpdateBGStats(sZone, sDate, 1, nil);
end


function SkM_BGStats_AddKill()
        local sZone = SkM_GetZoneText();
        local sDate = SkM_GetDay();
        SkM_UpdateBGStats(sZone, sDate, nil, 1);
end


function SkM_NormString2(sString)
        local sRes = sString;
        local idx, val, i;
        for idx, val in SKM_ToStandardCase do
                for i=1, table.getn(val), 1 do
                        sRes = string.gsub(sRes, val[i], idx);
                end
        end
        return sRes;
end


function SkM_NormString(sInputString, iNormMinLen)
        local FName = "SkM_NormString";
        local sString = string.upper(sInputString);
        local iLen = string.len(sString);
        local sRes = "";
        local sNonStd = "";

        local iByte_A = string.byte("A");
        local iByte_Z = string.byte("Z");
        local iByte_a = string.byte("a");
        local iByte_z = string.byte("z");

        local i;
        for i=1,iLen,1 do
                --SkM_Trace(FName, 3, i.." Res="..sRes);

                local sChar = string.sub(sString, i, i);
                local iByte = string.byte(sString, i);
                if   ((iByte >= iByte_A) and (iByte <= iByte_Z))
                  or ((iByte >= iByte_a) and (iByte <= iByte_z))
                  or (intable(sChar, { " ", "-" } ))
                then
                        if (string.len(sNonStd) > 0) then
                                sRes = sRes..SkM_NormString2(sNonStd);
                                sNonStd = "";
                        end
                        sRes = sRes..sChar;
                        if (i<iLen) and (iNormMinLen) and (string.len(sRes) >= iNormMinLen) then
                                sRes = sRes..string.sub(sString, i+1, iLen);
                                return sRes;
                        end
                else
                        sNonStd = sNonStd..sChar;
                end
        end
        if (string.len(sNonStd) > 0) then
                sRes = sRes..SkM_NormString2(sNonStd);
                sNonStd = "";
        end

        return sRes;
end


function SkM_GetEnemyList(sName, bNormalize, bPrefix)
        local FName = "SkM_GetEnemyList";
        local TheList = { };

        local sEnemyName = string.upper(sName);
        if (bNormalize) then
                sEnemyName = SkM_NormString(sName);
        end
        local iNameLen = string.len(sEnemyName);

        SkM_Trace(FName, 3, "Name = "..sName);

        local ResList = {};
        for idx, val in SKM_Data[_RealmName][_PlayerName].EnemyHistory do
                local sCurName = string.upper(idx);
                local bMatch = false;
                if (bNormalize) then
                        sCurName = SkM_NormString(sCurName, iNameLen);
                end
                SkM_Trace(FName, 3, "CurName = "..sCurName);
                if (not bPrefix) then
                        if (sCurName == sEnemyName) then
                                bMatch = true;
                        end
                else
                        if (string.sub(sCurName, 1, iNameLen) == sEnemyName) then
                                bMatch = true;
                        end
                end

                if (bMatch) then
                        table.insert(TheList, idx);
                end
        end

        table.sort(TheList, function(e1,e2) return e1<e2; end);

        return TheList;
end


function SkM_GetEnemyInfo(sName, bMatchFullName, bMatchSpecialChar)
        local FName = "SkM_GetEnemyInfo";

        SkM_Trace(FName, 2, "Name = "..sName..", FullName = "..snil(bMatchFullName)..", MatchSpec = "..snil(bMatchSpecialChar));

        local bNormalize = true;
        local bPrefix = true;

        if (bMatchSpecialChar) then
                bNormalize = false;
        end
        if (bMatchFullName) then
                bPrefix = false;
        end

        SkM_ChatMessageCol("Looking for : "..sName);

        local EnemyList = SkM_GetEnemyList(sName, bNormalize, bPrefix);
        local iEnemyCount = table.getn(EnemyList);

        for i=1,iEnemyCount,1 do
                local Enemy = SKM_Data[_RealmName][_PlayerName].EnemyHistory[EnemyList[i]];
                if (Enemy) then
                        local Lines = {};

                        local iKill = ifnil(Enemy[_SKM._playerKill], 0);
                        local iAssistKill = ifnil(Enemy[_SKM._playerAssistKill], 0);
                        local iFullKill = ifnil(Enemy[_SKM._playerFullKill], 0);
                        local iTotalKill = iKill + iAssistKill + iFullKill;
                        local iDeath = ifnil(Enemy[_SKM._enemyKillPlayer], 0);
                        local iMet = ifnil(Enemy[_SKM._meetCount], 0);
                        local sDisplayDate = string.sub(Enemy[_SKM._lastView], 1, 10);

                        local Guild;
                        local bGuildWar = false;
                        if (Enemy[_SKM._guild] ~= nil) and (Enemy[_SKM._guild] ~= "") then
                                Guild = SKM_Data[_RealmName][_PlayerName].GuildHistory[Enemy[_SKM._guild]];
                                bGuildWar = Guild[_SKM._atWar];
                        end

                        -- line 1 : <rank> <player> <guild> : level <level> <race> <class>

                        local sLine = "";
                        if (Enemy[_SKM._rank]) then
                                sLine = sLine..SKM_Config.Col_Rank..Enemy[_SKM._rank].." ";
                        end

                        if (Enemy[_SKM._atWar]) then
                                sLine = sLine..SKM_Config.Col_PlayerWar;
                        else
                                sLine = sLine..SKM_Config.Col_Label;
                        end
                        sLine = sLine..Enemy[_SKM._name]..SKM_Config.Col_Label;

                        if (Guild) then
                                sLine = sLine.." <";
                                if (Guild[_SKM._atWar]) then
                                        sLine = sLine..SKM_Config.Col_PlayerWar;
                                end
                                sLine = sLine..Enemy[_SKM._guild]..SKM_Config.Col_Label..">";
                        end

                        if (Enemy[_SKM._level] ~= nil) and (Enemy[_SKM._race] ~= nil) and (Enemy[_SKM._class] ~= nil) then
                                sLine = sLine.." : "..SKM_UI_STRINGS.List_Frame_Level..Enemy[_SKM._level].." "..SkM_GetRaceText(Enemy[_SKM._race]).." "..SkM_GetClassText(Enemy[_SKM._class]);
                        end

                        table.insert(Lines, sLine);

                        if (iEnemyCount == 1) then

                                -- line 2 : <met> <kill> <death> <last seen date and location>
                                sLine = "";

                                sLine = sLine..SKM_Config.Col_LabelTitle..SKM_UI_STRINGS.List_Frame_Met..SKM_Config.Col_Label..iMet;
                                sLine = sLine.."  "..SKM_Config.Col_LabelTitle..SKM_UI_STRINGS.List_Frame_Kill..SKM_Config.Col_Label..iTotalKill;
                                sLine = sLine.."  "..SKM_Config.Col_LabelTitle..SKM_UI_STRINGS.List_Frame_Death..SKM_Config.Col_Label .. iDeath;

                                sLine = sLine.."  "..SKM_Config.Col_LabelTitle..SKM_UI_STRINGS.List_Frame_Last_Seen;
                                sLine = sLine..SKM_Config.Col_Label..sDisplayDate;

                                if (Enemy[_SKM._continent] ~= nil) and (Enemy[_SKM._zone] ~= nil) then
                                        local sZoneText = SkM_GetZoneTextFromIndex(Enemy[_SKM._continent], Enemy[_SKM._zone]);
                                        sLine = sLine.." - "..sZoneText;

                                elseif (Enemy[_SKM._zoneName] ~= nil) then
                                        sLine = sLine.." - "..Enemy[_SKM._zoneName];
                                end

                                table.insert(Lines, sLine);


                                if (Enemy[_SKM._playerNote] ~= nil) and (Enemy[_SKM._playerNote] ~= "") then

                                        local sLine = "";

                                        sLine = sLine..SKM_Config.Col_LabelTitle..SKM_UI_STRINGS.List_Frame_Note;
                                        sLine = sLine..SKM_Config.Col_Label..Enemy[_SKM._playerNote];

                                        table.insert(Lines, sLine);
                                end


                        end

                        for i=1,table.getn(Lines),1 do
                                DEFAULT_CHAT_FRAME:AddMessage(Lines[i]);
                        end

                end
        end

        --SkM_ChatMessageCol("Enemy matching : "..iEnemyCount);
        SkM_ChatMessageCol("Found : "..iEnemyCount);
end


function SkM_DataCleanUp()
        local FName = "SkM_DataCleanUp";
        if (not SKM_Data) then
                return;
        end
        if (not SKM_Settings) then
                return;
        end

        if (SkM_GetOption("DataCleanUp")) then
                local sDate = SkM_GetDate();
                local iDiffTime = SkM_DiffDate(sDate, SKM_Settings.LastDataCleanUp);
                if (SKM_Settings.LastDataCleanUp == nil) or (iDiffTime == nil) or (iDiffTime > SkM_GetOption("DataCleanUpInterval") * 3600 * 24) then

                        SkM_DoCleanUp();

                        SKM_Settings.LastDataCleanUp = sDate;
                end
        end

end


function SkM_DoCleanUp()
        local FName = "SkM_DoCleanUp";

        local sDate = SkM_GetDate();

        if (SkM_GetOption("CleanInactiveEnemies")) then

                for idx_realm, val_realm in SKM_Data do
                        for idx_char, val_char in SKM_Data[idx_realm] do
                                if (SKM_Data[idx_realm][idx_char].PlayerName == idx_char) then

                                        SkM_Trace(FName, 1, "Cleaning for "..idx_realm.." / "..idx_char);
                                        for idx_enemy, val_enemy in SKM_Data[idx_realm][idx_char].EnemyHistory do

                                                local iDiffTime = SkM_DiffDate(sDate, val_enemy[_SKM._lastView]);
                                                if (iDiffTime ~= nil) and (iDiffTime > SkM_GetOption("CleanInactiveEnemiesDelay") * 3600 * 24) then
                                                        -- long time no see. Check if we have to remove him
                                                        if   (ifnil(val_enemy[_SKM._playerAssistKill], 0) + ifnil(val_enemy[_SKM._playerKill], 0) + ifnil(val_enemy[_SKM._playerFullKill], 0) == 0)
                                                         and (ifnil(val_enemy[_SKM._enemyKillPlayer], 0) == 0)
                                                         and (ifnil(val_enemy[_SKM._enemyKillBG], 0) == 0)
                                                         and (ifnil(val_enemy[_SKM._playerBGKill], 0) == 0)
                                                         and not (val_enemy[_SKM._atWar])
                                                         and ( (val_enemy[_SKM._playerNote] == nil) or (val_enemy[_SKM._playerNote] == "") )
                                                        then
                                                                SkM_Trace(FName, 2, idx_realm.." / "..idx_char.." : remove "..idx_enemy);

                                                                SKM_Data[idx_realm][idx_char].EnemyHistory[idx_enemy] = nil;
                                                        end

                                                end
                                        end
                                end
                        end
                end
        end

        if (SkM_GetOption("CleanEmptyGuilds")) then
                for idx_realm, val_realm in SKM_Data do
                        for idx_char, val_char in SKM_Data[idx_realm] do
                                if (SKM_Data[idx_realm][idx_char].PlayerName == idx_char) then
                                        for idx_guild, val_guild in SKM_Data[idx_realm][idx_char].GuildHistory do

                                                if (SkM_CountGuildMembers(idx_guild, idx_realm, idx_char) == 0) then
                                                        SkM_Trace(FName, 2, idx_realm.." / "..idx_char.." : remove "..idx_guild);
                                                        SKM_Data[idx_realm][idx_char].GuildHistory[idx_guild] = nil;
                                                end
                                        end
                                end
                        end
                end
        end

end



function SkM_ZoneRematch(RealmName, PlayerName, ZoneShift, minDate, maxDate)
        local FName = "SkM_ZoneRematch";
        local idx_c, val_c, idx_z, val_z, idx_gn, Note;
        local cont_shift, zone_shift;

        local iCountShift = 0;
        local iCountNoShift = 0;

        if (ZoneShift == nil) then
                SkM_Trace(FName, 1, "ZoneShift is nil ! ");
                return;
        end

        -- reinitialize map data
        SKM_Data[RealmName][PlayerName].MapData = { };

        for idx_c, val_c in SKM_Context.Continents do
                SKM_Data[RealmName][PlayerName].MapData[idx_c] = { };

                for idx_z, val_z in SKM_Context.Zones[idx_c] do
                        SKM_Data[RealmName][PlayerName].MapData[idx_c][idx_z] = { };
                end
        end


        -- parse global map data to rebuild local map data
        if (SKM_Data[RealmName][PlayerName].GlobalMapData) then
                for idx_gn, Note in SKM_Data[RealmName][PlayerName].GlobalMapData do
                        local bDoShift = true;
                        idx_c = Note[_SKM._continent];
                        idx_z = Note[_SKM._zone];

                        local StoredInfo = Note[_SKM._storedInfo];

                        if (StoredInfo) and (StoredInfo[_SKM._date]) then
                                if (maxDate) then
                                        local iDiffTime = SkM_DiffDate(maxDate, StoredInfo[_SKM._date]);
                                        if (iDiffTime < 0) then
                                                -- Note generated after max date, skip
                                                bDoShift = false;
                                        end
                                end
                                if (minDate) then
                                        local iDiffTime = SkM_DiffDate(minDate, StoredInfo[_SKM._date]);
                                        if (iDiffTime > 0) then
                                                -- Note generated before min date, skip
                                                bDoShift = false;
                                        end
                                end
                        end

                        if (bDoShift) then
                                zone_shift = ZoneShift[idx_c][idx_z];

                                -- insert new map note
                                table.insert(SKM_Data[RealmName][PlayerName].MapData[idx_c][zone_shift], idx_gn);

                                -- update global note zone index
                                SKM_Data[RealmName][PlayerName].GlobalMapData[idx_gn][_SKM._zone] = zone_shift;

                                iCountShift = iCountShift + 1;
                        else
                                -- insert unchanged map note
                                table.insert(SKM_Data[RealmName][PlayerName].MapData[idx_c][idx_z], idx_gn);

                                iCountNoShift = iCountNoShift + 1;
                        end
                end

        end


        -- parse enemy information and shift zone where last seen
        for idx_enemy, val_enemy in SKM_Data[RealmName][PlayerName].EnemyHistory do
                idx_c = val_enemy[_SKM._continent];
                idx_z = val_enemy[_SKM._zone];
                local bDoShift = true;

                if (idx_c == nil) or (idx_z == nil) then
                        bDoShift = false;

                        if (val_enemy[_SKM._lastView]) then
                                if (maxDate) then
                                        local iDiffTime = SkM_DiffDate(maxDate, val_enemy[_SKM._lastView]);
                                        if (iDiffTime < 0) then
                                                -- generated after max date, skip
                                                bDoShift = false;
                                        end
                                end
                                if (minDate) then
                                        local iDiffTime = SkM_DiffDate(minDate, val_enemy[_SKM._lastView]);
                                        if (iDiffTime > 0) then
                                                -- generated before min date, skip
                                                bDoShift = false;
                                        end
                                end
                        end
                end

                if (bDoShift) then
                        zone_shift = ZoneShift[idx_c][idx_z];

                        SKM_Data[RealmName][PlayerName].EnemyHistory[idx_enemy][_SKM._zone] = zone_shift;
                end
        end

        SkM_Trace(FName, 1, "Realm = "..RealmName.." / Player = "..PlayerName.." : Zone Rematch completed, shift count = "..iCountShift..", 'no shift' count = "..iCountNoShift);
end


function SkM_AccountZoneRematch(ZoneShift, minDate, maxDate)
        local FName = "SkM_AccountZoneRematch";

        if (ZoneShift == nil) then
                SkM_Trace(FName, 1, "ZoneShift is nil ! ");
                return;
        end

        local idx_realm, val_realm, idx_char, val_char;

        for idx_realm, val_realm in SKM_Data do
                for idx_char, val_char in SKM_Data[idx_realm] do
                        if (SKM_Data[idx_realm][idx_char].PlayerName == idx_char) then
                                SkM_ZoneRematch(idx_realm, idx_char, ZoneShift, minDate, maxDate)
                        end
                end
        end
end


function SkM_DoMapShift(Source, Dest, ZoneShift, minDate, maxDate)
        local FName = "SkM_DoMapShift";

        local sDate = SkM_GetDate();

        if (ZoneShift == nil) then
                SkM_Trace(FName, 1, "ZoneShift is nil ! ");
                return;
        end

        SkM_Trace(FName, 1, "Do shift ("..snil(Source).." -> "..snil(Dest)..")");
        SkM_Trace(FName, 1, "Shift interval = ["..snil(minDate).." -> "..snil(maxDate).."]");

        SkM_AccountZoneRematch(ZoneShift, minDate, maxDate);

        if (SKM_Settings.ZoneShifts == nil) then
                SKM_Settings.ZoneShifts = {};
        end
        local ZoneShiftRecord = {
                Date = sDate;
                Source = Source;
                Dest = Dest;
                minDate = minDate;
                maxDate = maxDate;
        };
        table.insert(SKM_Settings.ZoneShifts, ZoneShiftRecord);

end


function SkM_GetShiftTable(source, dest)
        if (SKM_ShiftTables == nil) then
                return nil;
        end
        if (SKM_ShiftTables[source] == nil) then
                return nil;
        end
        return SKM_ShiftTables[source][dest];
end


-- --------------------------------------------------------------------------------------
-- SkM_MapShiftMigration
-- --------------------------------------------------------------------------------------
-- Proceed to all map shift migrations, if needed.
-- If no new zone shift is defined since last migration, nothing will be done.
-- --------------------------------------------------------------------------------------
function SkM_MapShiftMigration()
        local FName = "SkM_MapShiftMigration";

        local bShift = false;
        local curTime = GetTime();

        -- check if there are map migrations that need be done

        -- NOTE : the following case is not handled and WILL NOT BE !!
        -- if user changes his locale interface language while there has been a new locale
        -- shift defined on his previous language, before installing the new version of SKMap.
        -- too bad !


        SkM_Trace(FName, 1, "Automatic map migration starting...");

        local bNewLocale = false;
        -- if we don't have information of last local language, we assume there were no
        -- language change... otherwise, there's nothing we can do...
        if (SKM_Settings.LastLocale ~= nil) then
                if (SKM_CurrentLocale ~= SKM_Settings.LastLocale) then
                        bNewLocale = true;
                end
        else
                SKM_Settings.LastLocale = SKM_CurrentLocale;
        end


        -- are there new locale shift defines for previous language ?
        local iNbLocalShift = 0;
        local iNextLocalShift = 0;
        local bNewLocalShift = false;
        if (SKM_Locale[SKM_Settings.LastLocale]) and (SKM_Locale[SKM_Settings.LastLocale].LocalShift) then
                iNbLocalShift = table.getn(SKM_Locale[SKM_Settings.LastLocale].LocalShift);
        end
        if (iNbLocalShift > ifnil(SKM_Settings.LastLocalShift, 0)) then
                bNewLocalShift = true;
                iNextLocalShift = ifnil(SKM_Settings.LastLocalShift, 0) + 1;
        end


        if (bNewLocalShift) then

                if (SKM_Settings.ZoneShiftDest) and (SKM_Settings.ZoneShiftSource) then
                        -- apply reverse shift first for all data ulterior to next local shift

                        SkM_DoMapShift(
                                SKM_Settings.ZoneShiftDest, SKM_Settings.ZoneShiftSource,
                                SkM_GetShiftTable(SKM_Settings.ZoneShiftDest, SKM_Settings.ZoneShiftSource),
                                SKM_Locale[SKM_Settings.LastLocale].LocalShift[iNextShift].DateShift, nil);

                        bShift = true;
                end

                SKM_Settings.LastLocalShift = 0;
                SKM_Settings.LastCurrentShift = 0;

                -- now perform all new local shifts
                -- each local shift is performed on all data up to the local shift date
                local i;
                for i=iNextLocalShift,iNbLocalShift, 1 do

                        SkM_Trace(FName, 3, "Performing local shift "..i);
                        local Source = SKM_Locale[SKM_Settings.LastLocale].LocalShift[i].Source;
                        local Dest = SKM_Locale[SKM_Settings.LastLocale].LocalShift[i].Dest;
                        local DateShift = SKM_Locale[SKM_Settings.LastLocale].LocalShift[i].DateShift;

                        SkM_DoMapShift(Source, Dest, SkM_GetShiftTable(Source, Dest), nil, DateShift)

                        bShift = true;

                        SKM_Settings.LastLocalShift = i;
                end

                -- now apply current shift if need be
                -- moved out of if condition, see below
        end

        -- now apply current shift if need be
        if (ifnil(SKM_Settings.LastCurrentShift, 0) == 0) then
                if (SKM_Locale[SKM_Settings.LastLocale].CurrentShift) then

                        local Source = SKM_Locale[SKM_Settings.LastLocale].CurrentShift.Source;
                        local Dest = SKM_Locale[SKM_Settings.LastLocale].CurrentShift.Dest;

                        if (Source == SKM_Settings.ZoneShiftSource) and (Dest == SKM_Settings.ZoneShiftDest) then
                                SkM_Trace(FName, 3, "current shift already performed");
                        else

                                SkM_Trace(FName, 3, "Perform final shift (current)");

                                local DateMin = nil;
                                local DateMax = nil;

                                -- fix for 1.4 bug for DE version
                                if (SKM_CurrentLocale == "DE") then
                                        DateMax = SKM_Context.DateIndexBug;
                                end

                                SkM_DoMapShift(Source, Dest, SkM_GetShiftTable(Source, Dest), DateMin, DateMax);

                                bShift = true;
                        end

                        SKM_Settings.LastCurrentShift = 1;

                        SKM_Settings.ZoneShiftSource = Source;
                        SKM_Settings.ZoneShiftDest = Dest;
                else
                        -- no current shift
                        SKM_Settings.ZoneShiftSource = nil;
                        SKM_Settings.ZoneShiftDest = nil;
                end
        end


        if (bNewLocale) then

                if (SKM_Locale[SKM_CurrentLocale]) and (SKM_Locale[SKM_CurrentLocale].CurrentShift) then
                        SKM_Settings.ZoneShiftSource = SKM_Locale[SKM_CurrentLocale].CurrentShift.Source;
                        SKM_Settings.ZoneShiftDest = SKM_Locale[SKM_CurrentLocale].CurrentShift.Dest;
                else
                        -- no current shift

                        SKM_Settings.ZoneShiftSource = nil;
                        SKM_Settings.ZoneShiftDest = nil;
                end

                -- now we can safely store the new language as the current one.
                SKM_Settings.LastLocale = SKM_CurrentLocale;

                -- also store LastLocalShift as the latest local shift, if any
                if (SKM_Locale[SKM_Settings.LastLocale]) and (SKM_Locale[SKM_Settings.LastLocale].LocalShift) then
                        iNbLocalShift = table.getn(SKM_Locale[SKM_Settings.LastLocale].LocalShift);
                        SKM_Settings.LastLocalShift = iNbLocalShift;
                else
                        SKM_Settings.LastLocalShift = nil;
                end

        end


        SkM_Trace(FName, 3, "Zone Shift performed ? "..snil(bShift));

        if (bShift) then
                local endTime = GetTime();
                local elapsedTime = math.floor(100 * (endTime - curTime)) / 100;
                SkM_ChatMessageCol("Zone order shift(s) performed, elapsed time : "..elapsedTime.." s");
        end

end


function SkM_ReverseZoneShift(ZoneShift)
        local NewShift = { };

        local i,j;
        for i=1,table.getn(ZoneShift),1 do
                local Line = { };
                for j=1,table.getn(ZoneShift[i]),1 do
                        Line[ZoneShift[i][j]] = j;
                end
                NewShift[i] = Line;
        end
        return NewShift;
end


function SkM_CharDataMigration(Ver, RealmName, PlayerName)
        local FName = "SkM_CharDataMigration";

        -- Migration of EnemyHistory
        if (SKM_Data[RealmName][PlayerName].EnemyHistory) then
                local idx_enemy, val_enemy;
                for idx_enemy, val_enemy in SKM_Data[RealmName][PlayerName].EnemyHistory do
                        local EnemyMigr = {};
                        local idx, val;
                        for idx, val in SKM_IndexMigr[Ver].EnemyHistory do
                                if (val_enemy[val.Old]) then
                                        EnemyMigr[val.New] = val_enemy[val.Old];
                                end
                        end

                        SKM_Data[RealmName][PlayerName].EnemyHistory[idx_enemy] = nil;
                        SKM_Data[RealmName][PlayerName].EnemyHistory[idx_enemy] = EnemyMigr;

                        -- now migration of race and class to indexes
                        local sRace = SKM_Data[RealmName][PlayerName].EnemyHistory[idx_enemy][_SKM._race];
                        local sClass = SKM_Data[RealmName][PlayerName].EnemyHistory[idx_enemy][_SKM._class];
                        local id_race = SKM_Context.Race.StringToIndex[sRace];
                        local id_class = SKM_Context.Class.StringToIndex[sClass];
                        SKM_Data[RealmName][PlayerName].EnemyHistory[idx_enemy][_SKM._race] = id_race;
                        SKM_Data[RealmName][PlayerName].EnemyHistory[idx_enemy][_SKM._class] = id_class;
                end
        end

        -- Migration of DuelHistory
        if (SKM_Data[RealmName][PlayerName].DuelHistory) then
                local idx_enemy, val_enemy;
                for idx_enemy, val_enemy in SKM_Data[RealmName][PlayerName].DuelHistory do
                        local DuelMigr = {};
                        local idx, val;
                        for idx, val in SKM_IndexMigr[Ver].DuelHistory do
                                if (val_enemy[val.Old]) then
                                        DuelMigr[val.New] = val_enemy[val.Old];
                                end
                        end

                        SKM_Data[RealmName][PlayerName].DuelHistory[idx_enemy] = nil;
                        SKM_Data[RealmName][PlayerName].DuelHistory[idx_enemy] = DuelMigr;

                        -- now migration of race and class to indexes
                        local sRace = SKM_Data[RealmName][PlayerName].DuelHistory[idx_enemy][_SKM._race];
                        local sClass = SKM_Data[RealmName][PlayerName].DuelHistory[idx_enemy][_SKM._class];
                        local id_race = SKM_Context.Race.StringToIndex[sRace];
                        local id_class = SKM_Context.Class.StringToIndex[sClass];
                        SKM_Data[RealmName][PlayerName].DuelHistory[idx_enemy][_SKM._race] = id_race;
                        SKM_Data[RealmName][PlayerName].DuelHistory[idx_enemy][_SKM._class] = id_class;
                end
        end

        -- Migration of GuildHistory
        if (SKM_Data[RealmName][PlayerName].GuildHistory) then
                local idx_guild, val_guild;
                for idx_guild, val_guild in SKM_Data[RealmName][PlayerName].GuildHistory do
                        local GuildMigr = {};
                        local idx, val;
                        for idx, val in SKM_IndexMigr[Ver].GuildHistory do
                                if (val_guild[val.Old]) then
                                        GuildMigr[val.New] = val_guild[val.Old];
                                end
                        end

                        SKM_Data[RealmName][PlayerName].GuildHistory[idx_guild] = nil;
                        SKM_Data[RealmName][PlayerName].GuildHistory[idx_guild] = GuildMigr;
                end
        end

        -- Migration of BGStats
        if (SKM_Data[RealmName][PlayerName].BGStats) then
                local idx_zone, val_zone;
                for idx_zone, val_zone in SKM_Data[RealmName][PlayerName].BGStats do
                        local idx_date, val_date;
                        for idx_date, val_date in SKM_Data[RealmName][PlayerName].BGStats[idx_zone] do
                                local StatMigr = {};
                                local idx, val;
                                for idx, val in SKM_IndexMigr[Ver].BGStatDate do
                                        if (val_date[val.Old]) then
                                                StatMigr[val.New] = val_date[val.Old];
                                        end
                                end

                                SKM_Data[RealmName][PlayerName].BGStats[idx_zone][idx_date] = nil;
                                SKM_Data[RealmName][PlayerName].BGStats[idx_zone][idx_date] = StatMigr;
                        end
                end
        end

        -- Migration of GlobalMapData
        if (SKM_Data[RealmName][PlayerName].GlobalMapData) then
                local iNbNotes = table.getn(SKM_Data[RealmName][PlayerName].GlobalMapData);
                local i;
                for i=1, iNbNotes, 1 do
                        local Note = SKM_Data[RealmName][PlayerName].GlobalMapData[i];
                        local NoteMigr = {};
                        local idx, val;
                        for idx, val in SKM_IndexMigr[Ver].GlobalMapData do
                                if (Note[val.Old]) then
                                        NoteMigr[val.New] = Note[val.Old];
                                end
                        end

                        SKM_Data[RealmName][PlayerName].GlobalMapData[i] = nil;

                        SKM_Data[RealmName][PlayerName].GlobalMapData[i] = NoteMigr;

                        local StoredInfo = NoteMigr[_SKM._storedInfo];
                        if (StoredInfo) then
                                local InfoMigr = {};
                                for idx, val in SKM_IndexMigr[Ver].StoredInfo do
                                        if (StoredInfo[val.Old]) then
                                                InfoMigr[val.New] = StoredInfo[val.Old];
                                        end
                                end
                                InfoMigr[_SKM._type] = SKM_IndexMigr[Ver].RecordType[InfoMigr[_SKM._type]];
                                InfoMigr[_SKM._enemyType] = SKM_IndexMigr[Ver].EnemyType[InfoMigr[_SKM._enemyType]];

                                -- fix level if not "level up" event
                                if not (InfoMigr[_SKM._type] == _SKM._levelUp
                                        or InfoMigr[_SKM._type] == _SKM._creatureKill_Target
                                        or InfoMigr[_SKM._type] == _SKM._creatureKill_Xp
                                ) then
                                        InfoMigr[_SKM._level] = nil;
                                end

                                SKM_Data[RealmName][PlayerName].GlobalMapData[i][_SKM._storedInfo] = InfoMigr;
                        end
                end
        end

        -- fix battleground stats bug
        if (SKM_Data[RealmName][PlayerName].BGStats) then
                local idx_bg, val_bg
                for idx_bg, val_bg in SKM_Data[RealmName][PlayerName].BGStats do
                        if not (intable(idx_bg, SKM_BATTLEGROUNDS)) then
                                SKM_Data[RealmName][PlayerName].BGStats[idx_bg] = nil;
                        end
                end
        end

        -- fix wrong "level up" records
        if (SKM_Data[RealmName][PlayerName].GlobalMapData) then
                local i = 1;
                local iMaxLevel = 0;
                local iNbNotes = table.getn(SKM_Data[RealmName][PlayerName].GlobalMapData);
                while (i < iNbNotes) do
                        local Note = SKM_Data[RealmName][PlayerName].GlobalMapData[i];

                        local StoredInfo = Note[_SKM._storedInfo];
                        if (StoredInfo) and (StoredInfo[_SKM._type] == _SKM._levelUp) then

                                if (StoredInfo[_SKM._level] > 1) and (StoredInfo[_SKM._level] > iMaxLevel) then
                                        iMaxLevel = StoredInfo[_SKM._level];
                                        i = i + 1;
                                else
                                        -- remove from GlobalMapData and from MapData
                                        SkM_Trace(FName, 3, "Previous max level = "..iMaxLevel..", Note level up = "..StoredInfo[_SKM._level]);
                                        SkM_Trace(FName, 3, "Removing note for "..PlayerName.." : global index = "..i);

                                        SkM_DeleteNote(RealmName, PlayerName, i);
                                        iNbNotes = iNbNotes - 1;
                                end

                        else
                                i = i + 1;
                        end
                end
        end



end


function SkM_AccountDataMigration(Ver)
        local FName = "SkM_AccountDataMigration";

        local idx_realm, val_realm, idx_char, val_char;
        for idx_realm, val_realm in SKM_Data do
                for idx_char, val_char in SKM_Data[idx_realm] do
                        if (SKM_Data[idx_realm][idx_char].PlayerName == idx_char) then
                                SkM_CharDataMigration(Ver, idx_realm, idx_char);
                        end
                end
        end
end


function SkM_DataModelMigration()
        local FName = "SkM_DataModelMigration";

        local curTime = GetTime();
        local bMigr = false;

        if (SKM_Settings.SavedDataVersion == nil) then
                SKM_Settings.SavedDataVersion = 1;
        end

        if (SKM_Settings.SavedDataVersion < 2) then
                SkM_AccountDataMigration(2);
                SKM_Settings.SavedDataVersion = 2;
                bMigr = true;
        end

        if (bMigr) then
                local endTime = GetTime();
                local elapsedTime = math.floor(100 * (endTime - curTime)) / 100;
                SkM_ChatMessageCol("Data migrated to v."..SKM_Settings.SavedDataVersion..", elapsed time : "..elapsedTime.." s");
        end

end


-- function provided to fix MapData indexes from GlobalMapData (which is supposed to be
-- correct !)
function SkM_FixMapIndexes(RealmName, PlayerName)
        local FName = "SkM_FixMapIndexes";
        local idx_c, val_c, idx_z, val_z, idx_gn;

        SkM_Trace(FName, 3, "Realm = "..snil(RealmName).. " / Player = "..snil(PlayerName));

        -- reinitialize map data
        SKM_Data[RealmName][PlayerName].MapData = { };

        for idx_c, val_c in SKM_Context.Continents do
                SKM_Data[RealmName][PlayerName].MapData[idx_c] = { };

                for idx_z, val_z in SKM_Context.Zones[idx_c] do
                        SKM_Data[RealmName][PlayerName].MapData[idx_c][idx_z] = { };
                end
        end

        -- parse global map data to rebuild local map data
        if (SKM_Data[RealmName][PlayerName].GlobalMapData) then
                for idx_gn, Note in SKM_Data[RealmName][PlayerName].GlobalMapData do
                        idx_c = Note[_SKM._continent];
                        idx_z = Note[_SKM._zone];

                        -- insert new map note
                        table.insert(SKM_Data[RealmName][PlayerName].MapData[idx_c][idx_z], idx_gn);
                end
        end

end

-- function provided to fix MapData indexes from GlobalMapData (which is supposed to be
-- correct !)
function SkM_AccountFixMapIndexes()
        local idx_realm, val_realm, idx_char, val_char;

        for idx_realm, val_realm in SKM_Data do
                for idx_char, val_char in SKM_Data[idx_realm] do
                        if (SKM_Data[idx_realm][idx_char].PlayerName == idx_char) then
                                SkM_FixMapIndexes(idx_realm, idx_char);
                        end
                end
        end
end


-- get date of first record affected by the index bug since 1.4
-- needed to fix german data !
function SkM_GetDateIndexBug(bIgnoreLocale)
        local FName = "SkM_GetDateIndexBug";

        SKM_Context.DateIndexBug = nil;

        local iBug = 0;
        local iOK = 0;

        if not (bIgnoreLocale) then
                if (SKM_CurrentLocale ~= "DE") then
                        SkM_Trace(FName, 1, "Non DE locale");
                        return;
                end
        end

        local idx_realm, val_realm, idx_char, val_char;
        local idx_c, val_c, idx_z, val_z, idx_n, val_n, Note;

        local r_c, r_z, r_n, r_gn, r_realm, r_char;

        for idx_realm, val_realm in SKM_Data do
                for idx_char, val_char in SKM_Data[idx_realm] do
                        if (SKM_Data[idx_realm][idx_char].PlayerName == idx_char) then
                                for idx_c, val_c in SKM_Data[idx_realm][idx_char].MapData do
                                        for idx_z, val_z in SKM_Data[idx_realm][idx_char].MapData[idx_c] do
                                                for idx_n, val_n in SKM_Data[idx_realm][idx_char].MapData[idx_c][idx_z] do
                                                        Note = SKM_Data[idx_realm][idx_char].GlobalMapData[val_n];
                                                        if not ((Note[_SKM._continent] == idx_c) and (Note[_SKM._zone] == idx_z)) then
                                                                -- bad index !
                                                                iBug = iBug + 1;
                                                                if (Note[_SKM._storedInfo]) and (Note[_SKM._storedInfo][_SKM._date]) then

                                                                        if (SKM_Context.DateIndexBug == nil) then
                                                                                SKM_Context.DateIndexBug = Note[_SKM._storedInfo][_SKM._date];
                                                                                r_c = idx_c; r_z = idx_z; r_n = idx_n; r_gn = val_n; r_realm = idx_realm; r_char = idx_char;
                                                                        else
                                                                                local iDiffTime = SkM_DiffDate(SKM_Context.DateIndexBug, Note[_SKM._storedInfo][_SKM._date]);
                                                                                if (iDiffTime ~= nil) and (iDiffTime > 0) then
                                                                                        SKM_Context.DateIndexBug = Note[_SKM._storedInfo][_SKM._date];
                                                                                        r_c = idx_c; r_z = idx_z; r_n = idx_n; r_gn = val_n; r_realm = idx_realm; r_char = idx_char;
                                                                                end
                                                                        end
                                                                end
                                                        else
                                                                iOK = iOK + 1;
                                                        end
                                                end
                                        end
                                end

                        end
                end
        end

        SkM_Trace(FName, 1, "OK = "..iOK.." / Bugged = "..iBug);
        SkM_Trace(FName, 1, "DateIndexBug = ".. snil(SKM_Context.DateIndexBug));
        SkM_Trace(FName, 1, "realm = "..snil(r_realm).." / char = "..snil(r_char));
        SkM_Trace(FName, 1, "cont. = "..snil(r_c).." / zone = "..snil(r_z).." / note = "..snil(r_n).." / gn = "..snil(r_gn));
end


function SkM_DataFixMapIndexes()
        if (SKM_Settings.FixMapIndexes == nil) then
                SkM_GetDateIndexBug();

                --if (SKM_CurrentLocale ~= "EN") then
                        SkM_AccountFixMapIndexes();
                --end
                local sDate = SkM_GetDate();
                SKM_Settings.FixMapIndexes = sDate;
        end
end


-- keep track of history of versions installed.
function SkM_RecordVersionHistory()
        local sLastVer;
        if (SKM_Settings.VersionHistory == nil) then
                SKM_Settings.VersionHistory = {};
        else
                local iCount = table.getn(SKM_Settings.VersionHistory);
                if (iCount > 0) then
                        sLastVer = SKM_Settings.VersionHistory[iCount].Version;
                end
        end
        if (sLastVer ~= SKM_VERSION) then
                local sDate = SkM_GetDate();
                local VersionRecord = {
                        Date = sDate;
                        Version = SKM_VERSION;
                };
                table.insert(SKM_Settings.VersionHistory, VersionRecord);
        end
end



-- nchnch - new
-- --------------------------------------------------------------------------------------



-- --------------------------------------------------------------------------------------
-- SkM_GetGuildChannelName
-- --------------------------------------------------------------------------------------
-- Build a unique channel name from the realm name and the player guild name
-- --------------------------------------------------------------------------------------
function SkM_GetGuildChannel()
        local FName = "SkM_GetGuildChannel";

        local sName;

        --if (not SKM_Context.RealmName) then
        --      SkM_Trace(FName, 1, "Realm not known !");
        --      return nil;
        --end

        local sGuildName = GetGuildInfo(SKM_UNIT_PLAYER);
        if (not sGuildName) then
                SkM_Trace(FName, 2, "Not in a guild");
                return nil;
        end

        sName = SKM_GuildChannelPrefix.." - "..sGuildName;

        SkM_Trace(FName, 3, "Channel name : "..snil(sName));
        return sName;
end


-- --------------------------------------------------------------------------------------
-- SkM_IsNameInGuild
-- --------------------------------------------------------------------------------------

-- Check if given player name is in guild/
-- --------------------------------------------------------------------------------------
function SkM_IsNameInGuild(sName)
        return (        (sName == _PlayerName)
                 or (intable(sName, SKM_Context.GuildList))
        );
end


function SkM_JoinGuildChannel()
        local FName = "SkM_JoinGuildChannel";
        local sChannel = SkM_GetGuildChannel();

        if (not sChannel) then
                SkM_Trace(FName, 1, "Unable to get SKMap guild channel name");
                return false;
        end

        JoinChannelByName(sChannel, nil, nil);

        SKM_Context.GuildChannel = sChannel;
        return true;
end


function SkM_LeaveGuildChannel()
        local FName = "SkM_LeaveGuildChannel";

        if (not SKM_Context.GuildChannel) then
                SkM_Trace(FName, 1, "SKMap guild channel name not defined");
                return;
        end

        LeaveChannelByName(SKM_Context.GuildChannel);
        SKM_Context.GuildChannel = nil;
end




function SkM_GetChannelIndex(sName)
        local FName = "SkM_GetChannelIndex";

        if (not sName) then
                SkM_Trace(FName, 1, "Channel name not specified");
                return nil;
        end

        SkM_Trace(FName, 3, "Looking for index for channel : "..snil(sName));

        local MyChannelList = GetChannelList();

        local idx, val;
        for idx, val in MyChannelList do
                if (val == sName) then
                        SkM_Trace(FName, 3, "Channel number = "..idx);
                        return idx;
                end
        end
        SkM_Trace(FName, 2, "Channel not found");
        return nil;
end


function SkM_SendGuildChannelMessage(sMsg)
        local FName = "SkM_SendGuildChannelMessage";
        local iChannelIndex = SkM_GetChannelIndex(SKM_Context.GuildChannel);

        if (iChannelIndex) then
                SendChatMessage(sMsg, "CHANNEL", nil, iChannelIndex);
        end
end


function SkM_GuildChannelMessage(iCode, ...)
        local FName = "SkM_GuildChannelMessage";
        local sMsg;
        local sParam = "";

        local i;
        for i=1, arg.n do
                local sField = SkM_FormatMessageField(arg[i]);
                sParam = sParam..sField.." ";
        end

        sMsg = string.format("<SKM=%d>%s</SKM>", iCode, sParam);

        SkM_Trace(FName, 3, "Formated message = "..snil(sMsg));

        SkM_SendGuildChannelMessage(sMsg);
end


function SkM_FormatMessageField(sField)
        local sOutput = sField;

        sOutput = string.gsub(sOutput, "\\", "\\\\");
        sOutput = string.gsub(sOutput, "\"", "\\\"");

        return sOutput;
end


function SkM_GetMessageFields(sMsg)
        local FName = "SkM_GetMessageFields";

        local i=1;
        local iLen = string.len(sMsg);
        local iStartField;
        local bSpecial = false;
        local bInField = false;
        local sField = "";
        local Fields = { };

        while (i <= iLen) do
                local sChar = string.sub(sMsg, i, i);

                if (not bInField) then
                        if (sChar == "\"") then
                                bInField = true;
                        elseif (sChar == " ") then
                                -- skip
                        else
                                -- error
                                SkM_Trace(FName, 1, "Unexpected character : '"..sChar.."' at position "..i);
                                return nil;
                        end
                else

                        if (sChar == "\\") then
                                if ( bSpecial) then
                                        sField = sField..sChar;
                                        bSpecial = false;
                                else
                                        bSpecial = true;
                                end
                        else
                                if (sChar == "\"") then
                                        if (bSpecial) then
                                                sField = sField..sChar;
                                                bSpecial = false;
                                        else
                                                table.insert(Fields, sField);
                                                sField = "";
                                                bInField = false;
                                        end
                                else
                                        sField = sField..sChar;
                                end

                        end
                end
                i = i + 1;
        end
        return Fields;
end


function SkM_ProcessChannelMessage(iCode, sValue)
        local FName = "SkM_ProcessChannelMessage";

        local Fields = SkM_GetMessageFields(sValue);

        if (iCode == 1) then
                -- message = update player information
                if (table.getn(Fields) == 4) then
                        local sName = Fields[1];
                        local sRace = Fields[2];

                        local sClass = Fields[3];
                        local sLevel = Fields[4];
                        SkM_Trace(FName, 3, "Player update: Name="..sName..", Race="..sRace..", Class="..sClass..", Lvl="..sLevel);


                end
        else

        end
end


function SkM_HandleChannelMessage(sMsg, sChannel, sSender)
        local FName = "SkM_HandleChannelMessage";

        if (sChannel == SKM_Context.GuildChannel) then
                SkM_Trace(FName, 3, "Message = "..snil(sMsg));


                -- check if sender is a guild member, if not he should not be sending
                -- messages on this channel !

                if (not SkM_IsNameInGuild(sSender)) then
                        SkM_Trace(FName, 2, "Sender : "..snil(sSender)..", not in your guild !");
                else
                        -- now handle the message
                        --for iCode, sValue in string.gfind(sMsg, SKM_Context.Pattern.) do
                        --"<SKM=%d>%s</SKM>"
                        for iCode, sValue in string.gfind(sMsg, "<SKM=([0-9]+)>(.+)</SKM>") do
                                if (sCode and sValue) then
                                        SkM_ProcessChannelMessage(iCode, sValue);
                                end
                        end
                end
        end
end


-- --------------------------------------------------------------------------------------
-- SkM_BuildGuildList
-- --------------------------------------------------------------------------------------
-- Build a list of players currently in the guild.
-- --------------------------------------------------------------------------------------
function SkM_BuildGuildList()
        local FName = "SkM_BuildGuildList";

        SkM_Trace(FName, 2, "Rebuild guild list");

        SKM_Context.GuildList = { };

        local iNumGuildMembers = GetNumGuildMembers();

        local i;
        for i=1, iNumGuildMembers, 1 do
--Usage: name, rank, rankIndex, level, class, zone, group, note, officernote, online = GetGuildRosterInfo(index);
                local sName = GetGuildRosterInfo(i);

                if (sName) then
                        table.insert(SKM_Context.GuildList, sName);
                end
        end

end




function SkM_AddGuildMessageToQueue(sMsg, iPriority)
        local FName = "SkM_AddGuildMessageToQueue";

        if (SKM_Context.QueueSendMessage == nil) then
                SKM_Context.QueueSendMessage = { };
        end

        local iMessagePriority = ifnil(iPriority, 0);

        if (iMessagePriority == 0) then
                table.insert(SKM_Context.QueueSendMessage, { Message = sMsg; Priority = iMessagePriority } );
                return;
        end

        local i;
        local iQueueSize = table.getn(SKM_Context.QueueSendMessage);
        for i=1,iQueueSize,1 do
                if (SKM_Context.QueueSendMessage[i].Priority < iMessagePriority) then
                        table.insert(SKM_Context.QueueSendMessage, { Message = sMsg; Priority = iMessagePriority }, i);
                        return;
                end
        end
        table.insert(SKM_Context.QueueSendMessage, { Message = sMsg; Priority = iMessagePriority } );

end



function SkM_ProcessQueue()
        local FName = "SkM_ProcessQueue";

        --local sDate = SkM_GetDate();
        local sTime = GetTime();
        local bProcess = false;

        if (SKM_Context.QueueLastSentMessage == nil) then
                bProcess = true;
        else
                local iDiffTime = sTime - SKM_Context.QueueLastSentMessage;

                --if (iDiffTime > SkM_GetOption("SendMessageMinDelay")) then
                if (iDiffTime > 1) then
                        bProcess = true;
                end
        end

        if (bProcess) then
                local sMsg = SKM_Context.QueueSendMessage[1].Message;
                table.remove(SKM_Context.QueueSendMessage, 1);

                SkM_SendGuildChannelMessage(sMsg);

                SKM_Context.QueueLastSentMessage = sTime;
        end

end



function SkM_SendWarList()
        local FName = "SkM_SendWarList";
        local idx, val;
        for idx, val in SKM_Data[_RealmName][_PlayerName].EnemyHistory do
        -- nchnch

                if (val[_SKM._atWar]) then
                        -- local sMsg =

                        SkM_AddGuildMessageToQueue(sMsg, 0);
                end

        end

end




-- TEST functions

function SkM_CharCode(sIn)
        --local List = { };
        local sOut = "";
        local i;
        for i=1, string.len(sIn), 1 do
                --List.insert(string.byte(sIn, i));
                sOut = sOut.."\\";
                sOut = sOut..string.byte(sIn, i);
        end
        --return List;
        return sOut;
end

function SkM_TestHonor()
        local FName = "SkM_TestHonor";
        local sMsg = "Gorwald dies, honorable kill Rank: First Sergeant (Estimated Honor Points: 67)";


        for sFoe, sRank in string.gfind(sMsg, SKM_Context.Pattern.Honor_Kill) do
                if (sFoe and sRank) then
                        SkM_Trace(FName, 0, "Honor_Kill : Foe = "..sFoe..", Rank = "..sRank);

                        SkM_Trace(FName, 0, "Enemy player death detected (from chat, honor) : "..snil(sFoe));
                        --SkM_PvpEnemyDeath(sFoe, true, sRank);
                end
        end

end


function SkM_RecMapZones()
        local FName = "SkM_RecMapZones";

        SkM_ClearDebugRecord();
        SkM_SetDebugLevel(1);
        SkM_StartDebugRecord();

        local lv_Continents = { GetMapContinents() } ;
        for idx, val in lv_Continents do
                SkM_Trace(FName, 1, "Continent "..idx.." = "..val);
                local lv_Zones = { GetMapZones(idx) };
                for idx2, val2 in lv_Zones do
                        SkM_Trace(FName, 1, "Cont. "..idx..", Zone "..idx2.." = "..val2);
                end
        end

        SkM_StopDebugRecord();
        SkM_SetDebugLevel(-1);
end



function test()
        local FName = "test";
        local idx, val;
        for idx, val in getfenv() do
                if (type(val) == "string" ) then
                        SkM_Trace(FName, 0, idx.." = "..val);
                end
        end
end

-- OBSOLETE stuff