// A revised version of Ingemar Ragnemalm's VectorUtils4 that more aligns with C++
// standards for constructors and operators of objects. Intended to be used
// along with Simpler Vulkan.

// Updated by Ingemar 20260331. Removed unnecessary operators that were left as exercise as needed. Restored dot, cross etc to be more intuitive and align with GLSL.

#ifndef SVK_VECTOR_UTILS_HPP
#define SVK_VECTOR_UTILS_HPP

#include <cmath>
#include <bitset> // std::hash
#include <iostream> // std::ostream operators
#include <utility> // std::pair


#ifndef M_PI
#define M_PI 3.14159265358979323846
#endif

struct vec3;
struct vec4;

struct vec2
{
	union
	{float x; float s;};
	union
	{float y; float t;};
	
	vec2();
	vec2(float const& value);
	vec2(float const& pX, float const& pY);
	vec2(vec3 const& v);
	vec2(vec4 const& v);

        // vec2 operators left as exersize if you need them
};

vec2 operator*(float const& lhs, vec2 const& rhs);

std::ostream& operator<<(std::ostream& os, vec2 const& v);

template <>
struct std::hash<vec2>
{
    std::size_t operator()(vec2 const& v) const noexcept;
};

struct vec3
{
	union
	{float x; float r;};
	union
	{float y; float g;};
	union
	{float z; float b;};

	vec3();
	vec3(float const& value);
	vec3(float const& pX, float const& pY, float const& pZ);
	vec3(vec2 const& v);
	vec3(vec2 const& v, float const& pZ);
	vec3(vec4 const& v);

	vec3& operator=(vec2 const& rhs);
	vec3& operator=(vec4 const& rhs);

	bool operator==(vec3 const& rhs) const;
	bool operator!=(vec3 const& rhs) const;

	vec3 operator+(vec3 const& rhs) const;
	vec3 operator-(vec3 const& rhs) const;
	vec3 operator*(vec3 const& rhs) const;

	vec3 operator+(float const& rhs) const;
	vec3 operator-(float const& rhs) const;
	vec3 operator*(float const& rhs) const;
	vec3 operator/(float const& rhs) const;

	vec3& operator+=(vec3 const& rhs);
	vec3& operator-=(vec3 const& rhs);
	vec3& operator*=(vec3 const& rhs);

	vec3& operator+=(float const& rhs);
	vec3& operator-=(float const& rhs);
	vec3& operator*=(float const& rhs);
	vec3& operator/=(float const& rhs);

	vec3 operator-();
};

vec3 operator*(float const& lhs, vec3 const& rhs);

std::ostream& operator<<(std::ostream& os, vec3 const& v);

template <>
struct std::hash<vec3>
{
    std::size_t operator()(vec3 const& v) const noexcept;
};
	

struct vec4
{
	union
	{float x; float r;};
	union
	{float y; float g;};
	union
	{float z; float b;};
	union
	{float w; float a; float h;};
	vec4();
	vec4(float const& value);
	vec4(float const& pX, float const& pY, float const& pZ, float const& pW);
	vec4(vec2 const& v);
	vec4(vec2 const& v, float const& pZ);
	vec4(vec2 const& v, float const& pZ, float const& pW);
	vec4(vec3 const& v);
	vec4(vec3 const& v, float const& pW);

	vec4& operator=(vec2 const& rhs);
	vec4& operator=(vec3 const& rhs);

	bool operator==(vec4 const& rhs) const;
	bool operator!=(vec4 const& rhs) const;

	vec4 operator+(vec4 const& rhs) const;
	vec4 operator-(vec4 const& rhs) const;
	vec4 operator*(vec4 const& rhs) const;

	vec4 operator+(float const& rhs) const;
	vec4 operator-(float const& rhs) const;
	vec4 operator*(float const& rhs) const;
	vec4 operator/(float const& rhs) const;

	vec4& operator+=(vec4 const& rhs);
	vec4& operator-=(vec4 const& rhs);
	vec4& operator*=(vec4 const& rhs);

	vec4& operator+=(float const& rhs);
	vec4& operator-=(float const& rhs);
	vec4& operator*=(float const& rhs);
	vec4& operator/=(float const& rhs);

	vec4 operator-();
};

vec4 operator*(float const& lhs, vec4 const& rhs);

std::ostream& operator<<(std::ostream& os, vec4 const& v);

template <>
struct std::hash<vec4>
{
    std::size_t operator()(vec4 const& v) const noexcept;
};


struct mat4;

struct mat3
{
	float m[9];
	mat3();
	mat3(float const& value);
	mat3(float const& p00, float const& p11, float const& p22 = 1.0f);
	mat3(float const& p00, float const& p01, float const& p02,
		 float const& p10, float const& p11, float const& p12,
		 float const& p20, float const& p21, float const& p22);
	mat3(vec3 const& v1, vec3 const& v2, vec3 const& v3);
	mat3(mat4 const& m);

	float const& operator[](size_t const& index) const;
	float& operator[](size_t const& index);

	float const& operator[](std::pair<size_t, size_t> const& index) const;
	float& operator[](std::pair<size_t, size_t> const& index);

	mat3 operator+(mat3 const& rhs) const;
	mat3 operator*(mat3 const& rhs) const;
	vec3 operator*(vec3 const& rhs) const;
};

std::ostream& operator<<(std::ostream& os, mat3 const& m);


struct mat4
{
	float m[16];
	mat4();
	mat4(float const& value);
	mat4(float const& p00, float const& p11, float const& p22, float const& p33 = 1.0f);
	mat4(float const& p00, float const& p01, float const& p02, float const& p03,
		 float const& p10, float const& p11, float const& p12, float const& p13,
		 float const& p20, float const& p21, float const& p22, float const& p23,
		 float const& p30, float const& p31, float const& p32, float const& p33);
	mat4(mat3 const& m);

	float const& operator[](size_t const& index) const;
	float& operator[](size_t const& index);

	float const& operator[](std::pair<size_t, size_t> const& index) const;
	float& operator[](std::pair<size_t, size_t> const& index);

	mat4 operator+(mat4 const& rhs) const;
	mat4 operator*(mat4 const& rhs) const;
	vec4 operator*(vec4 const& rhs) const;
};

std::ostream& operator<<(std::ostream& os, mat4 const& m);


vec3 calculateNormalVector(vec3 const& a, vec3 const& b, vec3 const& c);
void splitVector(vec3 const& v, vec3 const& n, vec3* vn, vec3* vp);

mat4 identityMatrix();
mat4 Rx(float const& a);
mat4 Ry(float const& a);
mat4 Rz(float const& a);
mat4 T(float const& tx, float const& ty, float const& tz);
mat4 S(float const& sx, float const& sy, float const& sz);

void orthoNormalizeMatrix(mat4& R);
mat4 arbRotate(vec3 const& axis, float const& fi);
mat4 crossMatrix(vec3 const& a);

// Configure, i.e. if you want matrices to be column-wise
void setAutomaticTranspose(bool const& value);

// GLU replacement functions
mat4 lookAt(vec3 const& position, vec3 const& target, vec3 up);
mat4 lookAt(float const& positionX, float const& positionY, float const& positionZ, 
			float const& targetX, float const& targetY, float const& targetZ,
			float const& upX, float const& upY, float const& upZ);

mat4 perspective(float const& fovyInDegrees, float const& aspectRatio,
				 float const& znear, float const& zfar);

mat4 frustum(float const& left, float const& right, float const& bottom, float const& top,
			 float const& znear, float const& zfar);

mat4 ortho(float const& left, float const& right, float const& bottom,
		   float const& top, float const& near, float const& far);

float dot(vec3 a, vec3 b);
vec3 cross(vec3 a, vec3 b);
float norm(vec3 a); // that is the length of the vector
vec3 normalize(vec3 a);
mat4 inverse(mat4 m);
mat3 inverse(mat3 m);
mat4 transpose(mat4 a);
mat3 transpose(mat3 a);

//mat3 inverseTransposed(); // ?


#endif // SVK_VECTOR_UTILS_HPP
