#ifndef BREEZE_EASINGS_H
#define BREEZE_EASINGS_H

#include <math.h>

// From: https://easings.net/
typedef enum BzEaseType {
    BZ_EASE_NONE,
    BZ_EASE_IN_SINE,
    BZ_EASE_OUT_SINE,
    BZ_EASE_INOUT_SINE,
    BZ_EASE_IN_QUAD,
    BZ_EASE_OUT_QUAD,
    BZ_EASE_INOUT_QUAD,
    BZ_EASE_IN_CUBIC,
    BZ_EASE_OUT_CUBIC,
    BZ_EASE_INOUT_CUBIC,
    BZ_EASE_IN_QUART,
    BZ_EASE_OUT_QUART,
    BZ_EASE_INOUT_QUART,
    BZ_EASE_IN_QUINT,
    BZ_EASE_OUT_QUINT,
    BZ_EASE_INOUT_QUINT,
    BZ_EASE_IN_EXPO,
    BZ_EASE_OUT_EXPO,
    BZ_EASE_INOUT_EXPO,
    BZ_EASE_IN_CIRC,
    BZ_EASE_OUT_CIRC,
    BZ_EASE_INOUT_CIRC,
    BZ_EASE_IN_BACK,
    BZ_EASE_OUT_BACK,
    BZ_EASE_INOUT_BACK,
    BZ_EASE_IN_ELASTIC,
    BZ_EASE_OUT_ELASTIC,
    BZ_EASE_INOUT_ELASTIC,
    BZ_EASE_IN_BOUNCE,
    BZ_EASE_OUT_BOUNCE,
    BZ_EASE_INOUT_BOUNCE,
    BZ_EASE_COUNT,
} BzEaseType;

static const char *bzEasePrettyName(BzEaseType type) {
    switch (type) {
        case BZ_EASE_NONE: return "easeNone";
        case BZ_EASE_IN_SINE: return "easeInSine";
        case BZ_EASE_OUT_SINE: return "easeOutSine";
        case BZ_EASE_INOUT_SINE: return "easeInOutSine";
        case BZ_EASE_IN_QUAD: return "easeInQuad";
        case BZ_EASE_OUT_QUAD: return "easeOutQuad";
        case BZ_EASE_INOUT_QUAD: return "easeInOutQuad";
        case BZ_EASE_IN_CUBIC: return "easeInCubic";
        case BZ_EASE_OUT_CUBIC: return "easeOutCubic";
        case BZ_EASE_INOUT_CUBIC: return "easeInOutCubic";
        case BZ_EASE_IN_QUART: return "easeInQuart";
        case BZ_EASE_OUT_QUART: return "easeOutQuart";
        case BZ_EASE_INOUT_QUART: return "easeInOutQuart";
        case BZ_EASE_IN_QUINT: return "easeInQuint";
        case BZ_EASE_OUT_QUINT: return "easeOutQuint";
        case BZ_EASE_INOUT_QUINT: return "easeInOutQuint";
        case BZ_EASE_IN_EXPO: return "easeInExpo";
        case BZ_EASE_OUT_EXPO: return "easeOutExpo";
        case BZ_EASE_INOUT_EXPO: return "easeInOutExpo";
        case BZ_EASE_IN_CIRC: return "easeInCirc";
        case BZ_EASE_OUT_CIRC: return "easeOutCirc";
        case BZ_EASE_INOUT_CIRC: return "easeInOutCirc";
        case BZ_EASE_IN_BACK: return "easeInBack";
        case BZ_EASE_OUT_BACK: return "easeOutBack";
        case BZ_EASE_INOUT_BACK: return "easeInOutBack";
        case BZ_EASE_IN_ELASTIC: return "easeInElastic";
        case BZ_EASE_OUT_ELASTIC: return "easeOutElastic";
        case BZ_EASE_INOUT_ELASTIC: return "easeInOutElastic";
        case BZ_EASE_IN_BOUNCE: return "easeeInBounce";
        case BZ_EASE_OUT_BOUNCE: return "easeOutBounce";
        case BZ_EASE_INOUT_BOUNCE: return "easeInOutBounce";
        default: return NULL;
    }
}

static f32 bzEaseNone(f32 x) {
    return x;
}

static f32 bzEaseInSine(f32 x) {
    return 1 - cosf((x * M_PI) / 2.0f);
}
static f32 bzEaseOutSine(f32 x) {
    return sinf((x * M_PI) / 2.0f);
}
static f32 bzEaseInOutSine(f32 x) {
    return -(-cosf(M_PI * x) - 1) / 2.0f;
}

static f32 bzEaseInQuad(f32 x) {
    return x * x;
}
static f32 bzEaseOutQuad(f32 x) {
    return 1 - (1 - x) * (1 - x);
}
static f32 bzEaseInOutQuad(f32 x) {
    return x < 0.5f ? 2.0f * x * x : 1 - powf(-2.0f * x + 2, 2) / 2.0f;
}

static f32 bzEaseInCubic(f32 x) {
    return x * x * x;
}
static f32 bzEaseOutCubic(f32 x) {
    return 1 - powf(1 - x, 3);
}
static f32 bzEaseInOutCubic(f32 x) {
    return x < 0.5 ? 4 * x * x * x : 1 - powf(-2.0f * x + 2, 3) / 2.0f;
}

static f32 bzEaseInQuart(f32 x) {
    return x * x * x * x;
}
static f32 bzEaseOutQuart(f32 x) {
    return 1 - powf(1 - x, 4);
}
static f32 bzEaseInOutQuart(f32 x) {
    return x < 0.5 ? 8 * x * x * x * x : 1 - powf(-2 * x + 2, 4) / 2.0f;
}

static f32 bzEaseInQuint(f32 x) {
    return x * x * x * x * x;
}
static f32 bzEaseOutQuint(f32 x) {
    return 1 - powf(1 - x, 5);
}
static f32 bzEaseInOutQuint(f32 x) {
    return x < 0.5 ? 16 * x * x * x * x * x : 1 - powf(-2 * x + 2, 5) / 2.0f;
}

static f32 bzEaseInExpo(f32 x) {
    return x == 0 ? 0 : powf(2, 10 * x - 10);
}
static f32 bzEaseOutExpo(f32 x) {
    return x == 1 ? 1 : 1 - powf(2, -10 * x);
}
static f32 bzEaseInOutExpo(f32 x) {
    if (x == 0) return 0;
    if (x == 1) return 1;
    return x < 0.5 ? powf(2, 20 * x - 10) / 2.0 : (2 - powf(2, -20 * x + 10)) / 2.0;
}

static f32 bzEaseInCirc(f32 x) {
    return 1 - sqrtf(1 - powf(x, 2));
}
static f32 bzEaseOutCirc(f32 x) {
    return sqrtf(1 - powf(x - 1, 2));
}
static f32 bzEaseInOutCirc(f32 x) {
    return x < 0.5
           ? (1 - sqrtf(1 - powf(2 * x, 2))) / 2
           : (sqrtf(1 - powf(-2 * x + 2, 2)) + 1) / 2;
}

static f32 bzEaseInBack(f32 x) {
    const f32 c1 = 1.70158f;
    const f32 c3 = c1 + 1;

    return (c3 * x * x * x) - (c1 * x * x);
}
static f32 bzEaseOutBack(f32 x) {
    const f32 c1 = 1.70158f;
    const f32 c3 = c1 + 1;

    return 1 + (c3 * powf(x - 1, 3)) + (c1 * powf(x - 1, 2));
}
static f32 bzEaseInOutBack(f32 x) {
    const f32 c1 = 1.70158f;
    const f32 c2 = c1 * 1.525;

    return x < 0.5
           ? (powf(2 * x, 2) * ((c2 + 1) * 2 * x - c2)) / 2.0f
           : (powf(2 * x - 2, 2) * ((c2 + 1) * (x * 2 - 2) + c2) + 2) / 2.0f;
}

static f32 bzEaseInElastic(f32 x) {
    const f32 c4 = (2 * M_PI) / 3.0f;

    if (x == 0) return 0;
    if (x == 1) return 1;

    return -powf(2, 10 * x - 10) * sinf((x * 10 - 10.75) * c4);
}
static f32 bzEaseOutElastic(f32 x) {
    const f32 c4 = (2 * M_PI) / 3.0f;

    if (x == 0) return 0;
    if (x == 1) return 1;

    return powf(2, -10 * x) * sinf((x * 10 - 0.75) * c4) + 1;
}
static f32 bzEaseInOutElastic(f32 x) {
    const f32 c5 = (2 * M_PI) / 4.5f;

    if (x == 0) return 0;
    if (x == 1) return 1;

    return x < 0.5 ?
        -(powf(2, 20 * x - 10) * sinf((20 * x - 11.125) * c5)) / 2.0f :
        (powf(2, -20 * x + 10) * sinf((20 * x - 11.125) * c5)) / 2.0f + 1;
}

static f32 bzEaseOutBounce(f32 x);
static f32 bzEaseInBounce(f32 x) {
    return 1 - bzEaseOutBounce(1 - x);
}
static f32 bzEaseOutBounce(f32 x) {
    const f32 n1 = 7.5625;
    const f32 d1 = 2.75;

    if (x < 1 / d1) {
        return n1 * x * x;
    } else if (x < 2 / d1) {
        return (x -= 1.5f / d1, n1 * x * x + 0.75f);
    } else if (x < 2.5 / d1) {
        return (x -= 2.25 / d1, n1 * x * x + 0.9375);
    } else {
        return (x -= 2.625 / d1, n1 * x * x + 0.984375);
    }
}
static f32 bzEaseInOutBounce(f32 x) {
    return x < 0.5
           ? (1 - bzEaseOutBounce(1 - 2 * x)) / 2.0f
           : (1 + bzEaseOutBounce(2 * x - 1)) / 2.0f;
}

static f32 bzEase(BzEaseType easeType, f32 x) {
    switch (easeType) {
        case BZ_EASE_NONE: return bzEaseNone(x);
        case BZ_EASE_IN_SINE: return bzEaseInSine(x);
        case BZ_EASE_OUT_SINE: return bzEaseOutSine(x);
        case BZ_EASE_INOUT_SINE: return bzEaseInOutSine(x);
        case BZ_EASE_IN_QUAD: return bzEaseInQuad(x);
        case BZ_EASE_OUT_QUAD: return bzEaseOutQuad(x);
        case BZ_EASE_INOUT_QUAD: return bzEaseInOutQuad(x);
        case BZ_EASE_IN_CUBIC: return bzEaseInCubic(x);
        case BZ_EASE_OUT_CUBIC: return bzEaseOutCubic(x);
        case BZ_EASE_INOUT_CUBIC: return bzEaseInOutCubic(x);
        case BZ_EASE_IN_QUART: return bzEaseInQuart(x);
        case BZ_EASE_OUT_QUART: return bzEaseOutQuart(x);
        case BZ_EASE_INOUT_QUART: return bzEaseInOutQuart(x);
        case BZ_EASE_IN_QUINT: return bzEaseInQuint(x);
        case BZ_EASE_OUT_QUINT: return bzEaseOutQuint(x);
        case BZ_EASE_INOUT_QUINT: return bzEaseInOutQuint(x);
        case BZ_EASE_IN_EXPO: return bzEaseInExpo(x);
        case BZ_EASE_OUT_EXPO: return bzEaseOutExpo(x);
        case BZ_EASE_INOUT_EXPO: return bzEaseInOutExpo(x);
        case BZ_EASE_IN_CIRC: return bzEaseInCirc(x);
        case BZ_EASE_OUT_CIRC: return bzEaseOutCirc(x);
        case BZ_EASE_INOUT_CIRC: return bzEaseInOutCirc(x);
        case BZ_EASE_IN_BACK: return bzEaseInBack(x);
        case BZ_EASE_OUT_BACK: return bzEaseOutBack(x);
        case BZ_EASE_INOUT_BACK: return bzEaseInOutBack(x);
        case BZ_EASE_IN_ELASTIC: return bzEaseInElastic(x);
        case BZ_EASE_OUT_ELASTIC: return bzEaseOutElastic(x);
        case BZ_EASE_INOUT_ELASTIC: return bzEaseInOutElastic(x);
        case BZ_EASE_IN_BOUNCE: return bzEaseInBounce(x);
        case BZ_EASE_OUT_BOUNCE: return bzEaseOutBounce(x);
        case BZ_EASE_INOUT_BOUNCE: return bzEaseInOutBounce(x);
    }
}

#endif //BREEZE_EASINGS_H
