#include "blitz/misc/Easing.h"

#include "blitz/misc/Maths.h"

namespace blitz {
namespace utils {

// Found at https://easings.net/

/* Sine functions */

float EaseInSine(float x) {
	return 1.0f - std::cos((x * PI) / 2.0f);
}

float EaseOutSine(float x) {
	return std::sin((x * PI) / 2.0f);
}

float EaseInOutSine(float x) {
	return -(std::cos(PI * x) - 1.0f) / 2.0f;
}

/* Cubic functions */

float EaseInCubic(float x) {
	return x * EaseInQuad(x);
}

float EaseOutCubic(float x) {
	return 1 - std::pow(1 - x, 3);
}

float EaseInOutCubic(float x) {
	return x < 0.5 ? 4 * EaseInCubic(x) : 1 - std::pow(-2 * x + 2, 3) / 2.0f;
}

/* Quint functions */

float EaseInQuint(float x) {
	return x * EaseInQuart(x);
}

float EaseOutQuint(float x) {
	return 1 - std::pow(1 - x, 5);
}

float EaseInOutQuint(float x) {
	return x < 0.5 ? 16 * EaseInQuint(x) : 1 - std::pow(-2 * x + 2, 5) / 2.0f;
}

/* Circ functions */

float EaseInCirc(float x) {
	return 1 - std::sqrt(1 - std::pow(x, 2));
}

float EaseOutCirc(float x) {
	return std::sqrt(1 - std::pow(x - 1, 2));
}

float EaseInOutCirc(float x) {
	return x < 0.5 ? (1 - std::sqrt(1 - std::pow(2 * x, 2))) / 2.0f : (std::sqrt(1 - std::pow(-2 * x + 2, 2)) + 1) / 2.0f;
}

/* Elastic functions */

float EaseInElastic(float x) {
	const float c4 = (2 * PI) / 3.0f;

	return x == 0 ? 0 : x == 1 ? 1 : -std::pow(2, 10 * x - 10) * std::sin((x * 10 - 10.75) * c4);
}

float EaseOutElastic(float x) {
	const float c4 = (2 * PI) / 3.0f;

	return x == 0 ? 0 : x == 1 ? 1 : std::pow(2, -10 * x) * std::sin((x * 10 - 0.75) * c4) + 1;
}

float EaseInOutElastic(float x) {
	const float c5 = (2 * PI) / 4.5;

	return x == 0	 ? 0
		   : x == 1	 ? 1
		   : x < 0.5 ? -(std::pow(2, 20 * x - 10) * std::sin((20 * x - 11.125) * c5)) / 2.0f
					 : (std::pow(2, -20 * x + 10) * std::sin((20 * x - 11.125) * c5)) / 2.0f + 1;
}

/* Quad functions */

float EaseInQuad(float x) {
	return x * x;
}

float EaseOutQuad(float x) {
	return 1 - (1 - x) * (1 - x);
}

float EaseInOutQuad(float x) {
	return x < 0.5 ? 2 * x * x : 1 - std::pow(-2 * x + 2, 2) / 2.0f;
}

/* Quart functions */

float EaseInQuart(float x) {
	return x * EaseInCubic(x);
}

float EaseOutQuart(float x) {
	return 1 - std::pow(1 - x, 4);
}

float EaseInOutQuart(float x) {
	return x < 0.5 ? 8 * EaseInQuart(x) : 1 - std::pow(-2 * x + 2, 4) / 2.0f;
}

/* Expo functions */

float EaseInExpo(float x) {
	return x == 0 ? 0 : std::pow(2, 10 * x - 10);
}

float EaseOutExpo(float x) {
	return x == 1 ? 1 : 1 - std::pow(2, -10 * x);
}

float EaseInOutExpo(float x) {
	return x == 0 ? 0 : x == 1 ? 1 : x < 0.5 ? std::pow(2, 20 * x - 10) / 2.0f : (2 - std::pow(2, -20 * x + 10)) / 2.0f;
}

/* Back functions */

float EaseInBack(float x) {
	const float c1 = 1.70158;
	const float c3 = c1 + 1;

	return c3 * EaseInCubic(x) - c1 * EaseInQuad(x);
}

float EaseOutBack(float x) {
	const float c1 = 1.70158;
	const float c3 = c1 + 1;

	return 1 + c3 * std::pow(x - 1, 3) + c1 * std::pow(x - 1, 2);
}

float EaseInOutBack(float x) {
	const float c1 = 1.70158;
	const float c2 = c1 * 1.525;

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

/* Bounce functions */

float EaseInBounce(float x) {
	return 1 - EaseOutBounce(1 - x);
}

float EaseOutBounce(float x) {
	const float n1 = 7.5625;
	const float d1 = 2.75;

	if (x < 1 / d1) {
		return n1 * EaseInQuad(x);
	} else if (x < 2 / d1) {
		x -= 1.5;
		return n1 * (x / d1) * x + 0.75;
	} else if (x < 2.5 / d1) {
		x -= 2.25;
		return n1 * (x / d1) * x + 0.9375;
	} else {
		x -= 2.625;
		return n1 * (x / d1) * x + 0.984375;
	}
}

float EaseInOutBounce(float x) {
	return x < 0.5 ? (1 - EaseOutBounce(1 - 2 * x)) / 2.0f : (1 + EaseOutBounce(2 * x - 1)) / 2.0f;
}

}  // namespace utils
}  // namespace blitz
