--[[
    This file is part of Decursive.
    
    Decursive (v 2.5.1-12-gb39554a) add-on for World of Warcraft UI
    Copyright (C) 2006-2007-2008-2009 John Wellesz (archarodim AT teaser.fr) ( http://www.2072productions.com/to/decursive.php )

    Starting from 2009-10-31 and until said otherwise by its author, Decursive
    is no longer free software, all rights are reserved to its author (John Wellesz).

    The only official and allowed distribution means are www.2072productions.com, www.wowace.com and curse.com.
    To distribute Decursive through other means a special authorization is required.
    

    Decursive is inspired from the original "Decursive v1.9.4" by Quu.
    The original "Decursive 1.9.4" is in public domain ( www.quutar.com )

    Decursive is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY.
--]]
-------------------------------------------------------------------------------

local addonName, T = ...;

-- big ugly scary fatal error message display function {{{
if not T._FatalError then
-- the beautiful error popup : {{{ -
StaticPopupDialogs["DECURSIVE_ERROR_FRAME"] = {
    text = "|cFFFF0000Decursive Error:|r\n%s",
    button1 = "OK",
    OnAccept = function()
        return false;
    end,
    timeout = 0,
    whileDead = 1,
    hideOnEscape = 1,
    showAlert = 1,
    }; -- }}}
T._FatalError = function (TheError) StaticPopup_Show ("DECURSIVE_ERROR_FRAME", TheError); end
end
-- }}}

if not T._LoadedFiles or not T._LoadedFiles["Dcr_lists.xml"] or not T._LoadedFiles["Dcr_lists.lua"] then -- XML are loaded even if LUA syntax errors exixts
    if not DecursiveInstallCorrupted then T._FatalError("Decursive installation is corrupted! (Dcr_lists.xml or Dcr_lists.lua not loaded)"); end;
    DecursiveInstallCorrupted = true;
    return;
end

local D   = T.Dcr;


local L     = D.L;
local LC    = D.LC;
local DC    = DcrC;
local DS    = DC.DS;


-- NS def
D.MicroUnitF = {};
-- create a shortcut
local MicroUnitF = D.MicroUnitF;
MicroUnitF.prototype = {};
MicroUnitF.metatable ={ __index = MicroUnitF.prototype };

function MicroUnitF:new(...)
    local instance = setmetatable({}, self.metatable);
    instance:init(...);
    return instance;
end



-- since there are tens of thousands of globals defined at all times, lets use some locals!
local BOOKTYPE_PET      = BOOKTYPE_PET;
local BOOKTYPE_SPELL    = BOOKTYPE_SPELL;

-- Init object factory defaults
--MicroUnitF.ExistingPerID          = {};
MicroUnitF.ExistingPerUNIT          = {};
MicroUnitF.ExistingPerNum           = {};
MicroUnitF.UnitToMUF                = {};
MicroUnitF.Number                   = 0;
MicroUnitF.UnitShown                = 0;
MicroUnitF.UnitsDebuffedInRange     = 0;
MicroUnitF.DraggingHandle           = false;
D.ForLLDebuffedUnitsNum             = 0;


-- using power 2 values just to OR them but only CHARMED_STATUS is ORed (it's a C style bitfield)
local NORMAL                = DC.NORMAL;
local ABSENT                = DC.ABSENT;
local FAR                   = DC.FAR;
local STEALTHED             = DC.STEALTHED;
local BLACKLISTED           = DC.BLACKLISTED;
local AFFLICTED             = DC.AFFLICTED;
local AFFLICTED_NIR         = DC.AFFLICTED_NIR;
local CHARMED_STATUS        = DC.CHARMED_STATUS;
local AFFLICTED_AND_CHARMED = DC.AFFLICTED_AND_CHARMED;


-- Those are the different colors used for the MUFs main texture
local MF_colors = { };

local unpack            = _G.unpack;
local select            = _G.select;
local pairs             = _G.pairs;
local ipairs            = _G.ipairs;
local GetTime           = _G.GetTime;
local PlaySoundFile     = _G.PlaySoundFile;
local IsControlKeyDown  = _G.IsControlKeyDown;
local floor             = _G.math.floor;
local table             = _G.table;
local t_insert          = _G.table.insert;
local str_format        = _G.string.format;
local str_sub           = _G.string.gsub;
local table             = _G.table;
local string            = _G.string;
local UnitExists        = _G.UnitExists;
local UnitClass         = _G.UnitClass;
local fmod              = _G.math.fmod;
local UnitIsUnit        = _G.UnitIsUnit;
local str_upper         = _G.string.upper;
local InCombatLockdown  = _G.InCombatLockdown;
local UnitAura          = _G.UnitAura;
local GetRaidTargetIndex= _G.GetRaidTargetIndex;


DC.AvailableButtonsReadable = { -- {{{
    ["%s1"]         =   L["HLP_LEFTCLICK"], -- left mouse button
    ["%s2"]         =   L["HLP_RIGHTCLICK"], -- right mouse button
    ["%s3"]         =   L["HLP_MIDDLECLICK"], -- middle mouse button
    ["ctrl-%s1"]    =   L["CTRL"]  .. "-" .. L["HLP_LEFTCLICK"],
    ["ctrl-%s2"]    =   L["CTRL"]  .. "-" .. L["HLP_RIGHTCLICK"],
    ["ctrl-%s3"]    =   L["CTRL"]  .. "-" .. L["HLP_MIDDLECLICK"],
    ["shift-%s1"]   =   L["SHIFT"] .. "-" .. L["HLP_LEFTCLICK"],
    ["shift-%s2"]   =   L["SHIFT"] .. "-" .. L["HLP_RIGHTCLICK"],
    ["shift-%s3"]   =   L["SHIFT"] .. "-" .. L["HLP_MIDDLECLICK"],
    ["alt-%s1"]     =   L["ALT"]   .. "-" .. L["HLP_LEFTCLICK"],
    ["alt-%s2"]     =   L["ALT"]   .. "-" .. L["HLP_RIGHTCLICK"],
    ["alt-%s3"]     =   L["ALT"]   .. "-" .. L["HLP_MIDDLECLICK"],
    -- 3, -- middle mouse button || RESERVED FOR TARGETTING
}; -- }}}

-- modifier for the macro
local AvailableModifier = { -- {{{
    "shift","ctrl","alt",
} -- }}}

-- MicroUnitF STATIC methods {{{

-- Updates the color table
function MicroUnitF:RegisterMUFcolors ()
   -- MF_colors = D.profile.MF_colors; -- this should be enough but is not because D.profile can change at unexpected times....
    D:tcopy(MF_colors, D.profile.MF_colors);
end

-- defines what is printed when the object is read as a string
function MicroUnitF:ToString() -- {{{
    return "Decursive Micro Unit Frame Object";
end -- }}}

-- The Factory for MicroUnitF objects
function MicroUnitF:Create(Unit, ID) -- {{{

    if InCombatLockdown() then
        -- if we are fighting, postpone the call
        D:AddDelayedFunctionCall (
        "Create"..Unit, self.Create,
        Unit, ID);
        return false;
    end

    -- if we attempt to create a MUF that already exists, update it instead
    if (self.ExistingPerUNIT[Unit]) then
        return self.ExistingPerUNIT[Unit];
    end

    self.Number = self.Number + 1;

    -- create a new MUF object
    self.ExistingPerUNIT[Unit] = self:new(D.MFContainer, Unit, self.Number, ID);

    self.ExistingPerNum[self.Number] = self.ExistingPerUNIT[Unit];

    return self.ExistingPerUNIT[Unit];
end -- }}}

-- return a MUF object if it exists, nil otherwise
--[=[
function MicroUnitF:Exists(IdOrNum) -- {{{
    return self.ExistingPerID[IdOrNum] or self.ExistingPerNum[IdOrNum];
end
--]=]
-- }}}

-- return the number MUFs we can use
function MicroUnitF:MFUsableNumber () -- {{{
    return ((self.MaxUnit > D.Status.UnitNum) and D.Status.UnitNum or self.MaxUnit);
end -- }}}

-- this is used when a setting influencing MUF's position is changed
function MicroUnitF:ResetAllPositions () -- {{{

    if InCombatLockdown() then
        D:AddDelayedFunctionCall (
        "ResetAllPositions", self.ResetAllPositions,
        self);
        return false;
    end

    local MF, i;

    D:Debug("Resetting all MF position");

    self:Delayed_MFsDisplay_Update ();

    local Unit_Array = D.Status.Unit_Array;

    for i=1, #Unit_Array do
        MF = self.ExistingPerUNIT[ Unit_Array[i] ]

        if MF then
            MF.Frame:SetPoint(unpack(self:GiveMFAnchor(i)));
        end
    end
end -- }}}

-- return the anchor of a given MUF depending on its creation ID
do
local Anchor = { "TOPLEFT", x, y, "TOPLEFT" };
function MicroUnitF:GiveMFAnchor (ID) -- {{{
    local LineNum = floor( (ID - 1) / D.profile.DebuffsFramePerline);
    local NumOnLine = fmod( (ID - 1), D.profile.DebuffsFramePerline);

    local x = (D.profile.DebuffsFrameGrowToTop and DC.MFSIZE or 0) + NumOnLine * (DC.MFSIZE + D.profile.DebuffsFrameXSpacing);
    local y = (D.profile.DebuffsFrameGrowToTop and -1 or 1) * LineNum * ((-1 * D.profile.DebuffsFrameYSpacing) - DC.MFSIZE);

    Anchor[2] = x; Anchor[3] = y;

    return Anchor;
end
end-- }}}


function MicroUnitF:Delayed_MFsDisplay_Update ()
    if D.profile.ShowDebuffsFrame then
        D:ScheduleDelayedCall("Dcr_UpdateMUFsNUM", self.MFsDisplay_Update, 1.5, self);
    end
end

-- This update the MUFs display, show and hide MUFs as necessary
function MicroUnitF:MFsDisplay_Update () -- {{{

    if (not D.profile.ShowDebuffsFrame) then
        return false; -- XXX we will have to call this function again when we show the MUFs
    end

    -- This function cannot do anything if we are fighting
    if InCombatLockdown() then
        -- if we are fighting, postpone the call
        D:AddDelayedFunctionCall (
        "UpdateMicroUnitFrameDisplay", self.MFsDisplay_Update,
        self);
        return false;
    end

    -- Get an up to date unit array if necessary
    D:GetUnitArray(); -- this is the only place where GetUnitArray() is called directly

    -- =======
    --  Begin
    -- =======
   
    -- get the number of MUFs we should display
    local NumToShow = self:MFUsableNumber();


    -- if we don't have all the MUFs needed then return, we are not ready
    if (self.Number < NumToShow) then
        self:Delayed_MFsDisplay_Update ();
        return false;
    end


    local MF = false;
    local i = 1;
    local Old_UnitShown = self.UnitShown;


    D:Debug("Update required: NumToShow = ", NumToShow);

    local Unit_Array_UnitToGUID = D.Status.Unit_Array_UnitToGUID;
    local Unit_Array            = D.Status.Unit_Array;


    -- Scan unit array in display order and show the maximum until NumToShow is reached
    -- The ID is set for all MUFs present in our unit array
    local Updated = 0;
    for i, Unit in ipairs(Unit_Array) do

        MF = self.ExistingPerUNIT[Unit];
        if MF then
            MF.ID = i;

            if not MF.Shown and i <= NumToShow then -- we got this unit in our group but it's hidden

                MF.Shown = true;
                self.UnitShown = self.UnitShown + 1;
                MF.ToPlace = true;
                Updated = Updated + 1;

                D:ScheduleDelayedCall("Dcr_Update"..MF.CurrUnit, MF.UpdateWithCS, D.profile.DebuffsFrameRefreshRate * Updated, MF);
                --D:Debug("|cFF88AA00Show schedule for MUF", Unit, "UnitShown:", self.UnitShown);
            end
        else
            --D:errln("showhide: no muf for", Unit); -- call delay display up 
            self:Delayed_MFsDisplay_Update ();
        end

    end

    -- hide remaining units
    if self.UnitShown > NumToShow then

        for Unit, MF in  pairs(self.ExistingPerUNIT) do -- see all the MUF we ever created and show or hide them if there corresponding unit exists

            -- show/hide
            if MF.Shown and (not Unit_Array_UnitToGUID[Unit] or MF.ID > NumToShow ) then -- we don't have this unit but its MUF is shown

                -- clear debuff before hiding to avoid leaving 'ghosts' behind...
                if D.UnitDebuffed[MF.CurrUnit] then
                    D.ForLLDebuffedUnitsNum = D.ForLLDebuffedUnitsNum - 1;
                end

                MF.Debuffs                      = false;
                MF.IsDebuffed                   = false;
                MF.Debuff1Prio                  = false;
                MF.PrevDebuff1Prio              = false;
                D.UnitDebuffed[MF.CurrUnit]     = false; -- used by the live-list only
                D.Stealthed_Units[MF.CurrUnit]  = false;


                MF.Shown = false;
                self.UnitShown = self.UnitShown - 1;
                --D:Debug("|cFF88AA00Hiding %d (%s), scheduling update in %f|r", i, MF.CurrUnit, D.profile.DebuffsFrameRefreshRate * i);
                Updated = Updated + 1;
                D:ScheduleDelayedCall("Dcr_Update"..MF.CurrUnit, MF.Update, D.profile.DebuffsFrameRefreshRate * Updated, MF);
                MF.Frame:Hide();
            end

        end
    end

    -- manage to get what we show in the screen
    if self.UnitShown > 0 and Old_UnitShown ~= self.UnitShown then
        MicroUnitF:Place();
    end

    return true;
end -- }}}


function MicroUnitF:Delayed_Force_FullUpdate ()
    if (D.profile.ShowDebuffsFrame) then
        D:ScheduleDelayedCall("Dcr_Force_FullUpdate", self.Force_FullUpdate, 0.3, self);
    end
end

function MicroUnitF:Force_FullUpdate () -- {{{
    if (not D.profile.ShowDebuffsFrame) then
        return false;
    end

    -- This function cannot do anything if we are fighting
    if InCombatLockdown() then
        -- if we are fighting, postpone the call
        D:AddDelayedFunctionCall (
        "Force_FullUpdate", self.Force_FullUpdate,
        self);
        return false;
    end

    local MF;--, MF_f;

    D.Status.SpellsChanged = GetTime(); -- will force an update of all MUFs attributes

    local i = 1;
    for Unit, MF in  pairs(self.ExistingPerUNIT) do

        if not MF.IsDebuffed then
            MF.UnitStatus = 0; -- reset status to force SetColor to update
        end

        MF.ChronoFontString:SetTextColor(unpack(MF_colors["COLORCHRONOS"]));

        D:ScheduleDelayedCall("Dcr_Update"..MF.CurrUnit, MF.UpdateWithCS, D.profile.DebuffsFrameRefreshRate * i, MF);
        i = i + 1;
    end
end -- }}}


-- Those set the scalling of the MUF container
-- SACALING FUNCTIONS (MicroUnitF Children) {{{
-- Place the MUFs container according to its scale
function MicroUnitF:Place () -- {{{

    if self.UnitShown == 0 or self.DraggingHandle then return end

    if InCombatLockdown() then
        -- if we are fighting, postpone the call
        D:AddDelayedFunctionCall (
        "MicroUnitFPlace", self.Place,
        self);
        return;
    end


    local UIScale       = UIParent:GetEffectiveScale()
    local FrameScale    = self.Frame:GetEffectiveScale();
    local x, y = D.profile.DebuffsFrame_x, D.profile.DebuffsFrame_y;

    -- If executed for the very first time, then put it in the top right corner of the screen
    if (not x or not y) then
        x =    (UIParent:GetWidth() * UIScale) - (UIParent:GetWidth() * UIScale) / 4;
        y =  - (UIParent:GetHeight() * UIScale) / 5;

        D.profile.DebuffsFrame_x = x;
        D.profile.DebuffsFrame_y = y;
    end


    local FirstLineNum    = 0;
    local Handle_x_offset = 0;
    local Handle_y_offset = 0;

    -- get the number of max unit per line
    if self.UnitShown >= D.profile.DebuffsFramePerline then FirstLineNum = D.profile.DebuffsFramePerline; else FirstLineNum = self.UnitShown; end

    -- get the offset of the handle we need to apply in order to align the MUFs on the right
    if D.profile.DebuffsFrameStickToRight then
        Handle_x_offset = -1 * FrameScale * (FirstLineNum * (DC.MFSIZE + D.profile.DebuffsFrameXSpacing) - D.profile.DebuffsFrameXSpacing );
    end

    Handle_x_offset = Handle_x_offset - FrameScale * (D.profile.DebuffsFrameGrowToTop and DC.MFSIZE or 0);
    Handle_y_offset = Handle_y_offset + FrameScale * (D.profile.DebuffsFrameGrowToTop and DC.MFSIZE or 0);

    -- check the right edge so it can't be out of the screen
    local RightEdge = x + FrameScale * (FirstLineNum * (DC.MFSIZE + D.profile.DebuffsFrameXSpacing) - D.profile.DebuffsFrameXSpacing + (D.profile.DebuffsFrameGrowToTop and DC.MFSIZE or 0)) + Handle_x_offset ;

    if (RightEdge > UIParent:GetWidth() * UIScale) then
        Handle_x_offset = Handle_x_offset - (RightEdge - UIParent:GetWidth() * UIScale);
        D:Debug("put the MUFs on the screen!!! (Right edge out)");
    end

    -- check the left edge so it can't be out of the screen
    local LeftEdge = x + Handle_x_offset;
    if (LeftEdge < 0) then
        Handle_x_offset = Handle_x_offset - LeftEdge;
        D:Debug("put the MUFs on the screen!!! (Left edge out)");
    end

    -- check the bottom edge
    local NumberOfLines = floor( (self.UnitShown - 1) / D.profile.DebuffsFramePerline) + 1;
    --D:Debug(NumberOfLines);
    
    local BottomEdge = y - FrameScale * ((D.profile.DebuffsFrameGrowToTop and 1 or NumberOfLines) * (DC.MFSIZE + D.profile.DebuffsFrameYSpacing) - D.profile.DebuffsFrameYSpacing) + Handle_y_offset;
    
    if -BottomEdge > UIParent:GetHeight() * UIScale then
        Handle_y_offset = Handle_y_offset - (BottomEdge + UIParent:GetHeight() * UIScale);
        D:Debug("put the MUFs on the screen!!! (Bottom edge out)");
    end

    -- check the top edge
    local TopEdge = y + FrameScale * (((D.profile.DebuffsFrameGrowToTop and NumberOfLines > 1) and NumberOfLines - 1 or 1) * (DC.MFSIZE + D.profile.DebuffsFrameYSpacing) - ((not D.profile.DebuffsFrameGrowToTop) and 1 or 0) * D.profile.DebuffsFrameYSpacing) + Handle_y_offset;
     if (TopEdge > 0) then
        Handle_y_offset = Handle_y_offset - TopEdge;
        D:Debug("put the MUFs on the screen!!! (Top edge out)");
    end

    x = x + Handle_x_offset;
    y = y + Handle_y_offset;

   

    -- set to the scaled position
    self.Frame:ClearAllPoints();
    self.Frame:SetPoint("TOPLEFT", UIParent, "TOPLEFT", x/FrameScale , y/FrameScale);
    D:Debug("MUF Window position set");
end -- }}}

-- Save the position of the frame without its scale
function MicroUnitF:SavePos () -- {{{

    if self.UnitShown == 0 then return end

    local FirstLineNum;

    if self.Frame:IsVisible() then
        -- We save the unscalled position (no problem if the sacale is changed behind our back)
        D.profile.DebuffsFrame_x = self.Frame:GetEffectiveScale() * self.Frame:GetLeft();
        D.profile.DebuffsFrame_y = self.Frame:GetEffectiveScale() * self.Frame:GetTop() - UIParent:GetHeight() * UIParent:GetEffectiveScale();


        -- if we choosed to align the MUF to the right then we have to add the
        -- width of the first line to get the original position of the handle
        
        if D.profile.DebuffsFrameStickToRight then

            if self.UnitShown >= D.profile.DebuffsFramePerline then
                FirstLineNum = D.profile.DebuffsFramePerline;
            else
                FirstLineNum = self.UnitShown;
            end

            D.profile.DebuffsFrame_x = D.profile.DebuffsFrame_x + self.Frame:GetEffectiveScale() * (FirstLineNum * (DC.MFSIZE + D.profile.DebuffsFrameXSpacing) - D.profile.DebuffsFrameXSpacing);
        end
        
        D.profile.DebuffsFrame_x = D.profile.DebuffsFrame_x + self.Frame:GetEffectiveScale() * (D.profile.DebuffsFrameGrowToTop and DC.MFSIZE or 0);
        D.profile.DebuffsFrame_y = D.profile.DebuffsFrame_y - self.Frame:GetEffectiveScale() * (D.profile.DebuffsFrameGrowToTop and DC.MFSIZE or 0);

        --      D:Debug("Frame position saved");
    end

end -- }}}

-- set the scaling of the MUFs container according to the user settings
function MicroUnitF:SetScale (NewScale) -- {{{
    
    -- save the current position without any scaling
--    self:SavePos ();
    -- Setting the new scale
    self.Frame:SetScale(NewScale);
    -- Place the frame adapting its position to the news cale
    self:Place ();
    
end -- }}}
-- }}}

-- Update the MUF of a given unitid
function MicroUnitF:UpdateMUFUnit(Unitid, CheckStealth)
    if not D.profile.ShowDebuffsFrame then
        return;
    end

    local unit = false;

    if (D.Status.Unit_Array_UnitToGUID[Unitid]) then
        unit = Unitid;
    else
        D:Debug("Unit", Unitid, "not in raid or party!" );
        return;
    end

    -- get the MUF object
    local MF = self.UnitToMUF[unit];

    if (MF and MF.Shown) then
        -- The MUF will be updated only every DebuffsFrameRefreshRate seconds at most
        -- but we don't miss any event XXX note this can be the cause of slowdown if 25 or 40 players got debuffed at the same instant, DebuffUpdateRequest is here to prevent that since 2008-02-17
        if (not D:DelayedCallExixts("Dcr_Update"..unit)) then
            D.DebuffUpdateRequest = D.DebuffUpdateRequest + 1;
            D:ScheduleDelayedCall("Dcr_Update"..unit, CheckStealth and MF.UpdateWithCS or MF.Update, D.profile.DebuffsFrameRefreshRate * (1 + floor(D.DebuffUpdateRequest / (D.profile.DebuffsFramePerUPdate / 2))), MF);
            D:Debug("Update scheduled for, ", unit, MF.ID);

            return true; -- return value used to aknowledge that the function actually did something
        end
    else
        D:Debug("No MUF found for ", unit, Unitid);
    end
end

-- Event management functions
-- MUF EVENTS (MicroUnitF children) (OnEnter, OnLeave, OnLoad, OnPreClick) {{{
-- It's outside the function to avoid creating and discarding this table at each call
local DefaultTTAnchor = {"ANCHOR_TOPLEFT", 0, 6};
local UnitGUID = _G.UnitGUID;
local TooltipButtonsInfo = {}; -- help tooltip text table
local TooltipUpdate = 0; -- help tooltip change update check
-- This function is responsible for showing the tooltip when the mouse pointer is over a MUF
-- it also handles Unstable Affliction detection and warning.
function MicroUnitF:OnEnter(frame) -- {{{
    D.Status.MouseOveringMUF = true;

    local MF = frame.Object;
    local Status;

    local Unit = MF.CurrUnit; -- shortcut
    local TooltipText = "";


    local GUIDwasFixed = false;
    local unitguid = UnitGUID(Unit);

    if unitguid ~= D.Status.Unit_Array_UnitToGUID[Unit] or Unit ~= D.Status.Unit_Array_GUIDToUnit[unitguid] then

        if unitguid then
            D.Status.Unit_Array_UnitToGUID[Unit] = unitguid;
            D.Status.Unit_Array_GUIDToUnit[unitguid] = Unit;
            GUIDwasFixed = true;
        end

    end

    MF:Update(false, false, true); -- will reset the color early and set the current status of the MUF
    MF:SetClassBorder(); -- set the border if it wasn't possible at the time the unit was discovered
    
    if not Unit then
        return; -- If the user overs the MUF befor it's completely initialized
    end

    --Test for unstable affliction
    if MF.Debuffs then
        for i, Debuff in ipairs(MF.Debuffs) do
            if Debuff.Type then
                -- Create a warning if the Unstable Affliction is detected
                if Debuff.Name == DS["Unstable Affliction"] then
                    --if Debuff.Name == "Malédiction de Stalvan" then -- to test easily
                    D:Println("|cFFFF0000 ==> %s !!|r (%s)", DS["Unstable Affliction"], D:MakePlayerName((D:PetUnitName(      Unit, true    ))));
                    PlaySoundFile("Sound\\Doodad\\G_NecropolisWound.wav");
                end
            end
        end
    end

    if D.profile.AfflictionTooltips then

        -- removes the CHARMED_STATUS bit from Status, we don't need it
        Status = bit.band(MF.UnitStatus,  bit.bnot(CHARMED_STATUS));

        -- First, write the name of the unit in its class color
        if UnitExists(MF.CurrUnit) then
            TooltipText =
            ((DC.RAID_ICON_LIST[GetRaidTargetIndex(Unit)]) and (DC.RAID_ICON_LIST[GetRaidTargetIndex(Unit)] .. "0:0:0:0|t ") or ""  ) ..
            -- Colored unit name
            D:ColorText(            (D:PetUnitName(       Unit, true    ))
            , "FF" .. ((UnitClass(Unit)) and DC.HexClassColor[ (select(2, UnitClass(Unit))) ] or "AAAAAA")) .. "  |cFF3F3F3F(".. Unit .. ")|r";
        else
            TooltipText = MF.CurrUnit;
        end


        -- set UnitStatus text
        local StatusText = "";

        -- set the status text, just translate the bitfield to readable text
        if Status == NORMAL then
            StatusText = L["NORMAL"];

        elseif Status == ABSENT then
            StatusText = str_format(L["ABSENT"], Unit);

        elseif Status == FAR then
            StatusText = L["TOOFAR"];

        elseif Status == STEALTHED then
            StatusText = L["STEALTHED"];

        elseif Status == BLACKLISTED then
            StatusText = L["BLACKLISTED"];

        elseif MF.Debuffs and (Status == AFFLICTED or Status == AFFLICTED_NIR) then
            local DebuffType = MF.Debuffs[1].Type;
            StatusText = str_format(L["AFFLICTEDBY"], D:ColorText( L[str_upper(DC.TypeNames[DebuffType])], "FF" .. DC.TypeColors[DebuffType]) );
        end

        -- Unit Status
        --TooltipText = TooltipText .. "\n" .. L["UNITSTATUS"] .. StatusText .. "\n";
        TooltipText = TooltipText .. "\n" .. StatusText;

        -- list the debuff(s) names
        if MF.Debuffs then
            for i, Debuff in ipairs(MF.Debuffs) do
                if Debuff.Type then
                    local DebuffApps = Debuff.Applications;
                    TooltipText = TooltipText .. "\n" .. str_format("%s", D:ColorText(Debuff.Name, "FF" .. DC.TypeColors[Debuff.Type])) .. (DebuffApps>0 and str_format(" (%d)", DebuffApps) or "");
                end
            end
        end

        local VerticalMUF    = floor((self.UnitShown - 1) / D.profile.DebuffsFramePerline ) * D.profile.DebuffsFramePerline + 1;

        -- The tooltip is anchored above the top first MUF
        if not D.profile.DebuffsFrameGrowToTop then
            local FirstMUFAnchor = self:GiveMFAnchor(1);
            DefaultTTAnchor[2] = FirstMUFAnchor[2];
            DefaultTTAnchor[3] = FirstMUFAnchor[3];
        else
            local TopMUFAnchor   = self:GiveMFAnchor(VerticalMUF);
            DefaultTTAnchor[2] = TopMUFAnchor[2];
            DefaultTTAnchor[3] = TopMUFAnchor[3];
        end

        -- Display the tooltip
        D:DisplayTooltip( TooltipText , self.Frame, DefaultTTAnchor);

        -- if the tooltip is at the top of the screen it means it's overlaping the MUF, let's move the tooltip somewhere else.
        if floor(DcrDisplay_Tooltip:GetTop()) == floor(UIParent:GetTop()) then
            local RefMUF = 1;

            if not D.profile.DebuffsFrameGrowToTop then
               RefMUF = VerticalMUF;
            end

            DcrDisplay_Tooltip:ClearAllPoints();
            DcrDisplay_Tooltip:SetPoint("TOPLEFT", self.ExistingPerNum[RefMUF].Frame, "BOTTOMLEFT");
        end
    end

    -- show a help text in the Game default tooltip
    if D.profile.DebuffsFrameShowHelp then
        -- if necessary we will update the help tooltip text
        if (D.Status.SpellsChanged ~= TooltipUpdate) then
            TooltipButtonsInfo = {};
            local AvailableButtons = D.db.global.AvailableButtons;

            for Spell, Prio in pairs(D.Status.CuringSpellsPrio) do
                TooltipButtonsInfo[Prio] =
                str_format("%s: %s", D:ColorText(DC.AvailableButtonsReadable[AvailableButtons[Prio]], D:NumToHexColor(MF_colors[Prio])), Spell);
            end

            t_insert(TooltipButtonsInfo, str_format("%s: %s", DC.AvailableButtonsReadable[AvailableButtons[#AvailableButtons - 1]], L["TARGETUNIT"]));
            t_insert(TooltipButtonsInfo, str_format("%s: %s", DC.AvailableButtonsReadable[AvailableButtons[#AvailableButtons    ]], L["FOCUSUNIT"]));
            TooltipButtonsInfo = table.concat(TooltipButtonsInfo, "\n");
            TooltipUpdate = D.Status.SpellsChanged;
        end

        GameTooltip_SetDefaultAnchor(GameTooltip, frame);
        GameTooltip:SetText(TooltipButtonsInfo);
        GameTooltip:Show();

    end

end -- }}}

-- No longer used
--[===[
function MicroUnitF:LateAnalysis(From, Debuffs, MF, Status, GUIDwasFixed)

    local Unit = MF.CurrUnit; -- shortcut
    local unitguid = UnitGUID(Unit);
    local RegisteredUnitguid = MF.UnitGUID;
    -- search for detection by combat event manager
    local foundcblevents = {}; local highestever = 0; local _; local i=1; local dname; local DebuffApplyTime = false;
    local debuffname = Debuffs and Debuffs[1].Name or "No debuff, charmed?";

    -- find the debuff on the unit and find out when it was applyed
    while 1 do
        dname = UnitAura(Unit, i, "HARMFUL");

        if not dname then break end;

        if dname == debuffname then
            local Dduration, DexpireTime;
            dname, _, _, _, _, Dduration, DexpireTime = UnitAura(Unit, i, "HARMFUL");
            DebuffApplyTime = DexpireTime - Dduration;
            break;
        end
        i = i + 1;
    end


    if not DebuffApplyTime or DebuffApplyTime == 0 then
        D:AddDebugText("DebuffApplyTime could not be found for ", dname, "on", Unit, "DebuffApplyTime set to now - 3s");
        DebuffApplyTime = GetTime() - 3;
    end

    local DetectHistoryIndex = 1;
    local halfrange = 5; local RangeMatch = false; local latestever = "none"; local latesttime = 0;
    local tconcat = _G.table.concat
    D:Debug("Looking for events on", Unit, "between", DebuffApplyTime - halfrange, "and", DebuffApplyTime + halfrange);
    -- look in the history of the combat log event handler
    while D.DetectHistory[DetectHistoryIndex] do
        -- take all the events related to this unit or debuffname around the time the missed debuff was applyed
        if D.DetectHistory[DetectHistoryIndex][8] == unitguid or D.DetectHistory[DetectHistoryIndex][12] == debuffname then

            if D.DetectHistory[DetectHistoryIndex][1] > DebuffApplyTime - halfrange and D.DetectHistory[DetectHistoryIndex][1] < DebuffApplyTime + halfrange then
                t_insert(foundcblevents, tconcat(D.DetectHistory[DetectHistoryIndex],", "));
                t_insert(foundcblevents, "\n");
                RangeMatch = true;
            elseif D.DetectHistory[DetectHistoryIndex][1] > latesttime then-- find the latest event concerning this unit
                latesttime = D.DetectHistory[DetectHistoryIndex][1];
                latestever = DetectHistoryIndex;
            end

        end

        DetectHistoryIndex = DetectHistoryIndex + 1;
    end

    if latestever ~= "none" then
        latestever = tconcat(D.DetectHistory[latestever],", ");
    end

    D:AddDebugText("Debuff late detection:", From, debuffname, "Type:", Debuffs[1].TypeName, "on unit:", Unit, unitguid, "_AppT_:", DebuffApplyTime, "DFRR:", D.profile.DebuffsFrameRefreshRate, "Status:", Status, "DT:", GetTime(), "LGU:", D.Status.GroupUpdatedOn, "LGuEr", D.Status.GroupUpdateEvent, "JFGUID:", GUIDwasFixed, "DbUreq:", D.DebuffUpdateRequest, "MFGuid~:", RegisteredUnitguid ~= unitguid, "Z:", GetZoneText(), "DTI:", DetectHistoryIndex);

    -- trigger a dcr diag if DetectHistoryIndex is 1 :/
    if DetectHistoryIndex == 1 then
        T._SelfDiagnostic(true, true);
    end

    if #foundcblevents == 0 then
        D:AddDebugText("No event in range at all for ", Unit, "or debuff:", debuffname, "latest found:", latestever);
    else
        D:AddDebugText(#foundcblevents / 2, "events for ", Unit, "or debuff:", debuffname, "Status:", Status, "Events:\n", unpack(foundcblevents));
    end
end
--]===]

function MicroUnitF:OnLeave() -- {{{
    D.Status.MouseOveringMUF = false;
    --D:Debug("Micro unit Hidden");
    DcrDisplay_Tooltip:Hide();

    if (D.profile.DebuffsFrameShowHelp) then
        GameTooltip:Hide();
    end
end -- }}}


function D.MicroUnitF:OnCornerEnter(frame)
    if (D.profile.DebuffsFrameShowHelp) then
        D:DisplayGameTooltip(frame,
        str_format(
        "|cFF11FF11%s|r-|cFF11FF11%s|r: %s\n\n"..
        --"|cFF11FF11%s|r: %s\n"..
        "|cFF11FF11%s|r-|cFF11FF11%s|r: %s\n\n"..
        "|cFF11FF11%s|r-|cFF11FF11%s|r: %s\n"..
        "|cFF11FF11%s|r-|cFF11FF11%s|r: %s\n\n"..
        "|cFF11FF11%s|r-|cFF11FF11%s|r: %s",

        D.L["ALT"],             D.L["HLP_LEFTCLICK"],   D.L["HANDLEHELP"],

        --D.L["HLP_RIGHTCLICK"],  D.L["STR_OPTIONS"],
        D.L["ALT"],             D.L["HLP_RIGHTCLICK"],  D.L["BINDING_NAME_DCRSHOWOPTION"],

        D.L["CTRL"],            D.L["HLP_LEFTCLICK"],   D.L["BINDING_NAME_DCRPRSHOW"], 
        D.L["SHIFT"],           D.L["HLP_LEFTCLICK"],   D.L["BINDING_NAME_DCRSKSHOW"],

        D.L["SHIFT"],           D.L["HLP_RIGHTCLICK"],  D.L["BINDING_NAME_DCRSHOW"]
        ));
    end;
end


function MicroUnitF:OnLoad(frame) -- {{{
    frame:SetScript("PreClick", self.OnPreClick);
    frame:SetScript("PostClick", self.OnPostClick);
end
-- }}}

function MicroUnitF.OnPreClick(frame, Button) -- {{{
        D:Debug("Micro unit Preclicked: ", Button);

        local Unit = frame.Object.CurrUnit; -- shortcut

        if (frame.Object.UnitStatus == NORMAL and (Button == "LeftButton" or Button == "RightButton")) then

            D:Println(L["HLP_NOTHINGTOCURE"]);

        elseif (frame.Object.UnitStatus == AFFLICTED) then
            local NeededPrio = D:GiveSpellPrioNum(frame.Object.Debuffs[1].Type);
            local RequestedPrio = false;
            local ButtonsString = "";

            if IsControlKeyDown() then
                ButtonsString = "ctrl-";
            elseif IsAltKeyDown() then
                ButtonsString = "alt-";
            elseif IsShiftKeyDown() then
                ButtonsString = "shift-";
            end

            if Button == "LeftButton" then
               ButtonsString = ButtonsString .. "%s1";
            elseif Button == "RightButton" then
               ButtonsString = ButtonsString .. "%s2";
            end

            RequestedPrio = D:tGiveValueIndex(D.db.global.AvailableButtons, ButtonsString);

            if RequestedPrio and NeededPrio ~= RequestedPrio then
                D:errln(L["HLP_WRONGMBUTTON"]);
                if NeededPrio and MF_colors[NeededPrio] then
                    D:Println(L["HLP_USEXBUTTONTOCURE"], D:ColorText(DC.AvailableButtonsReadable[ D.db.global.AvailableButtons[NeededPrio] ], D:NumToHexColor(MF_colors[NeededPrio])));
                --[===[@debug@
                else
                    D:AddDebugText("Button wrong click info bug: NeededPrio:", NeededPrio, "Unit:", Unit, "RequestedPrio:", RequestedPrio, "Button clicked:", Button, "MF_colors:", unpack(MF_colors), "Debuff Type:", frame.Object.Debuffs[1].Type);
                --@end-debug@]===]
                end


            elseif RequestedPrio and D.Status.HasSpell then
--              D:Print("XXX ClickedMF SET");
                D.Status.ClickedMF = frame.Object; -- used to update the MUF on cast success and failure to know which unit is being cured
                D.Status.ClickedMF.SPELL_CAST_SUCCESS = false;
                D:Debuff_History_Add(frame.Object.Debuffs[1].Name, frame.Object.Debuffs[1].TypeName);
            end
        end
end -- }}}

-- }}}

-- }}}



-- MicroUnitF NON STATIC METHODS {{{
-- init a new micro frame (Call internally by :new() only)
function MicroUnitF.prototype:init(Container, Unit, FrameNum, ID) -- {{{

        D:Debug("Initializing MicroUnit object", Unit, "with FrameNum=", FrameNum, " and ID", ID);


        -- set object default variables
        self.Parent             = Container;
        self.ID                 = ID; -- is set by te roaming updater
        self.FrameNum           = FrameNum;
        self.ToPlace            = true;
        self.Debuffs            = false;
        self.Debuff1Prio        = false;
        self.PrevDebuff1Prio    = false;
        self.IsDebuffed         = false;
        self.CurrUnit           = false;
        self.UnitName           = false;
        self.UnitGUID           = false;
        self.UnitClass          = false;
        self.UnitStatus         = 0;
        self.FirstDebuffType    = 0;
        self.NormalAlpha        = false;
        self.BorderAlpha        = false;
        self.Color              = {};
        self.IsCharmed          = false;
        self.UpdateCountDown    = 3;
        self.LastAttribUpdate   = 0;
        self.LitTime            = false;
        self.Chrono             = false;
        self.PrevChrono         = false;
        self.Shown              = false; -- Setting this to true will broke the stick to right option
        self.UpdateCD           = 0;
        self.RaidTargetIcon     = false;
        self.PrevRaidTargetIndex= false;

        -- create the frame
        self.Frame  = CreateFrame ("Button", "DcrMicroUnit"..Unit, self.Parent, "DcrMicroUnitTemplateSecure");
        self.CooldownFrame = CreateFrame ("Cooldown", "DcrMicroUnitCD"..Unit, self.Frame, "DcrMicroUnitCDTemplate");

        -- outer texture (the class border)
        -- Bottom side
        self.OuterTexture1 = self.Frame:CreateTexture("DcrMicroUnit"..Unit.."Bottom", "BORDER");
        self.OuterTexture1:SetPoint("BOTTOMLEFT", self.Frame, "BOTTOMLEFT", 0, 0);
        self.OuterTexture1:SetPoint("TOPRIGHT", self.Frame, "BOTTOMRIGHT",  0, 2);

        -- left side
        self.OuterTexture2 = self.Frame:CreateTexture("DcrMicroUnit"..Unit.."Left", "BORDER");
        self.OuterTexture2:SetPoint("TOPLEFT", self.Frame, "TOPLEFT", 0, -2);
        self.OuterTexture2:SetPoint("BOTTOMRIGHT", self.Frame, "BOTTOMLEFT", 2, 2);

        -- top side
        self.OuterTexture3 = self.Frame:CreateTexture("DcrMicroUnit"..Unit.."Top", "BORDER");
        self.OuterTexture3:SetPoint("TOPLEFT", self.Frame, "TOPLEFT", 0, 0);
        self.OuterTexture3:SetPoint("BOTTOMRIGHT", self.Frame, "TOPRIGHT", 0, -2);

        -- right side
        self.OuterTexture4 = self.Frame:CreateTexture("DcrMicroUnit"..Unit.."Right", "BORDER");
        self.OuterTexture4:SetPoint("TOPRIGHT", self.Frame, "TOPRIGHT", 0, -2);
        self.OuterTexture4:SetPoint("BOTTOMLEFT", self.Frame, "BOTTOMRIGHT", -2, 2);


        -- global texture
        self.Texture = self.Frame:CreateTexture("DcrMicroUnit"..Unit.."Back", "ARTWORK");
        self.Texture:SetPoint("CENTER",self.Frame ,"CENTER",0,0)
        self.Texture:SetHeight(16);
        self.Texture:SetWidth(16);

        -- inner Texture (Charmed special texture)
        self.InnerTexture = self.Frame:CreateTexture("DcrMicroUnit"..Unit.."Charmed", "OVERLAY");
        self.InnerTexture:SetPoint("CENTER",self.Frame ,"CENTER",0,0)
        self.InnerTexture:SetHeight(7);
        self.InnerTexture:SetWidth(7);
        self.InnerTexture:SetTexture(unpack(MF_colors[CHARMED_STATUS]));

        -- Chrono Font string
        self.ChronoFontString = self.Frame:CreateFontString("DcrMicroUnit"..Unit.."Chrono", "ARTWORK", "DcrMicroUnitChronoFont");
        self.ChronoFontString:SetTextColor(unpack(MF_colors["COLORCHRONOS"]));

        -- raid target icon
        self.RaidIconTexture = self.Frame:CreateTexture("DcrMicroUnit"..Unit.."RaidTargetIcon", "OVERLAY");
        self.RaidIconTexture:SetPoint("CENTER",self.Frame ,"CENTER",0,8)
        self.RaidIconTexture:SetHeight(13);
        self.RaidIconTexture:SetWidth(13);


        -- a reference to this object
        self.Frame.Object = self;

        -- register events
        self.Frame:RegisterForClicks("AnyUp");
        self.Frame:SetFrameStrata("MEDIUM");

        -- set the frame attributes
        self:UpdateAttributes(Unit);

        -- once the MF frame is set up, schedule an event to show it
        MicroUnitF:Delayed_MFsDisplay_Update();
end -- }}}


function MicroUnitF.prototype:Update(SkipSetColor, SkipDebuffs, CheckStealth)

    local MF = self;
    local ActionsDone = 0;

    local Unit = MF.CurrUnit;

    -- The unit is the same but the name isn't... (check for class change)
    if MF.CurrUnit == Unit and D.Status.Unit_Array_UnitToGUID[self.CurrUnit] ~= self.UnitGUID then
        if MF:SetClassBorder() then
            ActionsDone = ActionsDone + 1; -- count expensive things done
        end
        -- if the guid changed we really need to rescan the unit!
        SkipSetColor = false; SkipDebuffs = false; CheckStealth = true;
        --[===[@debug@
        D:Debug("|cFF00CC00MUF:Update(): Guid change rescanning", Unit, "|r");
        --@end-debug@]===]
    end

    -- Update the frame attributes if necessary (Spells priority or unit id changes)
    if (D.Status.SpellsChanged ~= MF.LastAttribUpdate ) then
        --D:Debug("Attributes update required: ", MF.ID);
        if (MF:UpdateAttributes(Unit, true)) then
            ActionsDone = ActionsDone + 1; -- count expensive things done
            SkipSetColor = false; SkipDebuffs = false; -- if some attributes were updated then update the rest
        end
    end


    if (not SkipSetColor) then
        if (not SkipDebuffs) then
            -- get the manageable debuffs of this unit
            MF:SetDebuffs();
            D:Debug("Debuff set for ", MF.ID);
            if CheckStealth then
                D.Stealthed_Units[MF.CurrUnit] = D:CheckUnitStealth(MF.CurrUnit); -- update stealth status
--              D:Debug("MF:Update(): Stealth status checked as requested.");
            end
        end

        if (MF:SetColor()) then
            ActionsDone = ActionsDone + 1; -- count expensive things done
        end
    end

    return ActionsDone;
end


function MicroUnitF.prototype:UpdateWithCS()
    self:Update(false, false, true);
end

function MicroUnitF.prototype:UpdateSkippingSetBuf()
    self:Update(false, true);
end

-- UPDATE attributes (Spells and Unit) {{{



do
    -- used to tell if we changed something to improve performances.
    -- Each attribute change trigger an event...
    local ReturnValue = false;
    -- this updates the sttributes of a MUF's frame object
    function MicroUnitF.prototype:UpdateAttributes(Unit, DoNotDelay)

        -- Delay the call if we are fighting
        if InCombatLockdown() then
            if not DoNotDelay then
                D:AddDelayedFunctionCall (
                "MicroUnit_" .. Unit,                   -- UID
                self.UpdateAttributes, self, Unit);     -- function call
            end
            return false;
        end

        ReturnValue = false;

        -- if the unit is not set
        if not self.CurrUnit then
            self.Frame:SetAttribute("unit", Unit);

            -- UnitToMUF[] can only be set when out of fight so it remains
            -- coherent with what is displayed when groups are changed during a
            -- fight
            
            MicroUnitF.UnitToMUF[Unit] = self;
            self.CurrUnit = Unit;

            self:SetClassBorder();

            -- set the return value because we did something expensive
            ReturnValue = self;
        end

        if (D.Status.SpellsChanged == self.LastAttribUpdate) then
            return ReturnValue; -- nothing changed
        end

        -- D:Debug("UpdateAttributes() executed");

        if self.LastAttribUpdate == 0 then -- only once
            -- set the mouse left-button actions on all modifiers
            self.Frame:SetAttribute("type1", "macro");
            self.Frame:SetAttribute("ctrl-type1", "macro");
            self.Frame:SetAttribute("alt-type1", "macro");
            self.Frame:SetAttribute("shift-type1", "macro");

            -- set the mouse right-button actions on all modifiers
            self.Frame:SetAttribute("type2", "macro");
            self.Frame:SetAttribute("ctrl-type2", "macro");
            self.Frame:SetAttribute("alt-type2", "macro");
            self.Frame:SetAttribute("shift-type2", "macro");

            -- set the mouse middle-button actions on all modifiers
            self.Frame:SetAttribute("type3", "macro");
            self.Frame:SetAttribute("ctrl-type3", "macro");
            self.Frame:SetAttribute("alt-type3", "macro");
            self.Frame:SetAttribute("shift-type3", "macro");
        end

        local AvailableButtons = D.db.global.AvailableButtons;

        self.Frame:SetAttribute(str_format(AvailableButtons[#AvailableButtons - 1], "macrotext"), str_format("/target %s", Unit));
        self.Frame:SetAttribute(str_format(AvailableButtons[#AvailableButtons    ], "macrotext"), str_format("/focus %s", Unit));

        -- set the spells attributes using the lookup tables above
        for Spell, Prio in pairs(D.Status.CuringSpellsPrio) do

            --the [target=%s, help][target=%s, harm] prevents the 'please select a unit' cursor problem (Blizzard should fix this...)
            self.Frame:SetAttribute(str_format(AvailableButtons[Prio], "macrotext"), str_format("%s/cast [target=%s, help][target=%s, harm] %s%s",
            ((not D.Status.FoundSpells[Spell][1]) and "/stopcasting\n" or ""),
            Unit,Unit,
            Spell,
            (DC.SpellsToUse[Spell].Rank and "(" .. (str_sub(DC.RANKNUMTRANS, '%d+', DC.SpellsToUse[Spell].Rank)) .. ")" or "")  ));


            --[[
            D:Debug("XX-> macro: ",str_format(AvailableButtons[Prio], "macrotext"), str_format("%s/cast [target=%s, help][target=%s, harm] %s%s",
            ((not D.Status.FoundSpells[Spell][1]) and "/stopcasting\n" or ""),
            Unit,Unit,
            Spell,
            (DC.SpellsToUse[Spell].Rank and "(" .. (str_sub(DC.RANKNUMTRANS, '%d+', DC.SpellsToUse[Spell].Rank)) .. ")" or "")  ));
            --]]

        end

        self.Debuff1Prio = false;

        -- the update timestamp
        self.LastAttribUpdate = D.Status.SpellsChanged;
        return self;
    end
end -- }}}

function MicroUnitF.prototype:SetDebuffs() -- {{{
    self.Debuffs, self.IsCharmed = D:UnitCurableDebuffs(self.CurrUnit);

    if D.UnitDebuffed[self.CurrUnit] then
        D.ForLLDebuffedUnitsNum = D.ForLLDebuffedUnitsNum - 1;
    end

    if (self.Debuffs and self.Debuffs[1] and self.Debuffs[1].Type) then
        --D:Debug("A debuff was found"); -- XXX
        self.IsDebuffed = true;
        self.Debuff1Prio = D:GiveSpellPrioNum( self.Debuffs[1].Type );

        D.UnitDebuffed[self.CurrUnit] = true;
        D.ForLLDebuffedUnitsNum = D.ForLLDebuffedUnitsNum + 1;

    else
        --D:Debug("No debuff found"); -- XXX
        self.IsDebuffed                 = false;
        self.Debuff1Prio                = false;
        self.PrevDebuff1Prio            = false;
        D.UnitDebuffed[self.CurrUnit] = false; -- used by the live-list only
    end
end -- }}}

do
    --[=[
    --      This function is responsible for setting all the textures of a MUF object:
    --          - The main color
    --          - Showing/Hiding the charmed alert square
    --          - The Alpha of the center and borders
    --      This function also set the Status of the MUF that will be used in the tooltip
    --]=]
    local DebuffType, Unit, PreviousStatus, BorderAlpha, Class, ClassColor, ReturnValue, RangeStatus, Alpha, PrioChanged, PrevChrono, Time, Status;
    local profile = {};

    -- global access optimization
    local IsSpellInRange    = _G.IsSpellInRange;
    local UnitClass         = _G.UnitClass;
    local UnitExists        = _G.UnitExists;
    local UnitIsVisible     = _G.UnitIsVisible;
    local UnitLevel         = _G.UnitLevel;
    local unpack            = _G.unpack;
    local select            = _G.select;
    local GetTime           = _G.GetTime;
    local floor             = _G.math.floor;
    local fmod              = _G.math.fmod;
    local CooldownFrame_SetTimer = _G.CooldownFrame_SetTimer;
    local GetSpellCooldown = _G.GetSpellCooldown;
    local GetRaidTargetIndex= _G.GetRaidTargetIndex;
    local bor = _G.bit.bor;

    function MicroUnitF.prototype:SetColor() -- {{{

        profile = D.profile;
        Status  = D.Status;

        -- register default alpha of the border
        BorderAlpha =  profile.DebuffsFrameElemBorderAlpha;

        -- register local variables
        DebuffType = false;
        ReturnValue = false;
        Unit = self.CurrUnit;
        PreviousStatus = self.UnitStatus;



        -- if unit not available, if a unit cease to exist (this happen often for pets)
        if not UnitExists(Unit) then
            if PreviousStatus ~= ABSENT then
                self.Color = MF_colors[ABSENT];
                self.UnitStatus = ABSENT;
                if self.LitTime then
                    self.LitTime = false;
                    self.ChronoFontString:SetText(" ");
                end
            end

            -- UnitIsVisible() behavior is not 100% reliable so we also use UnitLevel() that will return -1 when the Unit is too far...
        elseif not UnitIsVisible(Unit) or UnitLevel(Unit) < 1 then
            if PreviousStatus ~= FAR then
                self.Color = MF_colors[FAR];
                self.UnitStatus = FAR;
                if self.LitTime then
                    self.LitTime = false;
                    self.ChronoFontString:SetText(" ");

                end
            end

        else
            -- If the Unit is invisible
            if profile.Show_Stealthed_Status and D.Stealthed_Units[Unit] then
                if PreviousStatus ~= STEALTHED then
                    self.Color = MF_colors[STEALTHED];
                    self.UnitStatus = STEALTHED;
                    if self.LitTime then
                        self.LitTime = false;
                        self.ChronoFontString:SetText(" ");

                    end
                end

                -- if unit is blacklisted
            elseif Status.Blacklisted_Array[Unit] then
                if PreviousStatus ~= BLACKLISTED then
                    self.Color = MF_colors[BLACKLISTED];
                    self.UnitStatus = BLACKLISTED;
                    if self.LitTime then
                        self.LitTime = false;
                        self.ChronoFontString:SetText(" ");

                    end
                end

                -- if the unit has some debuffs we can handle
            elseif (self.IsDebuffed) then
                DebuffType = self.Debuffs[1].Type;

                if self.PrevDebuff1Prio ~= self.Debuff1Prio then
                    self.Color = MF_colors[self.Debuff1Prio];
                    self.PrevDebuff1Prio = self.Debuff1Prio;
                    PrioChanged = true;
                end

                -- Test if the spell we are going to use is in range
                -- Some time can elaps between the instant the debuff is detected and the instant it is shown.
                -- Between those instants, a reconfiguration can happen (pet dies or some spells become unavailable)
                -- So we test before calling this api that we can still cure this debuff type
                if Status.CuringSpells[DebuffType] then
                    RangeStatus = IsSpellInRange(Status.CuringSpells[DebuffType], Unit);
                else
                    RangeStatus = false;
                end

                Time = GetTime();

                if RangeStatus and self.UpdateCD < Status.UpdateCooldown then
                    CooldownFrame_SetTimer (self.CooldownFrame, GetSpellCooldown(Status.CuringSpells[DebuffType]) );
                    self.UpdateCD = Time;
                end

                -- update the chrono
                if profile.DebuffsFrameChrono then
                    if self.LitTime then
                        PrevChrono = self.Chrono;

                        if not profile.DebuffsFrameTimeLeft then
                            self.Chrono = floor(Time - self.LitTime);

                            if self.Chrono ~= PrevChrono then
                                self.ChronoFontString:SetText( ((self.Chrono < 60) and self.Chrono or (floor(self.Chrono / 60) .. "\'") ));
                            end
                        else
                            self.Chrono = floor(self.Debuffs[1].expirationTime - Time);

                            if self.Chrono ~= PrevChrono then
                                self.ChronoFontString:SetText( ((self.Chrono < 60) and (self.Chrono + 1) or (floor(self.Chrono / 60 + 1) .. "\'") ));
                            end
                        end
                    else
                        self.LitTime = Time;
                    end
                end
                
                self.RaidTargetIcon = GetRaidTargetIndex(Unit);
                if self.PrevRaidTargetIndex ~= self.RaidTargetIcon then
                    self.RaidIconTexture:SetTexture(self.RaidTargetIcon and DC.RAID_ICON_TEXTURE_LIST[self.RaidTargetIcon] or nil);
                    self.PrevRaidTargetIndex = self.RaidTargetIcon;
                end
 

                -- set the status according to RangeStatus
                if (not RangeStatus or RangeStatus == 0) then
                    Alpha = 0.40;
                    self.UnitStatus = AFFLICTED_NIR;
                else
                    Alpha = 1;
                    self.UnitStatus = AFFLICTED;
                    BorderAlpha = 1;

                    MicroUnitF.UnitsDebuffedInRange = MicroUnitF.UnitsDebuffedInRange + 1;

                    if (not Status.SoundPlayed) then
                        D:PlaySound (self.CurrUnit, "SetColor()" );
                    end
                end
            elseif PreviousStatus ~= NORMAL then
                -- the unit has nothing special, set the status to normal
                self.Color = MF_colors[NORMAL];
                self.UnitStatus = NORMAL;
                if self.LitTime then
                    self.LitTime = false;
                    self.ChronoFontString:SetText(" ");
                end

                if self.RaidTargetIcon then
                    self.RaidIconTexture:SetTexture(nil);
                    self.RaidTargetIcon = false;
                    self.PrevRaidTargetIndex = false;
                end

                -- if the previous status was FAR, trigger a full rescan of the unit (combat log event does not report events for far away units)
                if PreviousStatus == FAR then
                    D.MicroUnitF:UpdateMUFUnit(self.CurrUnit, true); -- this is able to deal when a lot of update queries
                end
            end
        end

        if PreviousStatus == AFFLICTED or PreviousStatus == AFFLICTED_AND_CHARMED  then
            MicroUnitF.UnitsDebuffedInRange = MicroUnitF.UnitsDebuffedInRange - 1;

            if MicroUnitF.UnitsDebuffedInRange == 0 and profile.Hide_LiveList then
                D:Debug("SetColor(): No more unit, sound re-enabled");
                Status.SoundPlayed = false;
            end
        end

        if not profile.DebuffsFrameElemBorderShow then
            BorderAlpha = 0;
        end


        -- set the class border color when needed (the class is unknown and the unit exists or the unit name changed)
        --self:SetClassBorder();
        
        -- set the alpha of the border if necessary
        if self.BorderAlpha ~= BorderAlpha then
            self.OuterTexture1:SetAlpha(BorderAlpha);
            self.OuterTexture2:SetAlpha(BorderAlpha);
            self.OuterTexture3:SetAlpha(BorderAlpha);
            self.OuterTexture4:SetAlpha(BorderAlpha);

            self.BorderAlpha = BorderAlpha;

            -- set this to true because we just did something expensive...
            ReturnValue = true;
            --D:Debug("border alpha set");
        end


        -- Add the charm status to the bitfield (note that it's treated separatly because it's shown even if the unit is not afflicetd by anything we can cure)
        if self.IsCharmed then
            self.UnitStatus = bor(self.UnitStatus, CHARMED_STATUS);
        end

        -- if the unit is not afflicted or too far set the color to a lower alpha
        if not DebuffType then -- if DebuffType was not set, it means that the unit is too far
            Alpha = self.Color[4] * profile.DebuffsFrameElemAlpha;
            self.PrevDebuff1Prio = false;
        end


        -- Apply the colors and alphas only if necessary
        --      The MUF status changed
        --      The user changed the defaultAlpha
        --      The priority (and thus the color) of the first affliction changed
        if (self.UnitStatus ~= PreviousStatus or self.NormalAlpha ~= profile.DebuffsFrameElemAlpha or PrioChanged) then-- or self.FirstDebuffType ~= DebuffType) then

            if PrioChanged then PrioChanged = false; end

            -- Set the main texture
            self.Texture:SetTexture(self.Color[1], self.Color[2], self.Color[3], Alpha);
            --self.Texture:SetAlpha(Alpha);

            

            -- Show the charmed alert square
            if self.IsCharmed then
                self.InnerTexture:Show();
            else
                self.InnerTexture:Hide();
            end

            --D:Debug("Color Applied, MUF Status:", self.UnitStatus);


            -- save the current global status
            self.NormalAlpha = profile.DebuffsFrameElemAlpha;
            self.FirstDebuffType = DebuffType;

            -- set this to true because we just did something expensive...
            ReturnValue = true;
        end

        return ReturnValue;

    end -- }}}

    function MicroUnitF.prototype:SetClassBorder()
        --D:Debug("SetClassBorder called ", D.Status.Unit_Array_UnitToGUID[self.CurrUnit] , self.UnitGUID);
        ReturnValue = false;
        if (D.profile.DebuffsFrameElemBorderShow and (D.Status.Unit_Array_UnitToGUID[self.CurrUnit] ~= self.UnitGUID or (not self.UnitClass and UnitExists(self.CurrUnit)))) then

            -- Get the GUID of this unit
            self.UnitGUID = D.Status.Unit_Array_UnitToGUID[self.CurrUnit];

            if self.UnitGUID then -- can be nil because of focus...
                -- Get its class
                Class = (select(2, UnitClass(self.CurrUnit)));
            else
                Class = false;
            end

            -- if the class changed
            if (Class and Class ~= self.UnitClass) then
                ClassColor = DC.ClassesColors[Class];
                -- update the border color (the four borders)
                self.OuterTexture1:SetTexture(  unpack(ClassColor) );
                self.OuterTexture2:SetTexture(  unpack(ClassColor) );
                self.OuterTexture3:SetTexture(  unpack(ClassColor) );
                self.OuterTexture4:SetTexture(  unpack(ClassColor) );

                -- save the class for futur reference
                self.UnitClass = Class;

                -- set this to true because we just did something expensive...
                ReturnValue = true;

                --D:Debug("Class '%s' set for '%s'", Class, self.CurrUnit);
            elseif not Class and self.UnitClass then
                -- if the class is not available, set it to false so this test will be done again and again until a class is found
                self.UnitClass = false;
                BorderAlpha = 0;
                self.OuterTexture1:SetAlpha(BorderAlpha);
                self.OuterTexture2:SetAlpha(BorderAlpha);
                self.OuterTexture3:SetAlpha(BorderAlpha);
                self.OuterTexture4:SetAlpha(BorderAlpha);

                self.BorderAlpha = BorderAlpha;

                ReturnValue = true;
                --D:Debug("Class of unit %s is Nil", self.CurrUnit);
            end
        end
        return ReturnValue;

    end

end 
-- }}}

do
    local MicroFrameUpdateIndex = 1; -- MUFs are not updated all together
    local NumToShow, ActionsDone, Unit, MF, pass, UnitNum;
    -- updates the micro frames if needed (called regularly by ACE event, changed in the option menu)
    function D:DebuffsFrame_Update() -- {{{

        local Unit_Array = self.Status.Unit_Array;
        local UnitToGUID = self.Status.Unit_Array_UnitToGUID;

        UnitNum = self.Status.UnitNum; -- we need to go through all the units to set MF.ID properly
        NumToShow = ((MicroUnitF.MaxUnit > UnitNum) and UnitNum or MicroUnitF.MaxUnit);

        ActionsDone = 0; -- used to limit the maximum number of consecutive UI actions


        -- we don't check all the MUF at each call, only some of them (changed in the options)
        for pass = 1, self.profile.DebuffsFramePerUPdate do

            -- When all frames have been updated, go back to the first
            if (MicroFrameUpdateIndex > UnitNum) then
                MicroFrameUpdateIndex = 1;
                -- self:Debug("last micro frame updated,,:: %d", #self.Status.Unit_Array);
            end

            -- get a unit
            Unit = Unit_Array[MicroFrameUpdateIndex];

            -- should never fire unless the player choosed to ignore everything or something is wrong somewhere in the code
            if not Unit then
                --self:Debug("Unit is nil :/");
                return false;
            end

            -- get its MUF
            MF = MicroUnitF.ExistingPerUNIT[Unit];

            -- if no MUF then create it (All MUFs are created here)
            if (not MF) then
                if not InCombatLockdown() then
                    MF = MicroUnitF:Create(Unit, MicroFrameUpdateIndex);
                    ActionsDone = ActionsDone + 1;
                end
            end

            -- place the MUF ~right where it belongs~
            if MF and MF.ToPlace ~= MicroFrameUpdateIndex and not InCombatLockdown() then

                --sanity check
                --[[
                if MicroFrameUpdateIndex ~= MF.ID then  -- XXX to remove for release
                    D:AddDebugText("DebuffsFrame_Update(): MicroFrameUpdateIndex ~= MF.ID", MicroFrameUpdateIndex, MF.ID, Unit, MF.CurrUnit, "ToPlace:", MF.ToPlace);
                end
                --]]
                
                MF.ToPlace = MicroFrameUpdateIndex;

                MF.Frame:SetPoint(unpack(MicroUnitF:GiveMFAnchor(MicroFrameUpdateIndex)));
                if MF.Shown then
                    MF.Frame:Show();
                end

                -- test for GUID change and force a debuff update in this case
                if UnitToGUID[MF.CurrUnit] ~= MF.UnitGUID then
                    MF.UpdateCountDown = 0; -- will force MF:Update() to be called
                    --[===[@debug@
                    D:Println("|cFFFFAA55GUID change detected while placing for |r", MicroFrameUpdateIndex, UnitToGUID[MF.CurrUnit], MF.UnitGUID );
                    --@end-debug@]===]
                end

                ActionsDone = ActionsDone + 1;
            end

            -- update the MUF attributes and its colors -- this is done by an event handler now (buff/debuff received...) except when the unit has a debuff and is in range
            if MF and MicroFrameUpdateIndex <= NumToShow then
                if not (MF.IsDebuffed or MF.IsCharmed) and MF.UpdateCountDown ~= 0 then
                    MF.UpdateCountDown = MF.UpdateCountDown - 1;
                else -- if MF.IsDebuffed or MF.IsCharmed or MF.UpdateCountDown == 0
                    ActionsDone = ActionsDone + MF:Update(false, true);--, not ((MF.IsDebuffed or MF.IsCharmed) and MF.UnitStatus ~= AFFLICTED)); -- we rescan debuffs if the unit is not in spell range XXX useless now since we rescan everyone every second
                    MF.UpdateCountDown = 3;
                end
            end

            -- we are done for this frame, go to te next
            MicroFrameUpdateIndex = MicroFrameUpdateIndex + 1;

            -- don't update more than 5 MUF in a row
            -- don't loop when reaching the end, wait for the next call (useful when less MUFs than PerUpdate)
            if (ActionsDone > 5 or pass == UnitNum) then
                --self:Debug("Max actions count reached");
                break;
            end

        end
        --    end
    end -- }}}
end

-- This little function returns the priority of the spell corresponding to an affliction type (one spell can be used for several types)
function D:GiveSpellPrioNum (Type) -- {{{
    return D.Status.CuringSpellsPrio[D.Status.CuringSpells[Type]];
end -- }}}








-- old unused variables and functions
-- UNUSED STUFF {{{
-- Micro Frame Events, useless for now

function MicroUnitF:OnPostClick(frame, button)
--      D:Debug("Micro unit PostClicked");
end

function MicroUnitF:OnAttributeChanged(self, name, value)
        D:Debug("Micro unit", name, "AttributeChanged to", value);
end


local MUF_Status = { -- unused
    [1] = "normal";
    [2] = "absent";
    [3] = "far";
    [4] = "stealthed";
    [5] = "blacklist";
    [6] = "afflicted";
    [7] = "afflicted-far";
    [8] = "afflicted-charmed";
    [9] = "afflicted-charmed-far";
}


local MF_Textures = { -- unused
    "Interface/AddOns/Decursive/Textures/BackDrop-red", -- red
    "Interface/AddOns/Decursive/Textures/BackDrop-blue", -- blue
    "Interface/AddOns/Decursive/Textures/BackDrop-orange", -- orange
    ["grey"] = "Interface\\AddOns\\Decursive\\Textures\\BackDrop-grey-medium",
    ["black"] = "Interface/AddOns/Decursive/Textures/BackDrop",
};


-- }}}

T._LoadedFiles["Dcr_DebuffsFrame.lua"] = "2.5.1-12-gb39554a";

-- Heresy
