/**
 * @file easing_functions.hpp
 * @brief Colección de funciones de suavizado (easing) para animaciones
 *
 * Todas las funciones toman un parámetro t (0.0 a 1.0) que representa
 * el progreso de la animación y retornan el valor suavizado.
 *
 * Convenciones:
 * - In: Aceleración (slow -> fast)
 * - Out: Desaceleración (fast -> slow)
 * - InOut: Aceleración + Desaceleración (slow -> fast -> slow)
 */

#pragma once

#include <cmath>
#include <numbers>

#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

namespace Easing {

    // LINEAR
    inline auto linear(float t) -> float {
        return t;
    }

    // QUAD (Cuadrática: t^2)
    inline auto quadIn(float t) -> float {
        return t * t;
    }

    inline auto quadOut(float t) -> float {
        return t * (2.0F - t);
    }

    inline auto quadInOut(float t) -> float {
        if (t < 0.5F) {
            return 2.0F * t * t;
        }
        return -1.0F + ((4.0F - (2.0F * t)) * t);
    }

    // CUBIC (Cúbica: t^3)
    inline auto cubicIn(float t) -> float {
        return t * t * t;
    }

    inline auto cubicOut(float t) -> float {
        const float F = t - 1.0F;
        return (F * F * F) + 1.0F;
    }

    inline auto cubicInOut(float t) -> float {
        if (t < 0.5F) {
            return 4.0F * t * t * t;
        }
        const float F = ((2.0F * t) - 2.0F);
        return (0.5F * F * F * F) + 1.0F;
    }

    // QUART (Cuártica: t^4)
    inline auto quartIn(float t) -> float {
        return t * t * t * t;
    }

    inline auto quartOut(float t) -> float {
        const float F = t - 1.0F;
        return 1.0F - (F * F * F * F);
    }

    inline auto quartInOut(float t) -> float {
        if (t < 0.5F) {
            return 8.0F * t * t * t * t;
        }
        const float F = t - 1.0F;
        return 1.0F - (8.0F * F * F * F * F);
    }

    // QUINT (Quíntica: t^5)
    inline auto quintIn(float t) -> float {
        return t * t * t * t * t;
    }

    inline auto quintOut(float t) -> float {
        const float F = t - 1.0F;
        return (F * F * F * F * F) + 1.0F;
    }

    inline auto quintInOut(float t) -> float {
        if (t < 0.5F) {
            return 16.0F * t * t * t * t * t;
        }
        const float F = ((2.0F * t) - 2.0F);
        return (0.5F * F * F * F * F * F) + 1.0F;
    }

    // SINE (Sinusoidal)
    inline auto sineIn(float t) -> float {
        return 1.0F - std::cos(t * std::numbers::pi_v<float> * 0.5F);
    }

    inline auto sineOut(float t) -> float {
        return std::sin(t * std::numbers::pi_v<float> * 0.5F);
    }

    inline auto sineInOut(float t) -> float {
        return 0.5F * (1.0F - std::cos(std::numbers::pi_v<float> * t));
    }

    // EXPO (Exponencial)
    inline auto expoIn(float t) -> float {
        if (t == 0.0F) {
            return 0.0F;
        }
        return std::pow(2.0F, 10.0F * (t - 1.0F));
    }

    inline auto expoOut(float t) -> float {
        if (t == 1.0F) {
            return 1.0F;
        }
        return 1.0F - std::pow(2.0F, -10.0F * t);
    }

    inline auto expoInOut(float t) -> float {
        if (t == 0.0F || t == 1.0F) {
            return t;
        }

        if (t < 0.5F) {
            return 0.5F * std::pow(2.0F, (20.0F * t) - 10.0F);
        }
        return 0.5F * (2.0F - std::pow(2.0F, (-20.0F * t) + 10.0F));
    }

    // CIRC (Circular)
    inline auto circIn(float t) -> float {
        return 1.0F - std::sqrt(1.0F - (t * t));
    }

    inline auto circOut(float t) -> float {
        const float F = t - 1.0F;
        return std::sqrt(1.0F - (F * F));
    }

    inline auto circInOut(float t) -> float {
        if (t < 0.5F) {
            return 0.5F * (1.0F - std::sqrt(1.0F - (4.0F * t * t)));
        }
        const float F = (2.0F * t) - 2.0F;
        return 0.5F * (std::sqrt(1.0F - (F * F)) + 1.0F);
    }

    // BACK (Overshoot - retrocede antes de avanzar)
    inline auto backIn(float t, float overshoot = 1.70158F) -> float {
        return t * t * (((overshoot + 1.0F) * t) - overshoot);
    }

    inline auto backOut(float t, float overshoot = 1.70158F) -> float {
        const float F = t - 1.0F;
        return (F * F * (((overshoot + 1.0F) * F) + overshoot)) + 1.0F;
    }

    inline auto backInOut(float t, float overshoot = 1.70158F) -> float {
        const float S = overshoot * 1.525F;

        if (t < 0.5F) {
            const float F = 2.0F * t;
            return 0.5F * (F * F * (((S + 1.0F) * F) - S));
        }

        const float F = (2.0F * t) - 2.0F;
        return 0.5F * ((F * F * (((S + 1.0F) * F) + S)) + 2.0F);
    }

    // ELASTIC (Oscilación elástica - efecto de resorte)
    inline auto elasticIn(float t, float amplitude = 1.0F, float period = 0.3F) -> float {
        if (t == 0.0F || t == 1.0F) {
            return t;
        }

        const float S = period / (2.0F * std::numbers::pi_v<float>)*std::asin(1.0F / amplitude);
        const float F = t - 1.0F;
        return -(amplitude * std::pow(2.0F, 10.0F * F) *
            std::sin((F - S) * (2.0F * std::numbers::pi_v<float>) / period));
    }

    inline auto elasticOut(float t, float amplitude = 1.0F, float period = 0.3F) -> float {
        if (t == 0.0F || t == 1.0F) {
            return t;
        }

        const float S = period / (2.0F * std::numbers::pi_v<float>)*std::asin(1.0F / amplitude);
        return (amplitude * std::pow(2.0F, -10.0F * t) *
                   std::sin((t - S) * (2.0F * std::numbers::pi_v<float>) / period)) +
            1.0F;
    }

    inline auto elasticInOut(float t, float amplitude = 1.0F, float period = 0.3F) -> float {
        if (t == 0.0F || t == 1.0F) {
            return t;
        }

        const float S = period / (2.0F * std::numbers::pi_v<float>)*std::asin(1.0F / amplitude);

        if (t < 0.5F) {
            const float F = (2.0F * t) - 1.0F;
            return -0.5F * (amplitude * std::pow(2.0F, 10.0F * F) * std::sin((F - S) * (2.0F * std::numbers::pi_v<float>) / period));
        }

        const float F = (2.0F * t) - 1.0F;
        return (0.5F * amplitude * std::pow(2.0F, -10.0F * F) *
                   std::sin((F - S) * (2.0F * std::numbers::pi_v<float>) / period)) +
            1.0F;
    }

    // BOUNCE (Rebote - simula física de rebote)
    inline auto bounceOut(float t) -> float {
        const float N1 = 7.5625F;
        const float D1 = 2.75F;

        if (t < 1.0F / D1) {
            return N1 * t * t;
        }
        if (t < 2.0F / D1) {
            const float F = t - (1.5F / D1);
            return (N1 * F * F) + 0.75F;
        }
        if (t < 2.5F / D1) {
            const float F = t - (2.25F / D1);
            return (N1 * F * F) + 0.9375F;
        }
        const float F = t - (2.625F / D1);
        return (N1 * F * F) + 0.984375F;
    }

    inline auto bounceIn(float t) -> float {
        return 1.0F - bounceOut(1.0F - t);
    }

    inline auto bounceInOut(float t) -> float {
        if (t < 0.5F) {
            return 0.5F * bounceIn(2.0F * t);
        }
        return (0.5F * bounceOut((2.0F * t) - 1.0F)) + 0.5F;
    }

}  // namespace Easing
