--------------------------------------------------------------------------------
-- File: AoUMeterData.lua
-- Desc: All classes used to store data
-- Original: DarkDPSMeterData.lua from DarkDPSMeter2
-- Mod by: Resha
--------------------------------------------------------------------------------

-- Redfine the function
group.IsPlayerInGroup = group.IsPlayerInGroup or group.IsCreatureInGroup

--------------------------------------------------------------------------------
-- Type TValueDetails
Global("TValueDetails", {})
--------------------------------------------------------------------------------
function TValueDetails:CreateNewObject(ID)
    return setmetatable({
            ID = ID,			-- ID of the value
            Type = "",			-- Type of the value
            Count = 0,			-- Count how many input
            Percentage = 0,		-- Inner percentage
            Amount = 0,			-- Total amount cumulated
            Min = -1,			-- Minimum input
            Avg = -1,			-- Average
            Max = -1,			-- Maximum input
        }, { __index = self })
end
--------------------------------------------------------------------------------
-- Recalc min, max, avg by adding a new value
--------------------------------------------------------------------------------
function TValueDetails:RecalcDetails(value)
    -- Increment the counter
    self.Count = self.Count + 1
    -- Increase the total damage amount
    self.Amount = self.Amount + value
    -- Update the min
    if (value < self.Min) or (self.Min == -1) then self.Min = value; end
    -- Update the max
    if (value > self.Max) or (self.Max == -1) then self.Max = value; end
    -- Update the average
    self.Avg = math.ceil(self.Amount / self.Count) or 0
end
--------------------------------------------------------------------------------
-- Type TTimer
Global("TTimer", {})
--------------------------------------------------------------------------------
function TTimer:CreateNewObject()
    return setmetatable({
            Counter = 0,		-- The counter incremeted each seconds
            IsStarted = false,	-- To know if the timer can be incremented
            LastHitTime = 0		-- The time of the last hit to remove the time after this hit		
        }, { __index = self })
end
--------------------------------------------------------------------------------
-- Reset the timer
--------------------------------------------------------------------------------
function TTimer:ResetTimer()
    self.Counter = 0
end
--------------------------------------------------------------------------------
-- Increment the timer
--------------------------------------------------------------------------------
function TTimer:IncrementTimer()
    if self.IsStarted then
        self.Counter = self.Counter + 1
    end
end
--------------------------------------------------------------------------------
-- Start the timer
--------------------------------------------------------------------------------
function TTimer:StartTimer()
    self.IsStarted = true
end
--------------------------------------------------------------------------------
-- Reset the timer
--------------------------------------------------------------------------------
function TTimer:StopTimer()
    self.IsStarted = false
    self.Counter = self.LastHitTime
end
--------------------------------------------------------------------------------
-- Set hit to save the last action time
--------------------------------------------------------------------------------
function TTimer:SetLastHit()
    self.LastHitTime = self.Counter
end
--------------------------------------------------------------------------------
-- Get elapsed time (return a value >= 1)
--------------------------------------------------------------------------------
function TTimer:GetElapsedTime()
    return self.Counter
end
--------------------------------------------------------------------------------
-- Type TDamageSpellData
Global("TDamageSpellData", {})
--------------------------------------------------------------------------------
function TDamageSpellData:CreateNewObject()
    return setmetatable({
            Name = "",					-- Name of the spell
            ID = 0,						-- ID of the spell
            DebugName = "",				-- Debug name of the spell
            Count = 0,					-- Count how many spell input
            Hits = 0,					-- Count how many efficient spell input
            Type = "",					-- Damage type of the spell
            
            Amount = 0,					-- Total damage amount
            AmountPerSec = 0,			-- Damage per second
            Percentage = 0,				-- Percentage of damage regarding all other damages
            DetailsList = {				-- Damage per type of damage
                [enumHit.Normal]	= TValueDetails:CreateNewObject(enumHit.Normal, "Normal"),
                [enumHit.Critical]	= TValueDetails:CreateNewObject(enumHit.Critical, "Critical"),
                [enumHit.Glancing]	= TValueDetails:CreateNewObject(enumHit.Glancing, "Glancing")
            },
            
            MissList = {
                [enumMiss.Dodge]	= TValueDetails:CreateNewObject(enumMiss.Dodge, "Dodge"),
                [enumMiss.Miss]		= TValueDetails:CreateNewObject(enumMiss.Miss, "Miss"),
            },
            
            ResistAmount = 0,				-- Total amount of resist
            ResistPercentage = 0,			-- Percentage of damage resisted regarding all damage
            ResistDetailsList = {			-- Blocked damage per type of blocked damage
                [enumHitBlock.Block]		= TValueDetails:CreateNewObject(enumHitBlock.Block, "Block"),
                [enumHitBlock.Parry]		= TValueDetails:CreateNewObject(enumHitBlock.Parry, "Parry"),
                [enumHitBlock.Barrier]		= TValueDetails:CreateNewObject(enumHitBlock.Barrier, "Barrier"),
                [enumHitBlock.Resist]		= TValueDetails:CreateNewObject(enumHitBlock.Resist, "Resist"),
                [enumHitBlock.Absorb]		= TValueDetails:CreateNewObject(enumHitBlock.Absorb, "Absorb"),
                [enumHitBlock.RunesAbsorb]	= TValueDetails:CreateNewObject(enumHitBlock.RunesAbsorb, "RuneAbsorb"),
                [enumHitBlock.MultAbsorb]	= TValueDetails:CreateNewObject(enumHitBlock.MultAbsorb, "MultAbsorb"),
                [enumHitBlock.Mount]		= TValueDetails:CreateNewObject(enumHitBlock.Mount, "Mount"),
            },
            
            ExtraInfoList = {
            }
        }, { __index = self })
end
--------------------------------------------------------------------------------
-- Update the data by adding a new value
--------------------------------------------------------------------------------
function TDamageSpellData:ReceiveValuesFromParams(params)
    -- Increment the counter
    self.Count = self.Count + 1;
    
    -- Increase the total damage amount
    self.Amount = self.Amount + params.amount;
    
    -- Update the corresponding type of hit
    -- If it's a miss
    if params.isMiss then self.MissList[enumMiss.Miss]:RecalcDetails(params.amount) end
    
    -- If it's a dodge
    if params.isDodge then self.MissList[enumMiss.Dodge]:RecalcDetails(params.amount) end
    
    -- If it's not a miss nor a dodge
    if not params.isMiss and not params.isDodge then
        -- Increment the hits counter
        self.Hits = self.Hits + 1;
        
        -- If it's a critical hit
        if params.isCritical then self.DetailsList[enumHit.Critical]:RecalcDetails(params.amount) end
        
        -- If it's a glancing hit
        if params.isGlancing then self.DetailsList[enumHit.Glancing]:RecalcDetails(params.amount) end
        
        -- If it's not a critical hit nor a glancing hit
        if not params.isCritical and not params.isGlancing then self.DetailsList[enumHit.Normal]:RecalcDetails(params.amount) end
    end
    
    -- If the hit has been blocked by a shield
    if params.shieldBlock > 0 then self.ResistDetailsList[enumHitBlock.Block]:RecalcDetails(params.shieldBlock) end
    
    -- If the hit has been parried
    if params.parry > 0 then self.ResistDetailsList[enumHitBlock.Parry]:RecalcDetails(params.parry) end
    
    -- If the hit went in a barrier
    if params.barrier > 0 then self.ResistDetailsList[enumHitBlock.Barrier]:RecalcDetails(params.barrier) end
    
    -- If the hit has been resisted
    if params.resist > 0 then self.ResistDetailsList[enumHitBlock.Resist]:RecalcDetails(params.resist) end
    
    -- If the hit has been absorbed
    if params.absorb > 0 then self.ResistDetailsList[enumHitBlock.Absorb]:RecalcDetails(params.absorb) end
    
    -- If the hit has been absorbed by the defensive runes
    if params.runesAbsorb > 0 then self.ResistDetailsList[enumHitBlock.RunesAbsorb]:RecalcDetails(params.runesAbsorb) end
    
    -- If the hit has been absorbed by the mount
    if params.toMount > 0 then self.ResistDetailsList[enumHitBlock.Mount]:RecalcDetails(params.toMount) end
    
    -- If the hit has been absorbed by a multiplier (buff/debuff)
    if params.multipliersAbsorb > 0 then self.ResistDetailsList[enumHitBlock.MultAbsorb]:RecalcDetails(params.multipliersAbsorb) end
end
--------------------------------------------------------------------------------
-- Get the total blocked damage acount
--------------------------------------------------------------------------------
function TDamageSpellData:GetResistAmount()
    local res = 0
    for i, BlockDmgDetail in self.ResistDetailsList do
        res = res + BlockDmgDetail.Amount
    end
    return res
end
--------------------------------------------------------------------------------
-- Recalculate the pourcentage for each type of damage
--------------------------------------------------------------------------------
function TDamageSpellData:CalculateSpellDetailsPercentage()
    for i, DamageDetail in self.DetailsList do
        DamageDetail.Percentage = GetPercentageAt(DamageDetail.Count, self.Hits)
    end
    
    for i, MissDetail in self.MissList do
        MissDetail.Percentage = GetPercentageAt(MissDetail.Count, self.Count)
    end
    
    local AllDamage = self.Amount + self:GetResistAmount()
    for i, BlockDmgDetail in self.ResistDetailsList do
        BlockDmgDetail.Percentage = GetPercentageAt(BlockDmgDetail.Amount, AllDamage)
    end
end
--------------------------------------------------------------------------------
-- Type THealSpellData
Global("THealSpellData", {})
--------------------------------------------------------------------------------
function THealSpellData:CreateNewObject()
    return setmetatable({
            Name = "",					-- Name of the spell
            ID = 0,						-- ID of the spell
            DebugName = "",				-- Debug name of the spell
            Count = 0,					-- Count how many spell inpu
            Type = "",					-- Type of the heal
            
            Amount = 0,					-- Total heal amount
            AmountPerSec = 0,			-- Heal per second
            Percentage = 0,				-- Percentage of heal regarding all other heal spells
            DetailsList = {				-- Heal per type of heal
                [enumHit.Normal]	= TValueDetails:CreateNewObject(enumHit.Normal, "Normal"),
                [enumHit.Critical]	= TValueDetails:CreateNewObject(enumHit.Critical, "Critical"),
                [enumHit.Glancing]	= TValueDetails:CreateNewObject(enumHit.Glancing, "Glancing")
            },
            
            MissList = {
            },
            
            ResistAmount = 0,			-- Total of resisted heal amount
            ResistPercentage = 0,		-- Percentage of resisted heal regarding the total heal amount
            ResistDetailsList = {		-- Resisted heal per type of resisted
                [enumHealResist.Resisted]		= TValueDetails:CreateNewObject(enumHealResist.Resisted, "Resisted"),
                [enumHealResist.RuneResisted]	= TValueDetails:CreateNewObject(enumHealResist.RuneResisted, "RuneResisted"),
                [enumHealResist.Absorbed]		= TValueDetails:CreateNewObject(enumHealResist.Absorbed, "Absorbed"),
                [enumHealResist.Overload]		= TValueDetails:CreateNewObject(enumHealResist.Overload, "Overload"),
            },
            
            ExtraInfoList = {
                [enumExtraInfo.Lethality]		= TValueDetails:CreateNewObject(enumExtraInfo.Lethality, "Lethality"),
            }
        }, { __index = self })
end
--------------------------------------------------------------------------------
-- Update the data by adding a new value
--------------------------------------------------------------------------------
function THealSpellData:ReceiveValuesFromParams(params)
    local HealDetails
    
    -- Increment the counter
    self.Count = self.Count + 1;
    
    -- Increase the total heal amount
    self.Amount = self.Amount + params.heal;
    
    -- Update the corresponding type of heal
    -- If it's a critical hit
    if params.isCritical then self.DetailsList[enumHit.Critical]:RecalcDetails(params.heal) end
    
    -- If it's a glancing hit
    if params.isGlancing then self.DetailsList[enumHit.Glancing]:RecalcDetails(params.heal) end
    
    -- If it's not a critical hit nor a glancing hit
    if not params.isCritical and not params.isGlancing then self.DetailsList[enumHit.Normal]:RecalcDetails(params.heal) end
    
    -- Update the resists
    -- If the heal has been resisted
    if params.resisted > 0 then self.ResistDetailsList[enumHealResist.Resisted]:RecalcDetails(params.resisted) end
    
    -- If the hit has been parried
    if params.runeResisted > 0 then self.ResistDetailsList[enumHealResist.RuneResisted]:RecalcDetails(params.runeResisted) end
    
    -- If the hit went in a absorbed
    if params.absorbed > 0 then self.ResistDetailsList[enumHealResist.Absorbed]:RecalcDetails(params.absorbed) end
    
    -- If the hit went in a overload
    if params.overload > 0 then self.ResistDetailsList[enumHealResist.Overload]:RecalcDetails(params.overload) end
    
    -- The amount of the wounds
    if params.lethality > 0 then self.ExtraInfoList[enumExtraInfo.Lethality]:RecalcDetails(params.lethality) end
end
--------------------------------------------------------------------------------
-- Get the total resisted heal acount
--------------------------------------------------------------------------------
function THealSpellData:GetResistAmount()
    local res = 0
    for i, ResistHealDetail in self.ResistDetailsList do
        res = res + ResistHealDetail.Amount
    end
    return res
end
--------------------------------------------------------------------------------
-- Recalculate the pourcentage for each type of heal
--------------------------------------------------------------------------------
function THealSpellData:CalculateSpellDetailsPercentage()
    for i, HealDetail in self.DetailsList do
        HealDetail.Percentage = GetPercentageAt(HealDetail.Count, self.Count)
    end
    
    local AllHeal = self.Amount + self:GetResistAmount();
    for i, ResistHealDetail in self.ResistDetailsList do
        ResistHealDetail.Percentage = GetPercentageAt(ResistHealDetail.Amount, AllHeal)
    end
end
--------------------------------------------------------------------------------
-- Type TCombatantData
Global("TCombatantData", {})
--------------------------------------------------------------------------------
function TCombatantData:CreateNewObject()
    return setmetatable({
            Amount = 0,						-- Total amount
            AmoutPerSec = 0,				-- Amount per second
            Percentage = 0,					-- Percentage of amount regarding the rest of the group / raid
            LeaderPercentage = 0,			-- Percentage of amount regarding the top one in the group / raid
            SpellsList = {},				-- List of spells used		
        }, { __index = self })
end
--------------------------------------------------------------------------------
-- Type TCombatant
Global("TCombatant", {})
--------------------------------------------------------------------------------
function TCombatant:CreateNewObject(ID)
    return setmetatable({
            ID = ID,							-- ID of the combatant
            Name = object.GetName(ID),			-- Name of the combatant
            Class = unit.GetClass(ID).className or "UNKNOWN",		-- Class of the combatant
            Pet = unit.IsPlayer(ID) and (group.IsPlayerInGroup(ID) or ID == avatar.GetId()) and unit.GetActivePet(ID) and object.GetName(unit.GetActivePet(ID)) or nil,		-- Pet of the combatant if any
            Data = {							-- Data (Dps, Hps, Def)
                [enumMode.Dps]	= TCombatantData:CreateNewObject(),
                [enumMode.Hps]	= TCombatantData:CreateNewObject(),
                [enumMode.Def]	= TCombatantData:CreateNewObject()
            },
            SortValue = 0,			-- value which can represents damage amount, heal amout, ... for sort purpose
        }, { __index = self })
end
--------------------------------------------------------------------------------
function TCombatant:CreateNewObjectByName(Name)
    return setmetatable({
            ID = nil,			-- ID of the combatant
            Name = Name,		-- Name of the combatant
            Class = "PRIEST",	-- Class of the combatant
            Pet = nil,			-- Pet of the combatant if any
            Data = {			-- Data (Dps, Hps, Def)
                [enumMode.Dps]	= TCombatantData:CreateNewObject(),
                [enumMode.Hps]	= TCombatantData:CreateNewObject(),
                [enumMode.Def]	= TCombatantData:CreateNewObject()
            },
            SortValue = 0,			-- value which can represents damage amount, heal amout, ... for sort purpose
        }, { __index = self })
end
--------------------------------------------------------------------------------
-- Get spell by identifier = DebugNameType
--------------------------------------------------------------------------------
function TCombatant:GetSpellByIdentifier(Identifier, Mode)
    for i, SpellData in self.Data[Mode].SpellsList do
        if SpellData.Identifier == Identifier then
            return SpellData
        end
    end
    return nil
end
--------------------------------------------------------------------------------
-- Get spell by index
--------------------------------------------------------------------------------
function TCombatant:GetSpellByIndex(index, mode)
    return self.Data[mode].SpellsList[index]
end
--------------------------------------------------------------------------------
-- Add a new spell to the list
--------------------------------------------------------------------------------
function TCombatant:AddNewSpell(SpellInfo, Mode)
    if SpellInfo then
        local SpellData
        if Mode == enumMode.Dps or Mode == enumMode.Def then
            SpellData = TDamageSpellData:CreateNewObject()
        elseif Mode == enumMode.Hps then
            SpellData = THealSpellData:CreateNewObject()
        end
        if SpellData then
            SpellData.Name = SpellInfo.name
            SpellData.Type = SpellInfo.type
            SpellData.Identifier = SpellInfo.identifier
            SpellData.ID = SpellInfo.id
            
            table.insert(self.Data[Mode].SpellsList, SpellData)
            return SpellData
        end
    end
end
--------------------------------------------------------------------------------
-- Clear the spell list and data
--------------------------------------------------------------------------------
function TCombatant:ClearData()
    for i, Data in self.Data do
        Data.Amount = 0
        Data.AmountPerSec = 0
        Data.Percentage = 0
        Data.LeaderPercentage = 0
        Data.SpellsList = {}
    end
end
--------------------------------------------------------------------------------
-- Compare spell by amount
--------------------------------------------------------------------------------
local function CompareSpells(A, B)
    return A.Amount > B.Amount
end
--------------------------------------------------------------------------------
-- Sort the spell list by damage amount
--------------------------------------------------------------------------------
function TCombatant:SortSpellByAmount(Mode)
    table.sort(self.Data[Mode].SpellsList, CompareSpells)
end
--------------------------------------------------------------------------------
-- Calculate the damage, DPS, HPS, according to the fight time
--------------------------------------------------------------------------------
function TCombatant:CalculateSpell(FightTime, Mode)
    if not (FightTime > 0) then FightTime = 1 end
    for i, SpellData in self.Data[Mode].SpellsList do
        SpellData.ResistAmount = SpellData:GetResistAmount()
        SpellData.AmountPerSec = SpellData.Amount / FightTime
        SpellData.Percentage = GetPercentageAt(SpellData.Amount, self.Data[Mode].Amount)
        SpellData:CalculateSpellDetailsPercentage()
        SpellData.ResistPercentage = GetPercentageAt(SpellData.ResistAmount, (SpellData.Amount + SpellData.ResistAmount))
    end
    self:SortSpellByAmount(Mode)
end
--------------------------------------------------------------------------------
-- Update information of a combatant
--------------------------------------------------------------------------------
function TCombatant:UpdateCombatantDataByID(ID)
    if not ID then return end
    self.ID = ID
    self.Name = object.GetName(ID)
    self.Class = unit.GetClass(ID).className or "UNKNOWN"
    self.Pet = unit.IsPlayer(ID) and (group.IsPlayerInGroup(ID) or ID == avatar.GetId()) and unit.GetActivePet(ID) and object.GetName(unit.GetActivePet(ID)) or nil
end
--------------------------------------------------------------------------------
-- Type TFightData
Global("TFightData", {})
--------------------------------------------------------------------------------
function TFightData:CreateNewObject()
    return setmetatable({
            Amount = 0,			-- Total amount in the fight
            AmountPerSec = 0	-- Amount per second
        }, { __index = self })
end
--------------------------------------------------------------------------------
-- Type TFight
Global("TFight", {})
--------------------------------------------------------------------------------
function TFight:CreateNewObject(ID)
    return setmetatable({
            ID = ID,							-- ID of the fight
            Timer = TTimer:CreateNewObject(),	-- Timer of the fight
            Data = {							-- Data (Dps, Hps, Def)
                [enumMode.Dps]	= TFightData:CreateNewObject(),
                [enumMode.Hps]	= TFightData:CreateNewObject(),
                [enumMode.Def]	= TFightData:CreateNewObject()
            },
            CombatantsList = {},	-- List of combatants
        }, { __index = self })
end
--------------------------------------------------------------------------------
-- Add a new combatant by ID
--------------------------------------------------------------------------------
function TFight:AddNewCombatantByID(ID)
    if not self:GetCombatantByName(object.GetName(ID)) then
        table.insert(self.CombatantsList, TCombatant:CreateNewObject(ID))
    end
end
--------------------------------------------------------------------------------
-- Add a new combatant by name
--------------------------------------------------------------------------------
function TFight:AddNewCombatantByName(Name)
    if not self:GetCombatantByName(Name) then
        table.insert(self.CombatantsList, TCombatant:CreateNewObjectByName(Name))
    end
end
--------------------------------------------------------------------------------
-- Remove a combatant by name
--------------------------------------------------------------------------------
function TFight:RemoveCombatantByName(Name)
    local Combatant, index = self:GetCombatantByName(Name)
    
    if index then
        table.remove(self.CombatantsList, index)
    end
end
--------------------------------------------------------------------------------
-- Get a combatant by name
--------------------------------------------------------------------------------
function TFight:GetCombatantByName(Name)
    for i, Combatant in self.CombatantsList do
        if IsThisStringValue(Combatant.Name, Name) then
            return Combatant, i
        end
    end
    return nil
end
--------------------------------------------------------------------------------
-- Get a combatant by ID
--------------------------------------------------------------------------------
function TFight:GetCombatantByID(ID)
    for i, Combatant in self.CombatantsList do
        if Combatant.ID == ID then
            return Combatant
        end
    end
    return nil
end
--------------------------------------------------------------------------------
-- Get combatant by index
--------------------------------------------------------------------------------
function TFight:GetCombatantByIndex(Index)
    return self.CombatantsList[Index]
end
--------------------------------------------------------------------------------
-- Get combatant name by index
--------------------------------------------------------------------------------
function TFight:GetCombatantNameByIndex(Index)
    local Combatant = self.CombatantsList[Index]
    return Combatant and Combatant.Name or nil
end
--------------------------------------------------------------------------------
-- Get combatant count
--------------------------------------------------------------------------------
function TFight:GetCombatantCount()
    return table.getn(self.CombatantsList) or 0
end
--------------------------------------------------------------------------------
-- Start the fight
--------------------------------------------------------------------------------
function TFight:StartFight()
    self.Timer:StartTimer()
end
--------------------------------------------------------------------------------
-- Stop the fight
--------------------------------------------------------------------------------
function TFight:StopFight()
    self.Timer:StopTimer()
end
--------------------------------------------------------------------------------
-- Clear all data for combatant
--------------------------------------------------------------------------------
function TFight:ResetFight()
    for i, Combatant in self.CombatantsList do
        Combatant:ClearData()
    end
    for i, data in self.Data do
        data.Amount = 0
        data.AmoutPerSec = 0
    end
    self.Timer:ResetTimer()
end
--------------------------------------------------------------------------------
-- Increment the fight time of the fight and the inner fight time for all combatant
--------------------------------------------------------------------------------
function TFight:IncrementFightTime()
    self.Timer:IncrementTimer()
end
--------------------------------------------------------------------------------
-- Compare combatant by damage amount
--------------------------------------------------------------------------------
local function CompareCombatantsBySortValue(A, B)
    if A.SortValue == B.SortValue then
        return common.CompareWString(A.Name, B.Name) == -1 end
    return A.SortValue > B.SortValue
end
--------------------------------------------------------------------------------
-- Sort combatant by SortValue. This value is update in TFight:RecalculateCombatantsData(Mode)
-- It can be damage amount, heal amout, ...
--------------------------------------------------------------------------------
function TFight:SortCombatantsBySortValue(Mode)
    table.sort(self.CombatantsList, CompareCombatantsBySortValue)
    local ImbaCombatant = self.CombatantsList[1]
    if ImbaCombatant then
        local LeaderAmount = ImbaCombatant.Data[Mode].Amount
        for i, Combatant in self.CombatantsList do
            Combatant.Data[Mode].LeaderPercentage = GetPercentageAt(Combatant.Data[Mode].Amount, LeaderAmount)
        end
    end
end
--------------------------------------------------------------------------------
-- Recalculate combatant data according to the inner fight time
--------------------------------------------------------------------------------
function TFight:RecalculateCombatantsData(Mode)
    local FightTime = self.Timer:GetElapsedTime()
    if not (FightTime > 0) then FightTime = 1 end
    -- Calculate the total amount
    self.Data[Mode].Amount = 0
    for i, Combatant in self.CombatantsList do
        self.Data[Mode].Amount = self.Data[Mode].Amount + Combatant.Data[Mode].Amount
        Combatant.SortValue = Combatant.Data[Mode].Amount
    end
    -- Calculate the total amount per second
    self.Data[Mode].AmountPerSec = self.Data[Mode].Amount / FightTime
    -- For each combatant, calculate DPS and damage amount
    for i, Combatant in self.CombatantsList do
        Combatant.Data[Mode].AmountPerSec = Combatant.Data[Mode].Amount / FightTime
        Combatant.Data[Mode].Percentage = GetPercentageAt(Combatant.Data[Mode].Amount, self.Data[Mode].Amount)
    end
    
    self:SortCombatantsBySortValue(Mode)
end
--------------------------------------------------------------------------------
-- Type TUMeter
Global("TUMeter", {})
--------------------------------------------------------------------------------
function TUMeter:CreateNewObject()
    return setmetatable({
            FightsList = {},			-- List of fight ([1]=Total, [2]=Previous, [3+]=Current)
            bCollectData = false,		-- If we must collect data or not (are we in fight or not)
            Fight = { Total = nil, Current = nil, Previous = nil },	-- index of remarquable fight
            OffBattleTime = 0,          -- Off-time battle allows to retrieve data coming just after the end of the fight (the events seems to not arrive in the correct order)
        }, { __index = self })
end
--------------------------------------------------------------------------------
-- Begin a new fight
--------------------------------------------------------------------------------
function TUMeter:AddNewFight()
    table.insert(self.FightsList, TFight:CreateNewObject(table.getn(self.FightsList) + 1))
    return table.getn(self.FightsList)
end
--------------------------------------------------------------------------------
-- Get fight by ID
--------------------------------------------------------------------------------
function TUMeter:GetFight(ID)
    return self.FightsList[ID]
end
--------------------------------------------------------------------------------
-- Add a new combatant
--------------------------------------------------------------------------------
function TUMeter:AddNewCombatantByID(ID)
    -- Add new combatant to current fight
    self.FightsList[self.Fight.Current]:AddNewCombatantByID(ID)
    
    -- Add new combatant to overall fight
    self.FightsList[self.Fight.Total]:AddNewCombatantByID(ID)
end
--------------------------------------------------------------------------------
-- Add a new combatant by name
--------------------------------------------------------------------------------
function TUMeter:AddNewCombatantByName(Name)
    -- Add new combatant to current fight
    self.FightsList[self.Fight.Current]:AddNewCombatantByName(Name)
    
    -- Add new combatant to overall fight
    self.FightsList[self.Fight.Total]:AddNewCombatantByName(Name)
end
--------------------------------------------------------------------------------
-- Remove combatant by name
--------------------------------------------------------------------------------
function TUMeter:RemoveCombatantByName(Name)
    self.FightsList[self.Fight.Current]:RemoveCombatantByName(Name)
    self.FightsList[self.Fight.Total]:RemoveCombatantByName(Name)
end
--------------------------------------------------------------------------------
-- Get source Id and variant of the given ID:
--  If it's player
--------------------------------------------------------------------------------
function TUMeter:GetSourceIdAndVariant(ID)
    if not ID then return end
    local Variant = nil
    local CombatantID = nil
    -- look for the type of the source
    if unit.IsPlayer(ID) or group.IsCreatureInGroup(ID) then
        for i, C in self.FightsList[self.Fight.Current].CombatantsList do
            if (ID == C.ID) then
                Variant = 1 -- Player or Mercenary.
                CombatantID = C.ID
                break
            end
        end
    elseif unit.GetFollowerMaster(ID) then
        local MasterID = unit.GetFollowerMaster(ID)
        for i, C in self.FightsList[self.Fight.Current].CombatantsList do
            if (MasterID == C.ID) then
                Variant = 2 -- Player's Pet.
                CombatantID = C.ID
                break
            end
        end
    elseif unit.GetRace(ID) and unit.GetRace(ID).creatureRace == 1 then
        for i, C in self.FightsList[self.Fight.Current].CombatantsList do
            if (C.Pet) and (common.CompareWString(object.GetName(ID), C.Pet) == 0) then
                Variant = 3 -- Druid's Minions.
                CombatantID = C.ID
                break
            end
        end
    end
    
    return CombatantID, Variant
end
--------------------------------------------------------------------------------
-- Collect damage data from event EVENT_UNIT_DAMAGE_RECEIVED
--------------------------------------------------------------------------------
function TUMeter:CollectDamageData(params)
    if params.isFall then return end
    
    if Settings["ModeDPS"] and params.source and object.IsExist(params.source) and unit.IsFriend(params.source) then
        self:CollectDamageDealedData(params)
    elseif Settings["ModeDEF"] and params.target and object.IsExist(params.target) and unit.IsFriend(params.target) then
        self:CollectDamageReceivedData(params)
    end
end
--------------------------------------------------------------------------------
-- Collect damage dealed data by someone in the party
--------------------------------------------------------------------------------
function TUMeter:CollectDamageDealedData(params)
    local Variant = nil
    local CombatantID = nil
    
    -- look for the type of the source
    CombatantID, Variant = self:GetSourceIdAndVariant(params.source)
    
    -- If the source is not part of the group
    if (not Variant) then return end
    
    -- If we're not collecting data, means we are not currently in fight, then start a new one
    if (not self.bCollectData) and (object.IsInCombat(avatar.GetId()) or params.lethal) then
        self:Start()
    end
    
    if (not self.bCollectData) then return end
    
    -- update last hit time
    self.FightsList[self.Fight.Current].Timer:SetLastHit()
    self.FightsList[self.Fight.Total].Timer:SetLastHit()
    
    -- if collecting dps data
    if Settings["ModeDPS"] then
        -- Update damage amount in the current fight
        self:UpdateData(self.Fight.Current, CombatantID, enumMode.Dps, params)
        
        -- Update damage amount in the overall fight
        self:UpdateData(self.Fight.Total, CombatantID, enumMode.Dps, params)
    end
end

--------------------------------------------------------------------------------
-- Collect damage received data by someone in the party
--------------------------------------------------------------------------------
function TUMeter:CollectDamageReceivedData(params)
    local CombatantID = nil
    local Variant = nil
    
    if (not self.bCollectData) and object.IsInCombat(avatar.GetId()) then
        self:Start()
    end
    
    -- look for the type of the target
    CombatantID, Variant = self:GetSourceIdAndVariant(params.target)
    
    if not CombatantID or not (Variant == 1) then return end
    
    if (not self.bCollectData) then return end
    
    if Settings["ModeDEF"] then
        -- Update damage amount in the current fight
        self:UpdateData(self.Fight.Current, CombatantID, enumMode.Def, params)
        
        -- Update damage amount in the overall fight
        self:UpdateData(self.Fight.Total, CombatantID, enumMode.Def, params)
    end
end

--------------------------------------------------------------------------------
-- Collect heal data from event EVENT_HEALING_RECEIVED
--------------------------------------------------------------------------------
function TUMeter:CollectHealData(params)
    -- Check if the healer and the target are valid
    if not params.unitId or not object.IsExist(params.unitId) or not unit.IsFriend(params.unitId)
        or not params.healerId or not object.IsExist(params.healerId) or not unit.IsFriend(params.healerId)
        or params.isFall then
        return
    end
    
    if (not self.bCollectData) and object.IsInCombat(avatar.GetId()) then
        self:Start()
    end
    
    -- Check that the healer is part of the group
    local CombatantID = nil
    local Variant = nil
    CombatantID, Variant = self:GetSourceIdAndVariant(params.healerId)
    
    if (not self.bCollectData) or (not CombatantID) then return end
    
    -- update last hit time
    self.FightsList[self.Fight.Current].Timer:SetLastHit()
    
    if Settings["ModeHPS"] then
        params.amount = params.heal
        -- Update heal amount in the current fight
        self:UpdateData(self.Fight.Current, CombatantID, enumMode.Hps, params)
        
        -- Update heal amount in the overall fight
        self:UpdateData(self.Fight.Total, CombatantID, enumMode.Hps, params)
    end
end
--------------------------------------------------------------------------------
-- Update the data in the given mode
--------------------------------------------------------------------------------
function TUMeter:UpdateData(fightId, combatantID, mode, params)
    local Fight = self.FightsList[fightId]
    local Combatant = Fight:GetCombatantByID(combatantID)
    if Combatant then -- Crazy, in extremely rare cases it is nil.
        Combatant.Data[mode].Amount = Combatant.Data[mode].Amount + params.amount
        local SpellInfo = GetSpellInfoFromParams(params)
        if SpellInfo then
            local SpellData = Combatant:GetSpellByIdentifier(SpellInfo.identifier, mode)
            if not SpellData then
                SpellData = Combatant:AddNewSpell(SpellInfo, mode)
            end
            if SpellData then
                SpellData:ReceiveValuesFromParams(params)
                Fight.Timer:SetLastHit()
            end
        end
    end
end
--------------------------------------------------------------------------------
-- Start combat
--------------------------------------------------------------------------------
function TUMeter:Start()
    self:CopyFightFromCurrenToPrev()
    
    self:ResetFight(self.Fight.Current)
    
    self.FightsList[self.Fight.Current]:StartFight()
    self.FightsList[self.Fight.Total]:StartFight()
    
    self.bCollectData = true
    self.OffBattleTime = 0
end
--------------------------------------------------------------------------------
-- Stop combat
--------------------------------------------------------------------------------
function TUMeter:Stop()
    self.bCollectData = false
    self.FightsList[self.Fight.Current]:StopFight()
    self.FightsList[self.Fight.Total]:StopFight()
end
--------------------------------------------------------------------------------
-- Update the fight time in second (fight time ++)
--------------------------------------------------------------------------------
function TUMeter:UpdateFightsTime()
    -- Update current fight
    self.FightsList[self.Fight.Current]:IncrementFightTime()
    -- Update overall fight
    self.FightsList[self.Fight.Total]:IncrementFightTime()
    -- Reset the offtime battle
    self.OffBattleTime = 0
end
--------------------------------------------------------------------------------
-- Update the off-time battle and return the new value
--------------------------------------------------------------------------------
function TUMeter:UpdateOffBattleTime()
    self.OffBattleTime = self.OffBattleTime + 1
    return self.OffBattleTime
end
--------------------------------------------------------------------------------
-- Reset all fights
--------------------------------------------------------------------------------
function TUMeter:ResetAllFights()
    self.FightsList = {}
    self.Fight.Total = self:AddNewFight()
    if not Settings["KeepHistory"] then
        self.Fight.Previous = self:AddNewFight()
    else
        self.Fight.Previous = nil
    end
    self.Fight.Current = self:AddNewFight()
    self.bCollectData = false
end
--------------------------------------------------------------------------------
-- Reset specific fight
--------------------------------------------------------------------------------
function TUMeter:ResetFight(ID)
    if ID > 0 then
        self.FightsList[ID]:ResetFight()
    end
end
--------------------------------------------------------------------------------
-- Copy the current fight to the previous fight
--------------------------------------------------------------------------------
function TUMeter:CopyFightFromCurrenToPrev()
    self.FightsList[self.Fight.Previous] = DeepCopyObject(self.FightsList[self.Fight.Current])
end
--------------------------------------------------------------------------------
-- Get member list
--------------------------------------------------------------------------------
function TUMeter:GetPartyMembers()
    local PartyMembersInfoList
    if not raid.IsExist() then
        PartyMembersInfoList = group.GetMembers() or {}
    else
        PartyMembersInfoList = {}
        local RaidGroups = raid.GetMembers()
        local ind = 0
        for i, Group in RaidGroups do
            for i, member in Group do
                PartyMembersInfoList[ind] = member
                ind = ind + 1
            end
        end
    end
    return PartyMembersInfoList
end
