/*=============================================================================*/
// Copyright 2021-2022 Elite Engine
// Authors: Matthieu Delaere, Thomas Goussaert
/*=============================================================================*/
// EMathUtilities.h: Utility class containing a bunch of commonely used functionality (not custom-type specific)
/*=============================================================================*/
#ifndef ELITE_MATH_UTILITIES
#define ELITE_MATH_UTILITIES
//Standard C++ includes
#include <cstdlib>
#include <cfloat>
#include <type_traits>

namespace Elite {
	/* --- CONSTANTS --- */
	#define E_PI	3.14159265358979323846
	#define E_PI_2	1.57079632679489661923
	#define E_PI_4	0.785398163397448309616

	/* --- FUNCTIONS --- */
	/*! Comparing two values (preferably float or doubles) and see if they are equal. You can change the precision (by default: epsilon)*/
	template<typename T, typename = std::enable_if<std::is_pod<T>::value>>
	constexpr bool AreEqual(T a, T b, float precision = FLT_EPSILON)
	{
		if (abs(a - b) > precision)
			return false;
		return true;
	}
	/*! An accurate inverse square root*/
	inline float InvSqrt(float f) //sqrtf not defined as constexpr
	{
		return 1.0f / sqrtf(f);
	}
	/*! An fast inverse square root, not fully accurate. Implementation based on Quake III Arena*/
	/*! Reference: https://betterexplained.com/articles/understanding-quakes-fast-inverse-square-root/ */
	inline float InvSqrtFst(float f)
	{
		const float xHalf = 0.5f * f;
		int i = *reinterpret_cast<int*>(&f);
		i = 0x5f3759df - (i >> 1);
		f = *reinterpret_cast<float*>(&i);
		f = f*(1.5f - xHalf*f*f);
		return f;
	}
	/*! Function to square a number */
	template<typename T, typename = std::enable_if<std::is_pod<T>::value>>
	constexpr auto Square(T v)
	{
		return v*v;
	}
	/*! Function to convert degrees to radians */
	constexpr float ToRadians(const float angle)
	{
		return angle * (static_cast<float>(E_PI) / 180.f);
	}
	/*! Function to convert radians to degrees */
	constexpr float ToDegrees(const float angle)
	{
		return angle * (180.f / static_cast<float>(E_PI));
	}
	/*! Clamped angle between -pi, pi (in radians)  */
	inline float ClampedAngle(const float radians)
	{
		float a = fmodf(radians + b2_pi, 2 * b2_pi);
		a = a >= 0 ? (a - b2_pi) : (a + b2_pi);
		return a;
	}
	/*! Template function to clamp between a minimum and a maximum value -> in STD since c++17 */
	template<typename T>
	constexpr T Clamp(const T a, T min, T max)
	{
		if (a < min)
			return min;

		if (a > max)
			return max;

		return a;
	}

	/*! Template function to clamp between a minimum and a maximum value*/
	template<typename T>
	constexpr T ClampRef(T& a, T min, T max)
	{
		if (a < min)
			a = min;

		if (a > max)
			a = max;

		return a;
	}

	/*! Random Integer */
	inline int randomInt(int max = 1)
	{ return rand() % max; }

	/*! Random Float */
	inline float randomFloat(float max = 1.f)
	{ return max * (float(rand()) / RAND_MAX); }

	/*! Random Float */
	inline float randomFloat(float min, float max)
	{
		auto range = max - min;
		return (range * (float(rand()) / RAND_MAX)) + min;
	}

	/*! Random Binomial Float */
	inline float randomBinomial(float max = 1.f)
	{ return randomFloat(max) - randomFloat(max); }

	/*! Linear Interpolation */
	/*inline float Lerp(float v0, float v1, float t)
	{ return (1 - t) * v0 + t * v1;	}*/
	template<typename T>
	inline T Lerp(T v0, T v1, float t)
	{ return (1 - t) * v0 + t * v1;	}

	/*! Smooth Step */
	inline float smoothStep(float edge0, float edge1, float x)
	{
		// Scale, bias and saturate x to 0..1 range
		x = Clamp((x - edge0) / (edge1 - edge0), 0.0f, 1.0f);
		// Evaluate polynomial
		return x * x * (3 - 2 * x);
	}

	/*! Sign Function*/
	template <typename T>  int sign(T val) 
	{ return (T(0) < val) - (val < T(0)); }
}
#endif