#include "mod_shared_kill_credit.h"
#include "Config.h"
#include "Creature.h"
#include "Group.h"
#include "ObjectAccessor.h"
#include "Player.h"
#include "ScriptMgr.h"

void SharedKillCredit::LoadConfig()
{
    _enabled = sConfigMgr->GetOption("SharedKillCredit.Enable", true);
}

void SharedKillCredit::RecordDamage(Unit* attacker, Unit* victim)
{
    if (!_enabled || !attacker || !victim || !victim->IsCreature())
        return;

    Player* player = attacker->IsPlayer() ? attacker->ToPlayer() : attacker->GetCharmerOrOwnerPlayerOrPlayerItself();
    if (!player)
        return;

    std::lock_guard lock(_mutex);
    _taggersByCreature[victim->GetGUID()].insert(player->GetGUID());
}

void SharedKillCredit::RewardTaggers(Unit* victim)
{
    if (!_enabled || !victim)
        return;

    Creature* creature = victim->ToCreature();
    if (!creature)
        return;

    std::unordered_set taggers;
    {
        std::lock_guard lock(_mutex);
        auto it = _taggersByCreature.find(creature->GetGUID());
        if (it == _taggersByCreature.end())
            return;

        taggers = std::move(it->second);
        _taggersByCreature.erase(it);
    }

    // Players credited through the normal tap/group reward path (KillRewarder)
    // must not be credited again here, or their quest kill counters would be
    // double-incremented.
    ObjectGuid const recipient = creature->GetLootRecipientGUID();
    ObjectGuid::LowType const recipientGroup = creature->GetLootRecipientGroupGUID();

    for (ObjectGuid const& taggerGuid : taggers)
    {
        if (taggerGuid == recipient)
            continue;

        Player* tagger = ObjectAccessor::FindConnectedPlayer(taggerGuid);
        if (!tagger || !tagger->IsInWorld())
            continue;

        if (recipientGroup && tagger->GetGroup() && tagger->GetGroup()->GetGUID().GetCounter() == recipientGroup)
            continue;

        // Give the same nearby-at-death eligibility that group members already
        // get for a shared kill, rather than rewarding a tagger who has long
        // since left the area.
        if (!tagger->IsAtGroupRewardDistance(creature))
            continue;

        tagger->KilledMonster(creature->GetCreatureTemplate(), creature->GetGUID());
    }
}

void SharedKillCredit::ForgetCreature(ObjectGuid const& creatureGuid)
{
    std::lock_guard lock(_mutex);
    _taggersByCreature.erase(creatureGuid);
}

class SharedKillCredit_Unit : public UnitScript
{
public:
    SharedKillCredit_Unit() : UnitScript("SharedKillCredit_Unit", true, { UNITHOOK_ON_DAMAGE, UNITHOOK_ON_UNIT_DEATH }) { }

    void OnDamage(Unit* attacker, Unit* victim, uint32& /*damage*/) override
    {
        sSharedKillCredit->RecordDamage(attacker, victim);
    }

    void OnUnitDeath(Unit* unit, Unit* /*killer*/) override
    {
        sSharedKillCredit->RewardTaggers(unit);
    }
};

class SharedKillCredit_AllCreature : public AllCreatureScript
{
public:
    SharedKillCredit_AllCreature() : AllCreatureScript("SharedKillCredit_AllCreature") { }

    void OnCreatureRemoveWorld(Creature* creature) override
    {
        sSharedKillCredit->ForgetCreature(creature->GetGUID());
    }
};

class SharedKillCredit_World : public WorldScript
{
public:
    SharedKillCredit_World() : WorldScript("SharedKillCredit_World") { }

    void OnBeforeConfigLoad(bool /*reload*/) override
    {
        sSharedKillCredit->LoadConfig();
    }
};

void AddSC_mod_shared_kill_credit()
{
    new SharedKillCredit_Unit();
    new SharedKillCredit_AllCreature();
    new SharedKillCredit_World();
}