local _, Cell = ...
local L = Cell.L
local F = Cell.funcs

local UnitIsFeignDeath = UnitIsFeignDeath
local IsInGroup = IsInGroup
local IsEncounterInProgress = IsEncounterInProgress
local GetSpellLink = C_Spell.GetSpellLink or GetSpellLink

----------------------------------------------------
-- vars
----------------------------------------------------
local init, instanceType, inInstance
local deathLogs = {
    -- time, type, name, ability, school, amount, overkill, resisted, blocked, absorbed, critical, sourceName
}
local limit, count
local blacklist = {
    [124255] = true
}

local overkillFormat, resistedFormat, blockedFormat, absorbedFormat, criticalText
if Cell.isAsian then
    overkillFormat = string.sub(_G.TEXT_MODE_A_STRING_RESULT_OVERKILLING, 4, string.len(_G.TEXT_MODE_A_STRING_RESULT_OVERKILLING)-3)
    resistedFormat = string.sub(_G.TEXT_MODE_A_STRING_RESULT_RESIST, 4, string.len(_G.TEXT_MODE_A_STRING_RESULT_RESIST)-3)
    blockedFormat = string.sub(_G.TEXT_MODE_A_STRING_RESULT_BLOCK, 4, string.len(_G.TEXT_MODE_A_STRING_RESULT_BLOCK)-3)
    absorbedFormat = string.sub(_G.TEXT_MODE_A_STRING_RESULT_ABSORB, 4, string.len(_G.TEXT_MODE_A_STRING_RESULT_ABSORB)-3)
    criticalText = string.sub(_G.TEXT_MODE_A_STRING_RESULT_CRITICAL, 4, string.len(_G.TEXT_MODE_A_STRING_RESULT_CRITICAL)-3)
else
    overkillFormat = strlower(string.gsub(_G.TEXT_MODE_A_STRING_RESULT_OVERKILLING, "[()]", ""))
    resistedFormat = strlower(string.gsub(_G.TEXT_MODE_A_STRING_RESULT_RESIST, "[()]", ""))
    blockedFormat = strlower(string.gsub(_G.TEXT_MODE_A_STRING_RESULT_BLOCK, "[()]", ""))
    absorbedFormat = strlower(string.gsub(_G.TEXT_MODE_A_STRING_RESULT_ABSORB, "[()]", ""))
    criticalText = strlower(string.gsub(_G.TEXT_MODE_A_STRING_RESULT_CRITICAL, "[()]", ""))
end

----------------------------------------------------
-- functions
----------------------------------------------------
local function UpdateDeathLog(guid, ...)
    if not deathLogs[guid] then
        deathLogs[guid] = {}
    end

    deathLogs[guid]["time"], deathLogs[guid]["type"], deathLogs[guid]["name"], deathLogs[guid]["ability"],
    deathLogs[guid]["school"], deathLogs[guid]["amount"], deathLogs[guid]["overkill"], deathLogs[guid]["resisted"],
    deathLogs[guid]["blocked"], deathLogs[guid]["absorbed"], deathLogs[guid]["critical"], deathLogs[guid]["sourceName"] = ...

    deathLogs[guid]["reported"] = false
end

local function Send(msg)
    -- F.Print(strupper(ACTION_UNIT_DIED)..": "..msg)
    if Cell.hasHighestPriority then
        if IsInGroup(LE_PARTY_CATEGORY_INSTANCE) then
            SendChatMessage(strupper(ACTION_UNIT_DIED)..": "..msg, "INSTANCE_CHAT")
        else
            SendChatMessage(strupper(ACTION_UNIT_DIED)..": "..msg, IsInRaid() and "RAID" or "PARTY")
        end
    end
end

local function Report(guid)
    if not deathLogs[guid] or deathLogs[guid]["reported"] then return end
    deathLogs[guid]["reported"] = true

    if instanceType == "raid" and IsEncounterInProgress() then
        count = count + 1
        if count > limit then
            return
        end
    end

    if not deathLogs[guid]["type"] or time()-deathLogs[guid]["time"]>=1 then -- unkown
        -- Send(deathLogs[guid]["name"].." > "..strlower(_G.UNKNOWN))
        Send(deathLogs[guid]["name"])

    elseif deathLogs[guid]["type"] == "INSTAKILL" then
        Send(deathLogs[guid]["name"].." > "..L["instakill"])

    elseif deathLogs[guid]["type"] == "ENVIRONMENTAL" then
        Send(deathLogs[guid]["name"].." > "..F.FormatNumber(deathLogs[guid]["amount"]).." ("..deathLogs[guid]["ability"]..")")

    else -- SPELL & RANGE & SWING
        -- local damageDetails = {}
        local damageDetails = ""

        if deathLogs[guid]["overkill"] > 0 then
            -- tinsert(damageDetails, string.format(overkillFormat, F.FormatNumber(deathLogs[guid]["overkill"])))
            damageDetails = " ("..string.format(overkillFormat, F.FormatNumber(deathLogs[guid]["overkill"]))..") "
        end
        -- if deathLogs[guid]["critical"] == 1 then
        --     tinsert(damageDetails, criticalText)
        -- end
        -- if deathLogs[guid]["resisted"] then
        --     tinsert(damageDetails, string.format(resistedFormat, F.FormatNumber(deathLogs[guid]["resisted"])))
        -- end
        -- if deathLogs[guid]["blocked"] then
        --     tinsert(damageDetails, string.format(blockedFormat, F.FormatNumber(deathLogs[guid]["blocked"])))
        -- end
        -- if deathLogs[guid]["absorbed"] then
        --     tinsert(damageDetails, string.format(absorbedFormat, F.FormatNumber(deathLogs[guid]["absorbed"])))
        -- end

        -- damageDetails = table.concat(damageDetails, ", ")

        local sourceName = (deathLogs[guid]["sourceName"] and deathLogs[guid]["name"]~=deathLogs[guid]["sourceName"]) and (" ["..deathLogs[guid]["sourceName"].."]") or ""
        local ability

        if deathLogs[guid]["type"] == "SPELL" then -- including RANGE
            -- tinsert(damageDetails, strlower(CombatLog_String_SchoolString(deathLogs[guid]["school"])))
            ability = deathLogs[guid]["ability"]
        else -- SWING
            ability = strlower(_G.MELEE)
        end

        -- damageDetails = table.concat(damageDetails, ", ")
        -- if damageDetails ~= "" then damageDetails = " ("..damageDetails..") " end
        Send(deathLogs[guid]["name"].." > "..ability.." "..F.FormatNumber(deathLogs[guid]["amount"])..damageDetails..sourceName)
    end

    -- wipe(deathLogs[guid])
end

----------------------------------------------------
-- event
----------------------------------------------------
local frame = CreateFrame("Frame")
-- frame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")

function frame:PLAYER_ENTERING_WORLD()
    local isIn, iType = IsInInstance()
    instanceType = iType

    if instanceType == "pvp" or instanceType == "arena" then
        frame:UnregisterEvent("ENCOUNTER_START")
        frame:UnregisterEvent("ENCOUNTER_END")
        frame:UnregisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
        frame:UnregisterEvent("GROUP_ROSTER_UPDATE")
        return
    else
        frame:RegisterEvent("GROUP_ROSTER_UPDATE")
    end

    if not init then frame:GROUP_ROSTER_UPDATE() end
    if isIn then
        inInstance = true
        if instanceType == "raid" then
            frame:RegisterEvent("ENCOUNTER_START")
            count = 0
        else
            frame:UnregisterEvent("ENCOUNTER_START")
        end
    elseif inInstance then -- left insntance
        inInstance = false
        wipe(deathLogs)
        frame:UnregisterEvent("ENCOUNTER_START")
    end
    -- texplore(deathLogs)
end

local timer
function frame:GROUP_ROSTER_UPDATE()
    if IsInGroup() then
        if IsEncounterInProgress() then
            frame:RegisterEvent("ENCOUNTER_END")
        else
            if timer then timer:Cancel() end
            timer = C_Timer.NewTimer(7, function()
                F.CheckPriority()
            end)
        end
    else
        frame:UnregisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
    end
    init = true
end

function frame:ENCOUNTER_END()
    frame:UnregisterEvent("ENCOUNTER_END")
    frame:GROUP_ROSTER_UPDATE()
end

function frame:ENCOUNTER_START()
    count = 0
end

function frame:COMBAT_LOG_EVENT_UNFILTERED(...)
    local timestamp, event, hideCaster, sourceGUID, sourceName, sourceFlags, sourceRaidFlags, destGUID, destName, destFlags, destRaidFlags, arg12, arg13, arg14 = ...
    local amount, overkill, school, resisted, blocked, absorbed, critical -- glancing, crushing

    -- arg12, arg13, arg14,
    -- UNIT_DIED: recapID, unconsciousOnDeath
    -- ENVIRONMENTAL: environmentalType
    -- SPELL/RANGE: spellId, spellName, spellSchool

    -- if string.find(destGUID, "^Player") then -- debug
    if string.find(destGUID, "^Player") and F.IsFriend(destFlags) then
        if event == "SPELL_INSTAKILL" then
            UpdateDeathLog(destGUID, timestamp, "INSTAKILL", destName)
        end

        if event == "ENVIRONMENTAL_DAMAGE" then
            amount, overkill, school, resisted, blocked, absorbed, critical = select(13, ...)
            amount = amount == 0 and absorbed or amount
            -- _G.ENVIRONMENTAL_DAMAGE.." "..
            UpdateDeathLog(destGUID, timestamp, "ENVIRONMENTAL", destName, strlower(_G["ACTION_ENVIRONMENTAL_DAMAGE_" .. strupper(arg12)]), nil, amount)
        end

        if event == "SWING_DAMAGE" then
            amount, overkill, school, resisted, blocked, absorbed, critical = select(12, ...)
            UpdateDeathLog(destGUID, timestamp, "SWING", destName, nil, school, amount, overkill or -1, resisted, blocked, absorbed, critical, sourceName)
        end

        if event == "SPELL_DAMAGE" or event == "SPELL_PERIODIC_DAMAGE" or event == "RANGE_DAMAGE" then
            if not blacklist[arg12] then
                amount, overkill, school, resisted, blocked, absorbed, critical = select(15, ...)
                local spellLink = GetSpellLink(arg12)
                UpdateDeathLog(destGUID, timestamp, "SPELL", destName, spellLink, school, amount, overkill or -1, resisted, blocked, absorbed, critical, sourceName)
            end
        end

        if event == "SPELL_AURA_APPLIED" then
            -- print(arg12, arg13, arg14)
            if arg12 == 27827 or arg12 == 358164 then -- 救赎之魂 or 灵魂疲惫
                C_Timer.After(0.25, function()
                    Report(destGUID)
                end)
            end
        end

        if event == "UNIT_DIED" and not UnitIsFeignDeath(destName) then
            C_Timer.After(0.5, function()
                if not deathLogs[destGUID] then deathLogs[destGUID] = {["name"]=destName} end
                Report(destGUID)
            end)
        end
    end
end

frame:SetScript("OnEvent", function(self, event, ...)
    if event == "COMBAT_LOG_EVENT_UNFILTERED" then
        self:COMBAT_LOG_EVENT_UNFILTERED(CombatLogGetCurrentEventInfo())
    else
        self[event](self, ...)
    end
end)

----------------------------------------------------
-- priority
----------------------------------------------------
local function UpdatePriority(hasHighestPriority)
    if hasHighestPriority and CellDB["tools"]["deathReport"][1] then
        frame:RegisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
    else
        frame:UnregisterEvent("COMBAT_LOG_EVENT_UNFILTERED")
    end
end
Cell.RegisterCallback("UpdatePriority", "DeathReport_UpdatePriority", UpdatePriority)

----------------------------------------------------
-- UpdateTools
----------------------------------------------------
local enabled
local function UpdateTools(which)
    if not which or which == "deathReport" then
        if CellDB["tools"]["deathReport"][1] then
            frame:RegisterEvent("PLAYER_ENTERING_WORLD")
            frame:RegisterEvent("GROUP_ROSTER_UPDATE")

            limit = CellDB["tools"]["deathReport"][2]
            count = 0
            if not enabled and which == "deathReport" then -- already in world, manually enabled
                frame:PLAYER_ENTERING_WORLD()
            end
            enabled = true
        else
            frame:UnregisterAllEvents()
            wipe(deathLogs)
            enabled = false
        end
    end
end
Cell.RegisterCallback("UpdateTools", "DeathReport_UpdateTools", UpdateTools)