using System;
using UnityEngine;
using SlugBase.Features;
using static SlugBase.Features.FeatureTypes;
using MonoMod.Cil;
using Mono.Cecil.Cil;
using RWCustom;

namespace JilloSlug.Features;

internal static class BounceFeature {
    // 0 = no bounce
    // 1 = preserve all velocity upon bouncing
    public static readonly PlayerFeature<float> Bounce = PlayerFloat("jillo/bounce");

    public static void AddHooks() {
        IL.Player.TerrainImpact += Player_TerrainImpact;
    }

    private static bool PlayerHandleBounce(Player player, IntVector2 direction, float speed) {
        float bounce;
        Bounce.TryGet(player, out bounce);
        bool shouldBounce = !player.dead && bounce >= 0;
        if (!shouldBounce) return true;

        if (speed > 0.5f) {
            player.room.PlaySound(SoundID.Slime_Mold_Terrain_Impact, player.mainBodyChunk, false, 0.7f + Mathf.Clamp(speed / 60f, 0f, 1f), 1f);
        }

        if (speed > 8f) {
            // handle bouncing
            foreach (var chunk in player.bodyChunks) {
                chunk.vel.x = Mathf.Lerp(chunk.vel.x, -chunk.vel.x * bounce, Mathf.Abs((float) direction.x));
                chunk.vel.y = Mathf.Lerp(chunk.vel.y, -chunk.vel.y * bounce, Mathf.Abs((float) direction.y));
            }
        }

        return false; // skip everything
    }

    private static void ILInsertHandleBounce(ILCursor c, ILLabel skipLabel) {
        c.Emit(OpCodes.Ldarg_0);
        c.Emit(OpCodes.Ldarg_2); // direction
        c.Emit(OpCodes.Ldarg_3); // speed
        c.EmitDelegate<Func<Player, IntVector2, float, bool>>((player, direction, speed) => {
            return PlayerHandleBounce(player, direction, speed);
        });
        c.Emit(OpCodes.Brfalse, skipLabel);
    }

    private static void Player_TerrainImpact(ILContext il) {
        ILCursor c = new ILCursor(il);

        // patch high speed impact (death condition)

        // matching for `speed > num && direction.y < 0`
        c.GotoNext(MoveType.After,
            // speed > num
            i => i.MatchLdarg(3),
            i => i.MatchLdloc(0),
            i => i.Match(OpCodes.Ble_Un_S),

            // direction.y < 0
            i => i.MatchLdarg(2),
            i => i.MatchLdfld<IntVector2>("y"),
            i => i.Match(OpCodes.Ldc_I4_0),
            i => i.Match(OpCodes.Bge_S)
        );

        // retrieve the label the end of the if would skip to
        // (illegal bytecode crimes)
        ILCursor endC = c.Clone();
        endC.GotoNext(MoveType.After, i => i.MatchCallOrCallvirt<Creature>("Die"));
        ILLabel skipLabel = endC.Next.Operand as ILLabel;

        ILInsertHandleBounce(c, skipLabel);

        // patch mid speed impact (stun condition)

        // matching for `speed > num2`
        c.GotoNext(MoveType.After,
            // speed > num2
            i => i.MatchLdarg(3),
            i => i.MatchLdloc(1),
            i => i.Match(OpCodes.Ble_Un)
        );

        ILInsertHandleBounce(c, skipLabel);

        // patch low speed impacts

        // matching for `direction.y < 0 && base.Consious`
        c.GotoNext(MoveType.After,
            i => i.MatchLdarg(2),
            i => i.MatchLdfld<IntVector2>("y"),
            i => i.Match(OpCodes.Ldc_I4_0),
            i => i.Match(OpCodes.Bge_S),

            i => i.MatchLdarg(0),
            i => i.MatchCallOrCallvirt<Creature>("get_Consious"),
            i => i.Match(OpCodes.Brfalse_S)
        );

        ILInsertHandleBounce(c, skipLabel);

        // matching for `this.room.PlaySound(SoundID.Slugcat_Terrain_Impact_Light, ...);`
        c.GotoNext(MoveType.Before,
            i => i.MatchLdarg(0),
            i => i.MatchLdfld<UpdatableAndDeletable>("room"),
            i => i.MatchLdsfld<SoundID>("Slugcat_Terrain_Impact_Light")
        );

        ILInsertHandleBounce(c, skipLabel);

        // matching for `this.room.PlaySound(SoundID.Slugcat_Terrain_Impact_Medium, ...);`
        c.GotoNext(MoveType.Before,
            i => i.MatchLdarg(0),
            i => i.MatchLdfld<UpdatableAndDeletable>("room"),
            i => i.MatchLdsfld<SoundID>("Slugcat_Terrain_Impact_Medium")
        );

        ILInsertHandleBounce(c, skipLabel);

        // matching for `this.room.PlaySound(SoundID.Slugcat_Terrain_Impact_Hard, ...;`
        c.GotoNext(MoveType.Before,
            i => i.MatchLdarg(0),
            i => i.MatchLdfld<UpdatableAndDeletable>("room"),
            i => i.MatchLdsfld<SoundID>("Slugcat_Terrain_Impact_Hard")
        );

        ILInsertHandleBounce(c, skipLabel);
    }
}
