// WingsEmu
// 
// Developed by NosWings Team

using System;
using WingsEmu.DTOs.Maps;
using WingsEmu.Game;
using WingsEmu.Game.Battle;
using WingsEmu.Game.Buffs;
using WingsEmu.Game.Characters;
using WingsEmu.Game.Entities;
using WingsEmu.Game.Extensions;
using WingsEmu.Game.Helpers.Damages;
using WingsEmu.Game.Maps;
using WingsEmu.Packets.Enums;

namespace WingsEmu.Plugins.BasicImplementations.BCards.Handlers;

public class BCardJumpBackPushHandler : IBCardEffectAsyncHandler
{
    private readonly IRandomGenerator _random;

    public BCardJumpBackPushHandler(IRandomGenerator random) => _random = random;

    public BCardType HandledType => BCardType.JumpBackPush;

    public void Execute(IBCardEffectContext ctx)
    {
        IBattleEntity sender = ctx.Sender;
        IBattleEntity target = ctx.Target;
        int firstData = ctx.BCard.FirstData;

        if (_random.RandomNumber() > firstData)
        {
            return;
        }

        int secondData = ctx.BCard.SecondData;
        switch (ctx.BCard.SubType)
        {
            case (byte)AdditionalTypes.JumpBackPush.JumpBackChance:
                short sX = target.PositionX;
                short sY = target.PositionY;
                short tX = sender.PositionX;
                short tY = sender.PositionY;

                int distance = sender.GetDistance(target);
                if (distance <= 0)
                {
                    distance = 1;
                }

                int d = distance + secondData;

                short maxX = (short)(sX - d * (sX - tX) / distance);
                short maxY = (short)(sY - d * (sY - tY) / distance);

                int dx = Math.Abs(maxX - tX);
                int sx = tX < maxX ? 1 : -1;
                int dy = -Math.Abs(maxY - tY);
                int sy = tY < maxY ? 1 : -1;
                int err = dx + dy;

                short lastX = tX;
                short lastY = tY;

                while (true)
                {
                    if (sender.MapInstance.IsBlockedZone(tX, tY))
                    {
                        tX = lastX;
                        tY = lastY;
                        break;
                    }

                    lastX = tX;
                    lastY = tY;

                    if (tX == maxX && tY == maxY)
                    {
                        break;
                    }

                    int e2 = 2 * err;
                    if (e2 >= dy)
                    {
                        err += dy;
                        tX += (short)sx;
                    }

                    if (e2 > dx)
                    {
                        continue;
                    }

                    err += dx;
                    tY += (short)sy;
                }

                if (sender is IPlayerEntity playerEntity && playerEntity.MapInstance != null && playerEntity.MapInstance.HasMapFlag(MapFlags.IS_BASE_MAP))
                {
                    playerEntity.MapX = tX;
                    playerEntity.MapY = tY;
                }

                sender.MapInstance?.Broadcast(sender.GeneratePushPacket(tX, tY, 2));
                sender.ChangePosition(new Position(tX, tY));
                break;
            case (byte)AdditionalTypes.JumpBackPush.PushBackChance:
                if (!target.IsAlive())
                {
                    return;
                }

                if (target is IMonsterEntity mapMonster && !mapMonster.CanBePushed)
                {
                    return;
                }

                (int resistance, _) = target.BCardComponent.GetAllBCardsInformation(BCardType.AbsorbedSpirit, (byte)AdditionalTypes.AbsorbedSpirit.ResistForcedMovement, target.Level);
                if (resistance != 0 && _random.RandomNumber() <= resistance)
                {
                    return;
                }

                sX = target.PositionX;
                sY = target.PositionY;
                tX = sender.PositionX;
                tY = sender.PositionY;

                distance = sender.GetDistance(target);
                if (distance <= 0)
                {
                    distance = 1;
                }

                d = distance + secondData;

                maxX = (short)(tX + d * (sX - tX) / distance);
                maxY = (short)(tY + d * (sY - tY) / distance);

                dx = Math.Abs(maxX - tX);
                sx = tX < maxX ? 1 : -1;
                dy = -Math.Abs(maxY - tY);
                sy = tY < maxY ? 1 : -1;
                err = dx + dy;

                lastX = tX;
                lastY = tY;

                while (true)
                {
                    if (sender.MapInstance.IsBlockedZone(tX, tY))
                    {
                        tX = lastX;
                        tY = lastY;
                        break;
                    }

                    lastX = tX;
                    lastY = tY;

                    if (tX == maxX && tY == maxY)
                    {
                        break;
                    }

                    int e2 = 2 * err;
                    if (e2 >= dy)
                    {
                        err += dy;
                        tX += (short)sx;
                    }

                    if (e2 > dx)
                    {
                        continue;
                    }

                    err += dx;
                    tY += (short)sy;
                }

                if (target is IPlayerEntity player && player.MapInstance != null && player.MapInstance.HasMapFlag(MapFlags.IS_BASE_MAP))
                {
                    player.MapX = tX;
                    player.MapY = tY;
                }

                sender.MapInstance?.Broadcast(target.GeneratePushPacket(tX, tY, 2));
                target.ChangePosition(new Position(tX, tY));
                break;
        }
    }
}