-- New Event in 12.0: FACTION_STANDING_CHANGED (payloads: factionID, updatedStanding)
-- It doesn't show the delta value so we need to cache it


local _, addon = ...


local InCombatLockdown = InCombatLockdown;
local GetInstanceInfo = GetInstanceInfo;
local GetFactionDataByID = C_Reputation.GetFactionDataByID;
local IsMajorFaction = C_Reputation.IsMajorFaction;
local GetFriendshipReputation = C_GossipInfo.GetFriendshipReputation;


local EL = CreateFrame("Frame");
EL.factionCache = {};

EL.staticEvents = {
    "FACTION_STANDING_CHANGED",
    "PLAYER_ENTERING_BATTLEGROUND",
    "PLAYER_ENTERING_WORLD",
};

EL.factionRemap = {
    -- When one of Severed Threads rep changes, it triggers the major faction standing changed 3 times
    [2600] = {2601, 2605, 2607},
};

local MAJOR_FACTION_THRESHOLD = 2500;

function EL:CacheDefaultReputations()
    -- Include major factions and the currently watched faction

    local factions = {
        2601, 2605, 2607, 2669, 2673, 2677, 2675, 2671,
    };

    local majorFactions = C_MajorFactions.GetMajorFactionIDs();
    if majorFactions then
        for _, factionID in ipairs(majorFactions) do
            table.insert(factions, factionID);
        end
    end

    local watchedFactionData = C_Reputation.GetWatchedFactionData();
    if watchedFactionData then
        table.insert(factions, watchedFactionData.factionID);
    end

    local delvesFactionID = C_DelvesUI.GetDelvesFactionForSeason();
    if delvesFactionID then
        table.insert(factions, delvesFactionID);
    end

    for _, factionID in ipairs(factions) do
        self:CacheFaction(factionID);
    end
end

function EL:CalculateMajorFactionEffectiveStanding(factionID)
    local data = C_MajorFactions.GetMajorFactionRenownInfo(factionID);
    if data then
        --renownLevel may still be the old value when it just levels up
        local value = data.renownLevel * MAJOR_FACTION_THRESHOLD + (data.renownReputationEarned or 0);

        if C_MajorFactions.HasMaximumRenown(factionID) then
            local extra = C_Reputation.GetFactionParagonInfo(factionID);
            value = value + (extra or 0);
        end

        return value
    end
end

function EL:TryAddParagonStanding(factionID, baseStanding)
    if not baseStanding then return end;
    if C_Reputation.IsFactionParagon(factionID) then
        local extra = C_Reputation.GetFactionParagonInfo(factionID);
        return baseStanding + extra;
    end
    return baseStanding
end

function EL:CacheMajorFaction(factionID)
    if not self.factionCache[factionID] then
        local data = C_MajorFactions.GetMajorFactionData(factionID);
        if data then
            local standing = self:CalculateMajorFactionEffectiveStanding(factionID);
            if standing then
                self.factionCache[factionID] = {
                    name = data.name,
                    standing = standing,
                    isMajorFaction = true,
                };
                --print(factionID, data.name, standing)
            end
        end
    end
end

function EL:CacheStandardFaction(factionID)
    if not self.factionCache[factionID] then
        local data = GetFactionDataByID(factionID);
        if data then
            local standing = self:TryAddParagonStanding(factionID, data.currentStanding);
            self.factionCache[factionID] = {
                name = data.name,
                standing = standing,
            };
            --print(factionID, data.name, standard)
        end
    end
end

function EL:CacheFriendshipFaction(factionID)
    if not self.factionCache[factionID] then
        local data = GetFriendshipReputation(factionID);
        if data then
            local standing = self:TryAddParagonStanding(factionID, data.standing);
            self.factionCache[factionID] = {
                name = data.name,
                standing = standing,
                isFriendship = true,
            };
            --print(factionID, data.name, standing)
        end
    end
end

function EL:CacheFaction(factionID)
    local isMajorFaction = IsMajorFaction(factionID);
    if isMajorFaction then
        self:CacheMajorFaction(factionID);
    else
        local data = GetFriendshipReputation(factionID);
        if data and data.friendshipFactionID > 0 then
            self:CacheFriendshipFaction(factionID);
        else
            self:CacheStandardFaction(factionID);
        end
    end
end

function EL:OnEvent(event, ...)
    if event == "FACTION_STANDING_CHANGED" then
        self:SetFactionStanding(...);
    elseif event == "PLAYER_REGEN_ENABLED" or event == "PLAYER_ENTERING_WORLD" or event == "PLAYER_ENTERING_BATTLEGROUND" then
        self:RequestUpdate();
    end
end

function EL:SetFactionStanding(factionID, updatedStanding)
    if not self.factionCache[factionID] then
        self:CacheFaction(factionID);
        return
    end

    if self.factionRemap[factionID] then
        for _, _factionID in ipairs(self.factionRemap[factionID]) do
            self:SetFactionStanding(_factionID);
        end
    end

    local info = self.factionCache[factionID];
    if info then
        if self.suppressed then
            self.factionDirty[factionID] = true;
            return
        end

        if InCombatLockdown() then
            self.factionDirty[factionID] = true;
            self:RegisterEvent("PLAYER_REGEN_ENABLED");
            return
        end

        self.factionDirty[factionID] = nil;

        local delta, newStanding;
        if info.isMajorFaction then
            newStanding = self:CalculateMajorFactionEffectiveStanding(factionID);
        elseif info.isFriendship then
            if not updatedStanding then
                local data = GetFriendshipReputation(factionID);
                updatedStanding = data and data.standing;
                updatedStanding = self:TryAddParagonStanding(factionID, updatedStanding);
            end
            newStanding = updatedStanding;
        else
            if not updatedStanding then
                local data = GetFactionDataByID(factionID);
                updatedStanding = data and data.currentStanding;
                updatedStanding = self:TryAddParagonStanding(factionID, updatedStanding);
            end
            newStanding = updatedStanding;
        end

        if newStanding then
            delta = newStanding - info.standing;
            info.standing = newStanding;
            if info.isMajorFaction and delta > MAJOR_FACTION_THRESHOLD then
                --Assume rep granted one time can't exceed 2500
                delta = delta - MAJOR_FACTION_THRESHOLD;
            end
        end

        --print(factionID, info.name, newStanding, delta);

        if delta and delta > 0 then
            addon.LootWindow:QueueDisplayReputation(factionID, info.name, delta);
        end
    end
end

function EL:RequestUpdate()
    self.t = 0;
    self:SetScript("OnUpdate", self.OnUpdate);
end

function EL:OnUpdate(elapsed)
    self.t = self.t + elapsed;
    if self.t > 1 then
        self.t = 0;
        self:SetScript("OnUpdate", nil);

        local _, instanceType = GetInstanceInfo();
        if instanceType == "arena" or instanceType == "pvp" then
            self.suppressed = true;
            return
        else
            self.suppressed = nil;
        end

        if InCombatLockdown() then
            self:RegisterEvent("PLAYER_REGEN_ENABLED");
            return
        else
            self:UnregisterEvent("PLAYER_REGEN_ENABLED");
        end

        for factionID in pairs(self.factionDirty) do
            self.factionDirty[factionID] = nil;
            self:SetFactionStanding(factionID);
        end
    end
end

function EL:RequestCacheReputations()
    C_Timer.After(1, function()
        if self.enabled then
            self:CacheDefaultReputations();
        end
    end);
end

function EL:EnableModule()
    if not self.enabled then
        self.enabled = true;
        self.factionCache = {};
        self.factionDirty = {};
        if C_EventUtils.IsEventValid("FACTION_STANDING_CHANGED") then
            self:RequestCacheReputations();
            self:RequestUpdate();
            addon.API.RegisterFrameForEvents(self, self.staticEvents);
            self:SetScript("OnEvent", self.OnEvent);
        end
    end
end

function EL:DisableModule()
    if self.enabled then
        self.enabled = nil;
        if C_EventUtils.IsEventValid("FACTION_STANDING_CHANGED") then
            addon.API.UnregisterFrameForEvents(self, self.staticEvents);
            self:UnregisterEvent("PLAYER_REGEN_ENABLED");
            self:SetScript("OnEvent", nil);
            self:SetScript("OnUpdate", nil);
        end
    end
end

addon.CallbackRegistry:RegisterCallback("SettingChanged.LootUI_ShowReputation", function(state)
    if state then
        EL:EnableModule();
    else
        EL:DisableModule();
    end
end);
