// maths.h - Simple mathematics library with aggressive inlining.
#ifndef MATHS_H
#define MATHS_H

#include <math.h>
#include <memory.h>

// Define M_PI
#ifndef M_PI
#define M_PI       3.14159265358979323846   // pi
#endif // !M_PI 

#define GLM_ENABLE_EXPERIMENTAL
#include <glm/glm.hpp>
#include <glm/gtx/transform.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/type_ptr.hpp>
#include <glm/gtx/rotate_vector.hpp>
#include <glm/gtx/quaternion.hpp>

#pragma warning( disable : 4244 )

struct Point2
{
	int x;
	int y;
};

struct Vec2
{
	float x;
	float y;

	Vec2() : x(0.0f), y(0.0f) {}
	Vec2(float _val) : x(_val), y(_val) {}
	Vec2(float _x, float _y) : x(_x), y(_y) {}

	// Vec2 with Vec2 operations

	inline Vec2 operator+(const Vec2& rhs) const { return Vec2(x + rhs.x, y + rhs.y); }
	inline Vec2 operator-(const Vec2& rhs) const { return Vec2(x - rhs.x, y - rhs.y); }
	inline Vec2 operator*(const Vec2& rhs) const { return Vec2(x * rhs.x, y * rhs.y); }
	inline Vec2 operator/(const Vec2& rhs) const { return Vec2(x / rhs.x, y / rhs.y); }

	inline Vec2& operator+=(const Vec2& rhs) { *this = *this + rhs; return *this; }
	inline Vec2& operator*=(const Vec2& rhs) { *this = *this * rhs; return *this; }
	inline Vec2& operator-=(const Vec2& rhs) { *this = *this - rhs; return *this; }
	inline Vec2& operator/=(const Vec2& rhs) { *this = *this / rhs; return *this; }

	// Vec2 with Scalar operations

	inline Vec2 operator+(const float s) const { return Vec2(x + s, y + s); }
	inline Vec2 operator-(const float s) const { return Vec2(x - s, y - s); }
	inline Vec2 operator*(const float s) const { return Vec2(x * s, y * s); }
	inline Vec2 operator/(const float s) const { return Vec2(x / s, y / s); }

	inline Vec2& operator+=(const float s) { *this = *this + s; return *this; }
	inline Vec2& operator*=(const float s) { *this = *this * s; return *this; }
	inline Vec2& operator-=(const float s) { *this = *this - s; return *this; }
	inline Vec2& operator/=(const float s) { *this = *this / s; return *this; }
};

struct Vec3
{
	float x;
	float y;
	float z;

	Vec3() : x(0.0f), y(0.0f), z(0.0f) {}
	Vec3(float _val) : x(_val), y(_val), z(_val) {}
	Vec3(float _x, float _y, float _z) : x(_x), y(_y), z(_z) {}

	// Vec3 with Vec2 operations

	inline Vec3 operator+(const Vec2& rhs) const { return Vec3(x + rhs.x, y + rhs.y, z); }
	inline Vec3 operator-(const Vec2& rhs) const { return Vec3(x - rhs.x, y - rhs.y, z); }
	inline Vec3 operator*(const Vec2& rhs) const { return Vec3(x * rhs.x, y * rhs.y, z); }
	inline Vec3 operator/(const Vec2& rhs) const { return Vec3(x / rhs.x, y / rhs.y, z); }

	inline Vec3& operator+=(const Vec2& rhs) { *this = *this + rhs; return *this; }
	inline Vec3& operator*=(const Vec2& rhs) { *this = *this * rhs; return *this; }
	inline Vec3& operator-=(const Vec2& rhs) { *this = *this - rhs; return *this; }
	inline Vec3& operator/=(const Vec2& rhs) { *this = *this / rhs; return *this; }

	// Vec3 with Vec3 operations

	inline Vec3 operator+(const Vec3& rhs) const { return Vec3(x + rhs.x, y + rhs.y, z + rhs.z); }
	inline Vec3 operator-(const Vec3& rhs) const { return Vec3(x - rhs.x, y - rhs.y, z - rhs.z); }
	inline Vec3 operator*(const Vec3& rhs) const { return Vec3(x * rhs.x, y * rhs.y, z * rhs.z); } 
	inline Vec3 operator/(const Vec3& rhs) const { return Vec3(x / rhs.x, y / rhs.y, z / rhs.z); } 

	inline Vec3& operator+=(const Vec3& rhs) { *this = *this + rhs; return *this; }
	inline Vec3& operator*=(const Vec3& rhs) { *this = *this * rhs; return *this; }
	inline Vec3& operator-=(const Vec3& rhs) { *this = *this - rhs; return *this; }
	inline Vec3& operator/=(const Vec3& rhs) { *this = *this / rhs; return *this; }

	// Vec3 with Scalar operations

	inline Vec3 operator+(const float s) const { return Vec3(x + s, y + s, z + s); }
	inline Vec3 operator-(const float s) const { return Vec3(x - s, y - s, z - s); }
	inline Vec3 operator*(const float s) const { return Vec3(x * s, y * s, z * s); }
	inline Vec3 operator/(const float s) const { return Vec3(x / s, y / s, z / s); }

	inline Vec3& operator+=(const float s) { *this = *this + s; return *this; }
	inline Vec3& operator*=(const float s) { *this = *this * s; return *this; }
	inline Vec3& operator-=(const float s) { *this = *this - s; return *this; }
	inline Vec3& operator/=(const float s) { *this = *this / s; return *this; }

	// Vec3 with GLM Vec3 operations (please delete someday :)

	inline Vec3 operator+(const glm::vec3& rhs) const { return Vec3(x + rhs.x, y + rhs.y, z + rhs.z); }
	inline Vec3 operator-(const glm::vec3& rhs) const { return Vec3(x - rhs.x, y - rhs.y, z - rhs.z); }
	inline Vec3 operator*(const glm::vec3& rhs) const { return Vec3(x * rhs.x, y * rhs.y, z * rhs.z); }
	inline Vec3 operator/(const glm::vec3& rhs) const { return Vec3(x / rhs.x, y / rhs.y, z / rhs.z); }

	inline Vec3& operator+=(const glm::vec3& rhs) { *this = *this + rhs; return *this; }
	inline Vec3& operator*=(const glm::vec3& rhs) { *this = *this * rhs; return *this; }
	inline Vec3& operator-=(const glm::vec3& rhs) { *this = *this - rhs; return *this; }
	inline Vec3& operator/=(const glm::vec3& rhs) { *this = *this / rhs; return *this; }
};

extern Vec3 g_zeroVector;
extern Vec3 g_identityVector;

struct Vec4
{
	float x;
	float y;
	float z;
	float w;
};

inline Vec3 operator+(const float s, const Vec3& v)
{
	return Vec3(s + v.x, s + v.y, s + v.z);
}

inline Vec3 operator-(const float s, const Vec3& v)
{
	return Vec3(s - v.x, s - v.y, s - v.z);
}

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

inline Vec3 operator/(const float s, const Vec3& v)
{
	return Vec3(s / v.x, s / v.y, s / v.z);
}

inline float lerp(float f, float a, float b)
{
	return a * (1.0 - f) + (b * f);
}

inline Vec3 lerp(float f, const Vec3& a, const Vec3& b)
{
	return a * (1.0 - f) + (b * f);
}

inline bool GetBoxIntersection2D(const Vec2& boxAmins, const Vec2& boxAmaxs,
	const Vec2& boxBmins, const Vec2& boxBmaxs)
{
	float xMin1 = boxAmins.x;
	float yMin1 = boxAmins.y;

	float xMax1 = boxAmaxs.x;
	float yMax1 = boxAmaxs.y;

	float xMin2 = boxBmins.x;
	float yMin2 = boxBmins.y;

	float xMax2 = boxBmaxs.x;
	float yMax2 = boxBmaxs.y;

	if (xMin1 < xMax2 || xMax1 > xMin2)
		return false;
	if (yMin1 < yMax2 || yMax1 > yMin2)
		return false;

	return true;
}

inline float DegtoRad(float _deg)
{
	return _deg * M_PI / 180.0f;
}

inline float RadtoDeg(float _rad)
{
	return _rad / M_PI * 180.0f;
}

inline Vec3 sub(const Vec3& _a, const Vec3& _b)
{
	return Vec3(_a.x - _b.x, _a.y - _b.y, _a.z - _b.z);
}

inline Vec3 sub(const Vec3 _a, float _b)
{
	return Vec3(_a.x - _b, _a.y - _b, _a.z - _b);
}

inline Vec3 mul(const Vec3& _a, const Vec3& _b)
{
	return Vec3(_a.x * _b.x, _a.y * _b.y, _a.z * _b.z);
}

inline Vec3 mul(const Vec3& _a, float _b)
{
	return Vec3(_a.x * _b, _a.y * _b, _a.z * _b);
}

inline float dot(const Vec3& _a, const Vec3& _b)
{
	return _a.x * _b.x + _a.y * _b.y + _a.z * _b.z;
}

inline float length(const Vec3& _a)
{
	return sqrt(dot(_a, _a));
}

inline Vec3 normalize(const Vec3& _a)
{
	const float invLen = 1.0f / length(_a);
	const Vec3 result = mul(_a, invLen);
	return result;
}

inline Vec3 cross(const Vec3& _a, const Vec3& _b)
{
	return Vec3(_a.y * _b.z - _a.z * _b.y, _a.z * _b.x - _a.x * _b.z, _a.x * _b.y - _a.y * _b.x);
}

// Matrix class
struct Matrix
{
	union {
		struct {
			float _11, _12, _13, _14;
			float _21, _22, _23, _24;
			float _31, _32, _33, _34;
			float _41, _42, _43, _44;
		};
		struct {
			Vec3 i;	float _14_;
			Vec3 j;	float _24_;
			Vec3 k;	float _34_;
			Vec3 c;	float _44_;
		};
		float m[4][4];
	};

	float* data() const { return (float*)this; }
	float* operator()()const{ return (float*)this; }
};

extern Matrix g_identityMatrix;

inline Matrix MakePerspective(float fovy, float aspect, float znear, float zfar)
{
	Matrix M = {};

	float f = 1.f / tanf(fovy / 2.f),
		A = (zfar + znear) / (znear - zfar),
		B = (2.f * zfar * znear) / (znear - zfar);

	M._11 = f / aspect; M._12 =  0.f; M._13 =  0.f; M._14 =  0.f;
	M._21 = 0.f;        M._22 =  f;   M._23 =  0.f; M._24 =  0.f;
	M._31 = 0.f;        M._32 =  0.f; M._33 =  A;   M._34 =  B;
	M._41 = 0.f;        M._42 =  0.f; M._43 = -1.f; M._44 =  0.f;

	return M;
}

struct BoundingBox
{
	glm::vec3 min;
	glm::vec3 max;
};

struct OBB
{
	BoundingBox bbox;
	glm::mat4 rot;
};

extern BoundingBox g_identityBoundingBox;

void TransformBoundingBox(BoundingBox& bbox, const glm::mat4& model);
void TransformOBB(OBB& obb, const BoundingBox& bbox, const glm::mat4& model);

inline bool GetBBoxIntersection3D(const BoundingBox& boxA, const BoundingBox& boxB)
{
	return ((boxA.max.x >= boxB.min.x) && (boxA.min.x <= boxB.max.x) &&
			(boxA.max.z >= boxB.min.z) && (boxA.min.z <= boxB.max.z) &&
			(boxA.max.y >= boxB.min.y) && (boxA.min.y <= boxB.max.y));
}

inline bool RayBoxIntersection(const Vec3& ray_pos, const Vec3& ray_dir, BoundingBox& box, float& tmin)
{
	float lo = ray_dir.x * (box.min.x - ray_pos.x);
	float hi = ray_dir.x * (box.max.x - ray_pos.x);

	float tmax;
	tmin = fminf(lo, hi);
	tmax = fmaxf(lo, hi);

	float lo1 = ray_dir.y * (box.min.y - ray_pos.y);
	float hi1 = ray_dir.y * (box.max.y - ray_pos.y);

	tmin = fmaxf(tmin, fminf(lo1, hi1));
	tmax = fminf(tmax, fmaxf(lo1, hi1));

	float lo2 = ray_dir.z * (box.min.z - ray_pos.z);
	float hi2 = ray_dir.z * (box.max.z - ray_pos.z);

	tmin = fmaxf(tmin, fminf(lo2, hi2));
	tmax = fminf(tmax, fmaxf(lo2, hi2));

	return (tmin <= tmax) && (tmax > 0.f);
}

// GLM Stuff

inline glm::vec3 ToGlm(const Vec3& v)
{
	return glm::vec3(v.x, v.y, v.z);
}

inline Vec3 ToEngine(const glm::vec3& v)
{
	return Vec3(v.x, v.y, v.z);
}

#endif // !MATHS_H
