#include "mod_instance_release.h"
#include "Battleground.h"
#include "Config.h"
#include "Map.h"
#include "ObjectAccessor.h"
#include "ObjectMgr.h"
#include "Opcodes.h"
#include "ScriptMgr.h"
#include "WorldPacket.h"

// SPELL_RESURRECTION_VISUAL (24171, "Resurrection Impact Visual") comes from
// Battleground.h's BattlegroundSpells enum - it's the same purely-cosmetic
// spell the battleground spirit-heal system casts on a player right after
// resurrecting them.

namespace
{
// A generic, purely-cosmetic "teleport in" flash reused across unrelated
// content in this codebase (Sunwell Kil'jaeden, Arcatraz, the Test Flight
// quest) - no gameplay effect, safe to reuse here for the arrival visual.
constexpr uint32 SPELL_INSTANCE_RELEASE_ARRIVAL_VISUAL = 35517;

// A same-map TeleportTo() relocates the player server-side immediately, but
// the client doesn't visually snap to the new position until it acks the
// teleport (MSG_MOVE_TELEPORT_ACK). Casting a visual spell synchronously
// right after TeleportTo() sends it while the client is still mid-transition,
// so it would appear to play at the old (death) spot instead of the entrance.
// Deferring a beat lets the client catch up first.
class DelayedArrivalVisualEvent : public BasicEvent
{
public:
    explicit DelayedArrivalVisualEvent(ObjectGuid playerGuid) : _playerGuid(playerGuid) { }

    bool Execute(uint64 /*time*/, uint32 /*diff*/) override
    {
        Player* player = ObjectAccessor::FindConnectedPlayer(_playerGuid);
        if (player && player->IsInWorld() && player->IsAlive())
            player->CastSpell(player, SPELL_INSTANCE_RELEASE_ARRIVAL_VISUAL, true);

        return true;
    }

private:
    ObjectGuid _playerGuid;
};
} // namespace

void InstanceRelease::LoadConfig()
{
    _enabled = sConfigMgr->GetOption("InstanceRelease.Enable", true);
    _includeDungeons = sConfigMgr->GetOption("InstanceRelease.IncludeDungeons", true);
    _includeRaids = sConfigMgr->GetOption("InstanceRelease.IncludeRaids", true);
    _autoResurrect = sConfigMgr->GetOption("InstanceRelease.AutoResurrect", true);
    _resurrectVisual = sConfigMgr->GetOption("InstanceRelease.ResurrectionVisual", true);
    _arrivalVisual = sConfigMgr->GetOption("InstanceRelease.ArrivalVisual", true);
}

bool InstanceRelease::TryReleaseAtEntrance(Player* player) const
{
    if (!_enabled)
        return false;

    Map* map = player->GetMap();
    if (!map || !map->IsDungeon())
        return false;

    if (map->IsRaid() ? !_includeRaids : !_includeDungeons)
        return false;

    AreaTriggerTeleport const* entrance = sObjectMgr->GetMapEntranceTrigger(player->GetMapId());
    if (!entrance)
        return false;

    player->TeleportTo(entrance->target_mapId, entrance->target_X, entrance->target_Y, entrance->target_Z, entrance->target_Orientation);

    if (_autoResurrect && player->isDead())
    {
        player->ResurrectPlayer(1.0f);
        player->SpawnCorpseBones();

        if (_resurrectVisual)
            player->CastSpell(player, SPELL_RESURRECTION_VISUAL, true);

        if (_arrivalVisual)
            player->m_Events.AddEvent(new DelayedArrivalVisualEvent(player->GetGUID()), player->m_Events.CalculateTime(250));
    }
    else if (player->isDead())
    {
        WorldPacket data(SMSG_DEATH_RELEASE_LOC, 4 * 4);
        data << entrance->target_mapId;
        data << entrance->target_X;
        data << entrance->target_Y;
        data << entrance->target_Z;
        player->SendDirectMessage(&data);
    }

    return true;
}

class InstanceRelease_Player : public PlayerScript
{
public:
    InstanceRelease_Player() : PlayerScript("InstanceRelease_Player") { }

    bool OnPlayerCanRepopAtGraveyard(Player* player) override
    {
        return !sInstanceRelease->TryReleaseAtEntrance(player);
    }
};

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

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

void AddSC_mod_instance_release()
{
    new InstanceRelease_Player();
    new InstanceRelease_World();
}