/*=============================================================================*/
// Copyright 2021-2022 Elite Engine
// Authors: Matthieu Delaere
/*=============================================================================*/
// EVector2.h: Vector2D struct
/*=============================================================================*/
#ifndef ELITE_MATH_VECTOR2
#define	ELITE_MATH_VECTOR2
namespace Elite
{
#define ZeroVector2 Vector2()
#define UnitVector2 Vector2(1.f,1.f)

	//Vector 2D
	struct Vector2
	{
		//=== Datamembers ===
		float x = 0.0f;
		float y = 0.0f;

		//=== Constructors ===
		Vector2() = default;
		Vector2(float _x, float _y) :x(_x), y(_y) {};

		//=== Vector Conversions Functions ===
#ifdef USE_BOX2D
		explicit Vector2(const b2Vec2& v) : x(v.x), y(v.y) {};
		Vector2& operator=(const b2Vec2& v) { x = v.x; y = v.y; return *this; }
		operator b2Vec2() const
		{return {x, y};};
#endif

		//=== Arithmetic Operators ===
		inline auto operator-(const Vector2& v) const
		{ return Vector2(x - v.x, y - v.y);	}
		inline auto operator-() const
		{ return Vector2(-x, -y); }
		inline auto operator*(float scale) const
		{ return Vector2(x * scale, y * scale);	}
		inline auto operator/(float scale) const
		{
			const auto revScale = 1.0f / scale;
			return Vector2(x * revScale, y * revScale);
		}

		//=== Compound Assignment Operators === //auto& for type deduction
		inline auto& operator+=(const Vector2& v)
		{ x += v.x; y += v.y; return *this;	}
		inline auto& operator-=(const Vector2& v)
		{ x -= v.x; y -= v.y; return *this;	}
		inline auto& operator*=(float scale)
		{ x *= scale; y *= scale; return *this;	}
		inline auto& operator/=(float scale)
		{
			const auto revScale = 1.0f / scale;
			x *= revScale; y *= revScale; return *this;
		}



		//=== Relational Operators ===
		inline auto operator==(const Vector2& v) const /*Check if both components are equal*/
		{ return AreEqual(x, v.x) && AreEqual(y, v.y);	}
		inline auto operator!=(const Vector2& v) const /*Check if one or both components are NOT equal*/
		{ return !(*this == v);	}

		//=== Member Access Operators ===
		inline float operator[](unsigned int i) const
		{
			return ((i == 0) ? x : y);
			//if (i >= 0 && i < 2)
			//	return ((i == 0) ? x : y);
			//throw; /*TODO : specify error thrown;*/
		}
		inline float& operator[](unsigned int i)
		{
			return ((i == 0) ? x : y);
			//if (i >= 0 && i < 2)
			//	return ((i == 0) ? x : y);
			//throw; /*TODO : specify error thrown;*/
		}

		//=== Internal Vector Functions ===
		inline auto Dot(const Vector2& v) const
		{ return x * v.x + y * v.y;	}

		inline auto Cross(const Vector2& v) const
		{ return x * v.y - y * v.x;	}

		inline auto GetAbs() const
		{ return Vector2(abs(x), abs(y)); }

		inline auto MagnitudeSquared() const
		{ return x*x + y*y;	}

		inline auto Magnitude() const
		{ return sqrtf(MagnitudeSquared()); }

		inline float Normalize()
		{
			auto m = Magnitude();
			if (AreEqual(m, 0.f))
			{
				*this = ZeroVector2;
				return 0.f;
			}

			auto invM = 1.f / m;
			x *= invM;
			y *= invM;

			return m;
		}

		inline Vector2 GetNormalized() const /*! Returns a normalized copy of this vector. This vector does not change.*/
		{
			auto v = Vector2(*this);
			v.Normalize();
			return v;
		}

		inline auto DistanceSquared(const Vector2& v) const
		{ return Square(v.x - x) + Square(v.y - y);	}

		inline auto Distance(const Vector2& v) const
		{ return sqrtf(DistanceSquared(v)); }

		inline auto Clamp(float max)
		{
			auto scale = max / Magnitude();
			scale = scale < 1.f ? scale : 1.f;
			return *this * scale;
		}
	};

	//=== Global Vector Operators ===
#pragma region GlobalVectorOperators
	inline auto operator+(const Vector2& v, const Vector2& v2)
	{ return Vector2(v.x + v2.x, v.y + v2.y); }

	inline auto operator* (float s, const Vector2& v)
	{ return Vector2(s * v.x, s * v.y); }

	inline auto operator*(const Vector2& a, const Vector2& b)
	{ return Vector2(a.x*b.x, a.y*b.y); }

	inline auto operator/ (float s, const Vector2& v)
	{
		const auto revScale = 1.0f / s;
		return Vector2(revScale * v.x, revScale * v.y);
	}

	inline std::ostream& operator<<(std::ostream& os, const Vector2& rhs)
	{
		os << "(" << rhs.x << ", " << rhs.y << " )";
		return os;
	}
#pragma endregion //GlobalVectorOperators

	//=== Global Vector Functions ===
#pragma region GlobalVectorFunctions
	inline auto Dot(const Vector2& v1, const Vector2& v2)
	{ return v1.Dot(v2); }

	inline auto Cross(const Vector2& v1, const Vector2& v2)
	{ return v1.Cross(v2); }

	inline auto GetAbs(const Vector2& v)
	{ return v.GetAbs(); }

	inline void Abs(Vector2& v) /*! Make absolute Vector2 of this Vector2 */
	{ v = v.GetAbs(); }

	inline void Normalize(Vector2& v)
	{ v.Normalize(); }

	inline auto GetNormalized(const Vector2& v)
	{ return v.GetNormalized(); }

	inline auto DistanceSquared(const Vector2& v1, const Vector2& v2)
	{ return v1.DistanceSquared(v2); }

	inline auto Distance(const Vector2& v1, const Vector2& v2)
	{ return v1.Distance(v2); }

	inline auto Clamp(const Vector2& v, float max)
	{
		auto scale = max / v.Magnitude();
		scale = scale < 1.f ? scale : 1.f;
		return v * scale;
	}
#pragma endregion //GlobalVectorFunctions

#pragma region ExtraFunctions
	/*! Random Vector2 */
	inline Vector2 randomVector2(float max = 1.f)
	{
		return{ randomBinomial(max),randomBinomial(max) };
	}
	inline Vector2 randomVector2(float min, float max)
	{
		return{ randomFloat(min, max),randomFloat(min, max) };
	}

	/* Get orientation from an a velocity vector
	-- [Deprecated] -- Use VectorToAngle instead*/
	[[deprecated("Use VectorToOrientation instead")]]
	inline float GetOrientationFromVelocity(const Elite::Vector2& velocity)
	{
		if (velocity.Magnitude() == 0)
			return 0.f;

		return atan2f(velocity.x, -velocity.y);
	}

	/*  Creates a normalized vector from an angle in radians.  */
	inline Vector2 OrientationToVector(float orientation)
	{
		return Vector2(cos(orientation), sin(orientation));
	}

	/*Calculates the orientation angle from a vector*/
	inline float VectorToOrientation(const Vector2& vector)
	{
		return atan2f(vector.y, vector.x);
	}

	/*! Get Angle Between 2 vectors*/
	inline float AngleBetween(const Elite::Vector2& v1, const Elite::Vector2& v2) {
		float x = v1.Dot(v2);
		float y = v1.Cross(v2);
		return atan2(y, x);
	}

#pragma endregion //ExtraFunctions
}
#endif