--[[
    This file is part of Decursive.
    
    Decursive (v 2.5.1-6-gd3885c5) 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_DebuffsFrame.xml"] or not T._LoadedFiles["Dcr_DebuffsFrame.lua"] then
    if not DecursiveInstallCorrupted then T._FatalError("Decursive installation is corrupted! (Dcr_DebuffsFrame.xml or Dcr_DebuffsFrame.lua not loaded)"); end;
    DecursiveInstallCorrupted = true;
    return;
end

local D   = Dcr;
--D:SetDateAndRevision("$Date: 2008-09-16 00:25:13 +0200 (mar., 16 sept. 2008) $", "$Revision: 81755 $");

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

--D.LiveList = OOP.Class();

-- http://www.lua.org/pil/13.4.1.html
-- define the namespace
D.LiveList = {};
local LiveList = D.LiveList;
-- a prototype for LiveList objects, empty, defaults are defined in the :New for better performances
LiveList.prototype = {};
LiveList.metatable ={ __index = LiveList.prototype };

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

local MicroUnitF = D.MicroUnitF;

LiveList.ExistingPerID      = {};
LiveList.Number             = 0;
LiveList.NumberShown        = 0;
D.ForLLDebuffedUnitsNum     = 0;

-- temporary variables often used in function
local Debuff, Debuffs, IsCharmed, MF, i, Index, RangeStatus;

-- local shortcuts to often called global functions
local pairs             = _G.pairs;
local ipairs            = _G.ipairs;
local next              = _G.next;
local select            = _G.select;
local unpack            = _G.unpack;
local table             = _G.table;
local UnitExists        = _G.UnitExists;
local IsSpellInRange    = _G.IsSpellInRange;
local UnitClass         = _G.UnitClass;
local UnitIsFriend      = _G.UnitIsFriend;
local UnitGUID          = _G.UnitGUID;
local floor             = _G.math.floor;
local str_upper         = _G.string.upper;
local GetRaidTargetIndex= _G.GetRaidTargetIndex;


-- defines what is printed when the object is read as a string
function LiveList:ToString() -- {{{
    return "Decursive Live-List object";
end -- }}}

-- The Factory for LiveList objects
function LiveList:Create() -- {{{

    if self.Number + 1 > D.profile.Amount_Of_Afflicted then
        return false;
    end

    self.Number = self.Number + 1;

    self.ExistingPerID[self.Number] = self:new(DcrLiveList, self.Number);


    return self.ExistingPerID[self.Number];

end -- }}}

function LiveList:DisplayItem (ID, UnitID, Debuff) -- {{{

    --D:Debug("(LiveList) Displaying LVItem %d for UnitID %s", ID, UnitID);
    local LVItem = false;

    if ID > self.Number + 1 then
        return error(("LiveList:DisplayItem: bad argument #1 'ID (= %d)' must be < LiveList.Number + 1 (LiveList.Number = %d)"):format(ID, self.Number),2);
    end

    if not self.ExistingPerID[ID] then
        LVItem=self:Create();
    else
        LVItem = self.ExistingPerID[ID];
    end

    if not LVItem then
        return false;
    end

    if not Debuff then
        Debuff = D.ManagedDebuffUnitCache[UnitID][1];
    end

    LVItem:SetDebuff(UnitID, Debuff, nil);
    --D:Debug("XXXX => Updating ll item %d for %s", ID, UnitID);

    if not LVItem.IsShown then
        --D:Debug("(LiveList) Showing LVItem %d", ID);
        LVItem.Frame:Show();
        self.NumberShown = self.NumberShown + 1;
        LVItem.IsShown = true;
    end

end -- }}}

function LiveList:RestAllPosition () -- {{{
    for _, LVitem in ipairs(self.ExistingPerID) do
        LVitem.Frame:ClearAllPoints();
        LVitem.Frame:SetPoint(LVitem:GiveAnchor());
    end
end -- }}}

function LiveList.prototype:GiveAnchor() -- {{{

    local ItemHeight = self.Frame:GetHeight();

    if D.profile.ReverseLiveDisplay then
    end

    if self.ID == 1 then
        if D.profile.ReverseLiveDisplay then
            return "BOTTOMLEFT", DecursiveMainBar, "BOTTOMLEFT", 5, -1 * (ItemHeight + 1) * D.profile.Amount_Of_Afflicted;
        else
            return "TOPLEFT", DecursiveMainBar, "BOTTOMLEFT", 5, 0;
        end
    else
        if D.profile.ReverseLiveDisplay then
            return "BOTTOMLEFT", LiveList.ExistingPerID[self.ID - 1].Frame, "TOPLEFT", 0, 1;
        else
            return "TOPLEFT", LiveList.ExistingPerID[self.ID - 1].Frame, "BOTTOMLEFT", 0, -1;
        end
    end

end -- }}}


function LiveList.prototype:init(Container,ID) -- {{{
    
    --LiveList.super.prototype.init(self); -- needed
    D:Debug("(LiveList) Initializing LiveList object '%s'", ID);

    --ObjectRelated
    self.ID                 = ID;
    self.IsShown            = false;
    self.Parent             = Container;

    --Debuff info
    self.UnitID             = false;
    self.UnitName           = false;
    self.RaidTargetIndex    = false;
    self.PrevUnitName       = false;
    self.PrevUnitID         = false;
    self.PrevRaidTargetIndex= false;
    self.UnitClass          = false;
    
    self.Debuff             = {};

    self.PrevDebuffIndex    = false;
    self.PrevDebuffName     = false;
    self.PrevDebuffTypeName    = false;
    self.PrevDebuffApplicaton  = false;
    self.PrevDebuffTexture  = false;

    self.IsCharmed          = false;
    self.PrevIsCharmed      = false;

    self.Alpha              = false;

    -- Create the frame
    self.Frame = CreateFrame ("Button", "DcrLiveListItem"..ID, self.Parent, "DcrLVItemTemplate");

    -- Set some basic properties
    self.Frame:SetFrameStrata("LOW");
    self.Frame:RegisterForClicks("AnyDown");

    -- Set the anchor of this item
    self.Frame:SetPoint(self:GiveAnchor());

    -- create the background
    self.BackGroundTexture = self.Frame:CreateTexture("DcrLiveListItem"..ID.."BackTexture", "BACKGROUND", "DcrLVBackgroundTemplate");

    -- Create the Icon Texture
    self.IconTexture = self.Frame:CreateTexture("DcrLiveListItem"..ID.."Icon", "ARTWORK", "DcrLVIconTemplate");

    -- Create the Debuff application count font string
    self.DebuffAppsFontString = self.Frame:CreateFontString("DcrLiveListItem"..ID.."Count", "OVERLAY", "DcrLLAfflictionCountFont");

    -- Create the character name Fontstring
    self.UnitNameFontString = self.Frame:CreateFontString("DcrLiveListItem"..ID.."UnitName", "OVERLAY", "DcrLLUnitNameFont");
    
    -- Create the unitID Fontstring
    self.UnitIDFontString = self.Frame:CreateFontString("DcrLiveListItem"..ID.."UnitID", "OVERLAY", "DcrLLUnitIDFont");
    --self.UnitIDFontString:SetHeight(3);

    -- Create the debuff type fontstring
    self.DebuffTypeFontString = self.Frame:CreateFontString("DcrLiveListItem"..ID.."Type", "OVERLAY", "DcrLLDebuffTypeFont");
    
    -- Create the Raid Target Icon Texture
    self.RaidIconTexture = self.Frame:CreateTexture("DcrLiveListItem"..ID.."RaidIcon", "ARTWORK", "DcrLVRaidIconTemplate");

    -- Create the debuff name fontstring
    self.DebuffNameFontString = self.Frame:CreateFontString("DcrLiveListItem"..ID.."Name", "OVERLAY", "DcrLLDebuffNameFont");


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

    self.Frame:Show();


end -- }}}

function LiveList.prototype:SetDebuff(UnitID, Debuff, IsCharmed) -- {{{
    self.UnitID             = UnitID;
    self.UnitName           = D:PetUnitName(UnitID, true);
    self.Debuff             = Debuff;
    self.IsCharmed          = IsCharmed;
    self.RaidTargetIndex    = GetRaidTargetIndex(UnitID);

    if D.profile.LiveListAlpha ~= self.Alpha then
        self.Frame:SetAlpha(D.profile.LiveListAlpha);
        self.Alpha = D.profile.LiveListAlpha;
    end

    -- Set the graphical elements to the right values
    -- Icon
    if self.PrevDebuffTexture ~= Debuff.Texture then
        self.IconTexture:SetTexture(Debuff.Texture);
        self.PrevDebuffTexture =  Debuff.Texture;
    end

    -- Raid Icon
    if self.PrevRaidTargetIndex ~= self.RaidTargetIndex then
        self.RaidIconTexture:SetTexture(self.RaidTargetIndex and DC.RAID_ICON_TEXTURE_LIST[self.RaidTargetIndex] or nil);
        self.PrevRaidTargetIndex = self.RaidTargetIndex;
    end

    -- Applications count
    if self.PrevDebuffApplicaton ~= Debuff.Applications then
        if (Debuff.Applications > 1) then
            self.DebuffAppsFontString:SetText(Debuff.Applications);
            self.PrevDebuffApplicaton = Debuff.Applications;
        else
            self.DebuffAppsFontString:SetText(" ");
            self.PrevDebuffApplicaton = " ";
        end
    end

    -- Unit Name
    if self.PrevUnitName ~= self.UnitName then
        self.UnitClass = (select(2, UnitClass(UnitID)));
        self.UnitNameFontString:SetText(self.UnitName);
        if self.UnitClass then
            self.UnitNameFontString:SetTextColor(unpack(DC.ClassesColors[self.UnitClass]));
        end
        self.PrevUnitName =  self.UnitName;
        --D:Debug("(LiveList) Updating %d with %s", self.ID, UnitID);
    end

    -- Unit ID
    if self.PrevUnitID ~= UnitID then
        self.UnitIDFontString:SetText("( "..UnitID.." )");
        self.PrevUnitID = UnitID;
    end

    -- Debuff Type Name
    if self.PrevDebuffTypeName ~= Debuff.TypeName then
        if Debuff.Type then
            self.DebuffTypeFontString:SetText(D:ColorText(L[str_upper(Debuff.TypeName)], "FF" .. DC.TypeColors[Debuff.Type] ));
            --self.DebuffTypeFontString:SetTextColor(DC.TypeColors[Debuff.Type]);
        else
            self.DebuffTypeFontString:SetText("Unknown");
        end
        self.PrevDebuffTypeName = Debuff.TypeName;
    end

    -- Debuff Name
    if self.PrevDebuffName ~= Debuff.Name then
        self.DebuffNameFontString:SetText(Debuff.Name);
        self.PrevDebuffName = Debuff.Name;
    end

end -- }}}


function LiveList:GetDebuff(UnitID) -- {{{
    --  (note that this function is only called for the mouseover and target if the MUFs are active)

    --D:Debug("(LiveList) Getting Debuff for ", UnitID);
    if (UnitID == "target" or UnitID == "mouseover") and not UnitIsFriend(UnitID, "player") then
        if D.ManagedDebuffUnitCache[UnitID] and D.ManagedDebuffUnitCache[UnitID][1] and D.ManagedDebuffUnitCache[UnitID][1].Type then
            D.ManagedDebuffUnitCache[UnitID][1].Type = false; -- clear target/mouseover debuff
            D.UnitDebuffed[UnitID] = false; -- XXX changed from 'target' to UnitID on 2010-06-08
        end
        --D:Debug("(LiveList) GetDebuff() |cFF00DDDDcanceled|r, unit %s is hostile or gone.", UnitID);
        return false;
    end

    -- decrease the total debuff number if the MUFs system isn't already doing it and if it's not the mouseover or target unit
    if not D.profile.ShowDebuffsFrame and D.UnitDebuffed[UnitID] and UnitID ~= "mouseover" and UnitID ~= "target" then
        D.ForLLDebuffedUnitsNum = D.ForLLDebuffedUnitsNum - 1;
    end

    -- Get the unit Debuffs
    if not D.profile.ShowDebuffsFrame or not MicroUnitF.UnitToMUF[UnitID] or UnitID == "mouseover" or UnitID == "target" then
        Debuffs, IsCharmed = D:UnitCurableDebuffs(UnitID);
        --Debuffs, IsCharmed = D:UnitCurableDebuffs(UnitID, true);
    else -- The MUFs are active and Unit is not mouseover and is not target
        MF = MicroUnitF.UnitToMUF[UnitID];
        if MF then
            Debuffs = MF.Debuffs;
            --[=[
        else -- (ticket #6)
            D:AddDebugText("Sanity check failed in LiveList:GetDebuff() no MUF for unit", UnitID, "MUFs are", D.profile.ShowDebuffsFrame, "MUFnum:", MicroUnitF.Number, "MUFshown:", MicroUnitF.UnitShown, "UnitNum:", D.Status.UnitNum, "UnitExists:", UnitExists(UnitID), "Auto MUF show/hide:", D.profile.AutoHideDebuffsFrame, "InCombatLockdown():", InCombatLockdown());
            D:AddDebugText("Stack:\n", debugstack(2));
            --]=]
        end
    end

    if (Debuffs and Debuffs[1] and Debuffs[1].Type) then -- there is a Debuff

        D.UnitDebuffed[UnitID] = true; -- register that this unit is debuffed

        -- increase the total debuff number
        if not D.profile.ShowDebuffsFrame and UnitID ~= "mouseover" and UnitID ~= "target" then

            D.ForLLDebuffedUnitsNum = D.ForLLDebuffedUnitsNum + 1;

        end
    else
        D.UnitDebuffed[UnitID] = false; -- unregister this unit
    end

    return D.UnitDebuffed[UnitID];
end -- }}}

function LiveList:DelayedGetDebuff(UnitID) -- {{{
    if not D:DelayedCallExixts("Dcr_GetDebuff"..UnitID) then
        D.DebuffUpdateRequest = D.DebuffUpdateRequest + 1;
        D:Debug("LiveList: GetDebuff scheduled for, ", UnitID);
        D:ScheduleDelayedCall("Dcr_GetDebuff"..UnitID, self.GetDebuff, (D.profile.ScanTime / 2) * (1 + floor(D.DebuffUpdateRequest / 7.5)), self, UnitID);
    end
end -- }}}

local IndexOffset = 0; -- used when target and/or mouseover are found
local DebuffedUnitsNumber = 0;
local _;
function LiveList:Update_Display() -- {{{

    if not D.DcrFullyInitialized  then
        return;
    end

    -- Update the unit array
    --[[
    if (D.Groups_datas_are_invalid) then
        D:GetUnitArray();
    end
    --]]

    Index = 0;

    if D.profile.ShowDebuffsFrame and D.profile.LV_OnlyInRange then -- The MUFs are here and we test for range
        DebuffedUnitsNumber = MicroUnitF.UnitsDebuffedInRange;
    else -- the MUFs are not here or we don't test for range
        DebuffedUnitsNumber = D.ForLLDebuffedUnitsNum;
    end

    -- Check the units in order of importance:

    -- First the Target
    if D.Status.TargetExists and not D.Status.Unit_Array_GUIDToUnit[UnitGUID("target")] and self:GetDebuff("target") then -- TargetExists implies that the unit is a friend
        Index = Index + 1;
        self:DisplayItem(Index, "target");
        --D:Debug("frenetic target update");

        DebuffedUnitsNumber = DebuffedUnitsNumber + 1;

        if not D.Status.SoundPlayed then
            D:PlaySound ("target", "LV target" );
        end
    end

    -- Then the MouseOver
    if not D.Status.MouseOveringMUF and D.UnitDebuffed["mouseover"] and not D.Status.Unit_Array_GUIDToUnit[UnitGUID("mouseover")] and self:GetDebuff("mouseover") then -- this won't catch new debuff if all debuffs disappeard while overing the unit...
        Index = Index + 1;
        self:DisplayItem(Index, "mouseover");
        --D:Debug("frenetic mouseover update");

        DebuffedUnitsNumber = DebuffedUnitsNumber + 1;

        if not D.Status.SoundPlayed then
            D:PlaySound ("mouseover", "LV mouseover" );
        end
    end

    -- the sound played status is reset here because the live list is able to display target and mouseover units and far away ones...
    if DebuffedUnitsNumber == 0 then
        D.Status.SoundPlayed = false;
    end

    IndexOffset = Index;

    -- Then continue with all the remaining units if at least one of them is debuffed
    -- We need this loop because:
    --      1, we have to show an ordered list (always true)
    --      2, we want to test if the unit is in spell range (only if the option is active and the MUFs hidden)
    --      There is no event to do the last and a not simple table.sort() would be needed for the first...
    if DebuffedUnitsNumber > 0 and Index < D.profile.Amount_Of_Afflicted then
        for _, UnitID in ipairs(D.Status.Unit_Array) do
            -- if the unit is debuffed and still exists and is not stealthed check this only if the MUFs engine is not there, redudent tests otherwise...
            if D.UnitDebuffed[UnitID] and UnitExists(UnitID) then

                -- we don't care about range
                if not D.profile.LV_OnlyInRange then
                    Index = Index + 1;
                    self:DisplayItem(Index, UnitID);

                    -- play the sound if not already done
                    if not D.Status.SoundPlayed then
                        D:PlaySound (UnitID, "LV scan NR" );
                    end

                else -- we care about range

                    if D.profile.ShowDebuffsFrame and MicroUnitF.UnitToMUF[UnitID] then
                        RangeStatus = MicroUnitF.UnitToMUF[UnitID].UnitStatus; -- MicroUnitF.UnitToMUF[UnitID] is nil sometimes XXX
                        RangeStatus = (RangeStatus == DC.AFFLICTED or RangeStatus == DC.AFFLICTED_AND_CHARMED) and true or false;
                    else
                        if D.Status.CuringSpells[D.ManagedDebuffUnitCache[UnitID][1].Type] then
                            RangeStatus = IsSpellInRange(D.Status.CuringSpells[D.ManagedDebuffUnitCache[UnitID][1].Type], UnitID);
                        else
                            D:AddDebugText(
                                "LiveList:Update_Display(): couldn't get range, DType:", D.ManagedDebuffUnitCache[UnitID][1].Type,
                                "DTypeName:", D.ManagedDebuffUnitCache[UnitID][1].TypeName,
                                "DName:", D.ManagedDebuffUnitCache[UnitID][1].Name,
                                "MUFs are:", D.profile.ShowDebuffsFrame,
                                "InCombatLockdown():", InCombatLockdown(),
                                "UnitID:", UnitID
                            );
                            RangeStatus = 0;

                        end
                        RangeStatus = (RangeStatus and RangeStatus ~= 0) and true or false;
                    end

                    if (RangeStatus) then
                        Index = Index + 1;
                        self:DisplayItem(Index, UnitID);
                        -- play the sound if not already done
                        if not D.Status.SoundPlayed then
                            D:PlaySound (UnitID, "LV R" );
                        end
                    end
                end
            end

            -- don't loop if we reach the max displayed unit num or if all debuffed units have been displayed
            if Index == D.profile.Amount_Of_Afflicted or Index == DebuffedUnitsNumber + IndexOffset then
                break;
            end
        end
    end

    -- reset the sound if no units were displayed
    if not D.profile.ShowDebuffsFrame and Index == 0 and D.Status.SoundPlayed then
        Dcr:Debug("LV: No more unit displayed, sound re-enabled");
        D.Status.SoundPlayed = false; -- re-enable the sound if no more debuff
    end

    -- Hide unneeded Items
    if self.NumberShown > Index then -- if there are more units shown than the actual number of debuffed units
        for i = Index + 1, self.NumberShown do
            if self.ExistingPerID[i] and self.ExistingPerID[i].IsShown then
                --D:Debug("(LiveList) Hidding LVItem %d", i);
                self.ExistingPerID[i].Frame:Hide();
                self.ExistingPerID[i].IsShown = false;
                self.NumberShown = self.NumberShown - 1;
            else
                break;
            end
        end
    end


end -- }}}



function LiveList:DisplayTestItem() -- {{{
    if not self.TestItemDisplayed and D.Status.Unit_Array[1] then
        self.TestItemDisplayed = GetTime();
        D:DummyDebuff(D.Status.Unit_Array[1], "Test item");
    end
end -- }}}

function LiveList:HideTestItem() -- {{{
     self.TestItemDisplayed = false;
     local i = 1;

     for UnitID, Debuffed in pairs(D.UnitDebuffed) do
         if Debuffed then
             D:ScheduleDelayedCall("Dcr_rmt"..i, D.DummyDebuff, i * (D.profile.ScanTime / 2), D, UnitID);
             i = i + 1;
         end
     end

end -- }}}


-- this displays the tooltips of the live-list
function LiveList:DebuffTemplate_OnEnter(frame) --{{{
    if (D.profile.AfflictionTooltips and frame.Object.UnitID) then
        DcrDisplay_Tooltip:SetOwner(frame, "ANCHOR_CURSOR");
        DcrDisplay_Tooltip:ClearLines();
        DcrDisplay_Tooltip:SetUnitDebuff(frame.Object.UnitID,frame.Object.Debuff.index); -- OK
        DcrDisplay_Tooltip:Show();
    else
        D:Debug(D.profile.AfflictionTooltips, frame.Object.UnitID );
    end
end --}}}

function LiveList:Onclick() -- {{{
    D:Println(L["HLP_LL_ONCLICK_TEXT"]);
end -- }}}

T._LoadedFiles["Dcr_LiveList.lua"] = "2.5.1-6-gd3885c5";
