using Cirno.Scripts.Components;
using Godot;

namespace Cirno.Scripts.Resources;

[GlobalClass]
[Tool]
public partial class SpeedModifier : BulletCreationModifier, IBulletModifier
{
    [Export] public SpeedModifierType ModifierType;
    [Export] public EasingType Easing;
    [Export] public bool Invert = false;
    [Export] public float MinimumSpeed = 10f;
    [Export] public float ScalingFactor = 10.0f;

    public override BulletInfo ModifyBullet(BulletInfo bullet, int bulletIndex, int totalBullets)
    {
        bullet.Speed = ModifySpeed(bullet.OriginalBulletResource.BulletSpeed, bulletIndex, totalBullets);
        return bullet;
    }
    
    public float ModifySpeed(float baseSpeed, int bulletIndex, int totalBullets)
    {
        if (totalBullets <= 1)
            return baseSpeed;

        float t = (float)bulletIndex / (totalBullets - 1); // Normalize to [0,1]
        if (Invert)
            t = 1 - t;
        t = ApplyEasing(t);
        
        return Mathf.Lerp(MinimumSpeed, baseSpeed, t);
    }

    private float ApplyEasing(float t)
    {
        switch (ModifierType)
        {
            case SpeedModifierType.Linear:
                return t;
            case SpeedModifierType.Quad:
                return Easing == EasingType.In ? t * t : Easing == EasingType.Out ? 1 - (1 - t) * (1 - t) : (t < 0.5f ? 2 * t * t : 1 - Mathf.Pow(-2 * t + 2, 2) / 2);
            case SpeedModifierType.Sine:
                return Easing == EasingType.In ? 1 - Mathf.Cos((t * Mathf.Pi) / 2) : Easing == EasingType.Out ? Mathf.Sin((t * Mathf.Pi) / 2) : -(Mathf.Cos(Mathf.Pi * t) - 1) / 2;
            case SpeedModifierType.Exponential:
                return Easing == EasingType.In ? Mathf.Pow(2, 10 * (t - 1)) : Easing == EasingType.Out ? 1 - Mathf.Pow(2, -10 * t) : (t < 0.5f ? Mathf.Pow(2, 10 * (2 * t - 1)) / 2 : (2 - Mathf.Pow(2, -10 * (2 * t - 1))) / 2);
            case SpeedModifierType.Quint:
                return Easing == EasingType.In ? t * t * t * t * t : Easing == EasingType.Out ? 1 - Mathf.Pow(1 - t, 5) : (t < 0.5f ? 16 * t * t * t * t * t : 1 - Mathf.Pow(-2 * t + 2, 5) / 2);
            case SpeedModifierType.Circ:
                return Easing == EasingType.In ? 1 - Mathf.Sqrt(1 - t * t) : Easing == EasingType.Out ? Mathf.Sqrt(1 - Mathf.Pow(t - 1, 2)) : (t < 0.5f ? (1 - Mathf.Sqrt(1 - 4 * t * t)) / 2 : (Mathf.Sqrt(1 - Mathf.Pow(-2 * t + 2, 2)) + 1) / 2);
            case SpeedModifierType.Cubic:
                return Easing == EasingType.In ? t * t * t : Easing == EasingType.Out ? 1 - Mathf.Pow(1 - t, 3) : (t < 0.5f ? 4 * t * t * t : 1 - Mathf.Pow(-2 * t + 2, 3) / 2);
            case SpeedModifierType.Quart:
                return Easing == EasingType.In ? t * t * t * t : Easing == EasingType.Out ? 1 - Mathf.Pow(1 - t, 4) : (t < 0.5f ? 8 * t * t * t * t : 1 - Mathf.Pow(-2 * t + 2, 4) / 2);
            case SpeedModifierType.Elastic:
                return Easing == EasingType.In ? (t == 0 ? 0 : t == 1 ? 1 : -Mathf.Pow(2, 10 * t - 10) * Mathf.Sin((t * 10 - 10.75f) * ((2 * Mathf.Pi) / 3))) : Easing == EasingType.Out ? (t == 0 ? 0 : t == 1 ? 1 : Mathf.Pow(2, -10 * t) * Mathf.Sin((t * 10 - 0.75f) * ((2 * Mathf.Pi) / 3)) + 1) : (t < 0.5f ? -(Mathf.Pow(2, 20 * t - 10) * Mathf.Sin((20 * t - 11.125f) * ((2 * Mathf.Pi) / 3))) / 2 : (Mathf.Pow(2, -20 * t + 10) * Mathf.Sin((20 * t - 11.125f) * ((2 * Mathf.Pi) / 3))) / 2 + 1);
            case SpeedModifierType.Back:
                float c1 = 1.70158f;
                float c2 = c1 * 1.525f;
                return Easing == EasingType.In ? (c1 + 1) * t * t * t - c1 * t * t : Easing == EasingType.Out ? 1 + (c1 + 1) * Mathf.Pow(t - 1, 3) + c1 * Mathf.Pow(t - 1, 2) : (t < 0.5f ? (Mathf.Pow(2 * t, 2) * ((c2 + 1) * 2 * t - c2)) / 2 : (Mathf.Pow(2 * t - 2, 2) * ((c2 + 1) * (t * 2 - 2) + c2) + 2) / 2);
            case SpeedModifierType.Bounce:
                return Easing == EasingType.In ? 1 - ApplyEasing(1 - t) : Easing == EasingType.Out ? BounceEaseOut(t) : (t < 0.5f ? (1 - BounceEaseOut(1 - 2 * t)) / 2 : (1 + BounceEaseOut(2 * t - 1)) / 2);
            default:
                return t;
        }
    }
    
    private float BounceEaseOut(float t)
    {
        if (t < 1 / 2.75f) return 7.5625f * t * t;
        else if (t < 2 / 2.75f) return 7.5625f * (t -= 1.5f / 2.75f) * t + 0.75f;
        else if (t < 2.5f / 2.75f) return 7.5625f * (t -= 2.25f / 2.75f) * t + 0.9375f;
        else return 7.5625f * (t -= 2.625f / 2.75f) * t + 0.984375f;
    }
    
    public float ApplyModifier(float baseSpeed, float factor)
    {
        float easedFactor = factor;

        switch (Easing)
        {
            case EasingType.In:
                easedFactor = factor * factor;
                break;
            case EasingType.Out:
                easedFactor = 1 - Mathf.Pow(1 - factor, 2);
                break;
            case EasingType.InOut:
                easedFactor = factor < 0.5f ? 2 * factor * factor : 1 - Mathf.Pow(-2 * factor + 2, 2) / 2;
                break;
        }
        
        float speedRange = baseSpeed - MinimumSpeed;
        float modifiedSpeed = MinimumSpeed + speedRange * easedFactor;

        switch (ModifierType)
        {
            case SpeedModifierType.Sine:
                modifiedSpeed *= Mathf.Sin(easedFactor * Mathf.Pi * 0.5f);
                break;
            case SpeedModifierType.Quad:
                modifiedSpeed *= easedFactor * easedFactor;
                break;
            case SpeedModifierType.Exponential:
                modifiedSpeed *= Mathf.Pow(2, easedFactor) - 1;
                break;
            case SpeedModifierType.Quint:
                modifiedSpeed *= easedFactor * easedFactor * easedFactor * easedFactor * easedFactor;
                break;
            case SpeedModifierType.Circ:
                modifiedSpeed *= 1 - Mathf.Sqrt(1 - easedFactor * easedFactor);
                break;
            case SpeedModifierType.Cubic:
                modifiedSpeed *= easedFactor * easedFactor * easedFactor;
                break;
            case SpeedModifierType.Linear:
                modifiedSpeed *= easedFactor;
                break;
            case SpeedModifierType.Quart:
                modifiedSpeed *= easedFactor * easedFactor * easedFactor * easedFactor;
                break;
            case SpeedModifierType.Elastic:
                modifiedSpeed *= Mathf.Sin(13 * easedFactor * Mathf.Pi) * Mathf.Pow(2, 10 * (easedFactor - 1));
                break;
            case SpeedModifierType.Back:
                modifiedSpeed *= easedFactor * easedFactor * (2.70158f * easedFactor - 1.70158f);
                break;
            case SpeedModifierType.Bounce:
                modifiedSpeed *= Mathf.Abs(Mathf.Sin(6.28f * (easedFactor + 1) * (easedFactor + 1)) * (1 - easedFactor));
                break;
        }

        if (Invert)
        {
            modifiedSpeed = baseSpeed + (baseSpeed - modifiedSpeed);
        }

        return Mathf.Max(modifiedSpeed, MinimumSpeed);
    }
}

public enum SpeedModifierType
{
    Sine,
    Quad,
    Exponential,
    Quint,
    Circ,
    Cubic,
    Linear,
    Quart,
    Elastic,
    Back,
    Bounce
}

public enum EasingType
{
    In,
    Out,
    InOut
}