module dlib.math;

import dlib.aliases;
import dlib.util;

import dlibincludes;

import core.stdc.math : tanf, cosf, sinf, sqrtf, fabsf;

import std.math;
import std.math.algebraic;
import std.traits;
import std.meta;
import std.format;
import std.stdio;

T
Max(T)(T a, T b)
{
	return a > b ? a : b;
}

T
Min(T)(T a, T b)
{
	return a < b ? a : b;
}

T
RoundUp(T)(T a, T b)
{
	a += b - 1;
	a -= a%b;
	return a;
}

T AlignPow2(T)(T v, T a)
{
	return (v + a - 1) & ~(a - 1);
}

f32 Radians(f32 deg)
{
	return deg * (PI / 180.0);
}

struct Vector(T, int N)
{
	static assert(N > 1 && N <= 4);

	enum _N = N;
	alias T _T;

	union
	{
		T[N] v = 0;
		struct
		{
			T x;
			alias x r;

			T y;
			alias y g;

			static if(N > 2)
			{
				T z;
				alias z b;
			}

			static if(N > 3)
			{
				T w;
				alias w a;
			}
		};
	}

	this(Vec3, f32)(Vec3 v3, f32 f) if(N == 4 && is(T: f32))
	{
		x = v3.x;
		y = v3.y;
		z = v3.z;
		w = f;
	}

	this(Arr)(Arr arr) if(is(Arr: typeof(v)))
	{
		this.v = arr;
	}

	this(Args...)(Args args) 
	{
		static if(args.length == 1)
		{
			opAssign!(Args[0])(args[0]);
		}
		else static if(args.length == N)
		{
			mixin(GenerateLoop!("v[@] = args[@];", N)());
		}
		else static if(args.length == 2 && N == 4)
		{
			v[0] = args[0];
			v[1] = args[0];
			v[2] = args[0];
			v[3] = args[1];
		}
		else
		{
			static assert(false, "Invalid Vector constructor");
		}
	}

	ref Vector opAssign(U)(U x) if(is(U: T) || isAssignable!(T, U)) 
	{
		mixin(GenerateLoop!("v[@] = x;", N)());
		return this;
	}

	ref Vector opAssign(U)(U u) if(isStaticArray!(U) && U.length == N && isAssignable!(T, typeof(*U.init.ptr)))
	{
		mixin(GenerateLoop!("v[@] = u[@];", N)());
		return this;
	}

	ref Vector opAssign(U)(U u) if(is(U : Vector))
	{
		v[] = u.v[];
		return this;
	}

	inout(T)* ptr() inout @property
	{
		return v.ptr;
	}

	bool opEquals(U)(U other) if(is(U: Vector!(T, N)))
	{
		bool result = true;

		foreach(i; 0 .. N)
		{
			if(fabsf(v[i] - other.v[i]) > 0.0000009)
			{
				result = false;
				break;
			}
		}

		return result;
	}

	int opDollar()
	{
		return N;
	}

	T[] opSlice()
	{
		return v[];
	}

	Vector opUnary(string op)() if(op == "+" || op == "-" || op == "~" || op == "!")
	{
		Vector result;
		mixin(GenerateLoop!("res.v[@] = " ~ op ~ " v[@];", N)());
		return res;
	}

	ref Vector opOpAssign(string op, U)(U value) if(is(U: Vector))
	{
		mixin(GenerateLoop!("v[@] " ~ op ~ "= value.v[@];", N)());
		return this;
	}

	ref Vector opOpAssign(string op, U)(U value) if(IsConvertible!(U))
	{
		Vector conv = value;
		return opOpAssign!(op)(conv);
	}

	@property auto opDispatch(string op, U = void)() if(ValidSwizzle!(op) && op.length <= 4)
	{
		Vector!(T, op.length) result;
		enum index_tuple = SwizzleTuple!(op);
		static foreach(i, index; index_tuple)
		{
			result.v[i] = v[index];
		}
		return result;
	}

	@property void opDispatch(string op, U)(U x) if((op.length > 1) && ValidUniqueSwizzle!(op) && is(typeof(Vector!(T, op.length)(x))))
	{
		Vector!(T, op.length) conv = x;
		enum index_tuple = SwizzleTuple!(op);
		static foreach(i, index; index_tuple)
		{
			v[index] = conv[i];
		}
	}

	static if(N == 4)
	{
		Vector opBinary(string op, U)(U operand) if((is(U: Vector!(f32, 4)) && is(T: f32)) && (op == "*" || op == "+" || op == "-" || op == "/"))
		{
			Vector result;
			f32* l = &x;
			f32* r = &operand.x;
			f32* res = &result.x;
			
			asm 
			{
				mov R8, l;
				mov R9, r;
				mov R10, res;
				movups XMM0, x.offsetof[R8];
				movups XMM1, operand.x.offsetof[R9];
			}
			static if(op == "*")      asm  { mulps XMM0, XMM1; }
			else static if(op == "-") asm  { subps XMM0, XMM1; }
			else static if(op == "+") asm  { addps XMM0, XMM1; }
			else static if(op == "/") asm  { divps XMM0, XMM1; }
			asm 
			{
				movups result.x.offsetof[R10], XMM0;
			}

			return result;
		}

		Vector opBinary(string op, U)(U operand) if(IsConvertible!(U) && (op == "*" || op == "+" || op == "-" || op == "/")) 
		{
			Vector result;
			Vector other = operand;
			f32* l = &x;
			f32* r = &other.x;
			f32* res = &result.x;
			
			asm 
			{
				mov R8, l;
				mov R9, r;
				mov R10, res;
				movups XMM0, x.offsetof[R8];
				movups XMM1, other.x.offsetof[R9];
			}
			static if(op == "*")      asm  { mulps XMM0, XMM1; }
			else static if(op == "-") asm  { subps XMM0, XMM1; }
			else static if(op == "+") asm  { addps XMM0, XMM1; }
			else static if(op == "/") asm  { divps XMM0, XMM1; }
			asm 
			{
				movups result.x.offsetof[R8], XMM0;
			}

			return result;
		}
	}
	else
	{
		Vector opBinary(string op, U)(U operand) if(is(U: Vector) && U._N == N && (op == "*" || op == "+" || op == "-" || op == "/"))
		{
			Vector res;
			mixin(GenerateLoop!("res.v[@] = v[@] " ~ op ~ " operand.v[@];", N)());
			return res;
		}

		Vector opBinary(string op, U)(U operand) if(IsConvertible!(U) && (op == "*" || op == "+" || op == "-" || op == "/"))
		{
			Vector res;
			Vector other = operand;
			mixin(GenerateLoop!("res.v[@] = v[@] " ~ op ~ " other.v[@];", N)());
			return res;
		}
	}

	

	ref T opIndex(size_t i)
	{
		return v[i];
	}

	T opIndexAssign(U : T)(U x, size_t i)
	{
		return v[i] = x;
	}

	U opCast(U)() if(IsVector!(U) && (U._N == _N))
	{
		U result;
		mixin(GenerateLoop!("res.v[@] = cast(U._T)v[@];", N)());
		return result;
	}

	template IsConvertible(T)
	{
		enum bool IsConvertible = (!is(T : Vector)) && is(typeof({ T x; Vector v = x; }()));
	}

	template SwizzleIndex(char c)
	{
		static if((c == 'x' || c == 'r') && N > 0)
			enum SwizzleIndex = 0;
		else static if((c == 'y' || c == 'g') && N > 1)
			enum SwizzleIndex = 1;
		else static if((c == 'z' || c == 'b') && N > 2)
			enum SwizzleIndex = 2;
		else static if((c == 'w' || c == 'a') && N > 3)
			enum SwizzleIndex = 3;
		else
			enum SwizzleIndex = -1;
	}

	template SwizzleSet(char c)
	{
		static if(c == 'x' || c == 'y' || c == 'z' || c == 'w')
			enum SwizzleSet = 0;
		else static if(c == 'r' || c == 'g' || c == 'b' || c == 'a')
			enum SwizzleSet = 1;
		else
			enum SwizzleSet = -1;
	}

	template SwizzleTuple(string op)
	{
		enum op_length = op.length;
		static if(op.length == 0)
			enum SwizzleTuple = [];
		else
			enum SwizzleTuple = [ SwizzleIndex!(op[0])] ~ SwizzleTuple!(op[1 .. op.length]);
	}

	template SearchString(char c, string s)
	{
		static if(s.length == 0)
		{
			enum bool result = false;
		}
		else
		{
			enum string tail = s[1 .. s.length];
			enum bool result = (s[0] == c) || SearchString!(c, tail).result;
		}
	}

	template UniqueChars(string s)
	{
		static if(s.length == 1)
		{
			enum bool result = true;
		}
		else
		{
			enum tail = s[1 .. s.length];
			enum bool result = !(SearchString!(s[0], tail).result) && UniqueChars!(tail).result;
		}
	}

	template ValidSwizzle(string op, int last_swizzle = -1)
	{
		static if(op.length == 0)
		{
			enum bool ValidSwizzle = true;
		}
		else
		{
			enum length = op.length;
			enum int swizzle_set = SwizzleSet!(op[0]);
			enum bool valid_swizzle_set = (last_swizzle == -1 || (swizzle_set == last_swizzle));
			enum bool ValidSwizzle = (SwizzleIndex!(op[0]) != -1) && valid_swizzle_set && ValidSwizzle!(op[1 .. length], swizzle_set);
		}
	}

	template ValidUniqueSwizzle(string op)
	{
		static if(ValidSwizzle!(op))
		{
			enum ValidUniqueSwizzle = UniqueChars!(op).result;
		}
		else
		{
			enum ValidUniqueSwizzle = false;
		}
	}
}

// TODO: fix alignment for non mat4s
align(16) struct Matrix(T, int D)
{
	static assert(D > 0 && D <= 4);

	alias Vector!(T, D) MatrixVec;
	alias T _T;
	alias T[4] Row;

	enum N = D*D;
	enum _D = D;

	union
	{
		T[N] v = 0;
		Row[D] rows;
		MatrixVec[D] vec;
		static if(D == 4) mat4 glm_mat;
		static if(D == 3) mat3 glm_mat;
		static if(D == 2) mat2 glm_mat;
	}

	// TODO: setup 

	this(U...)(U values)
	{
		static if((U.length == N) && allSatisfy!(IsTypeAssignable, U))
		{
			static foreach(i, x; values)
			{
				v[i] = x;
			}
		}
		else static if((U.length == 1) && (isAssignable!(U[0])) && (!is(U[0] : Matrix)))
		{
			v[] = values[0];
		}
		else static assert(false, "Cannot construct matrix with provided parameters");
	}

	this(U)(T x)
	{
		static foreach(i; 0 .. N)
		{
			v[i] = x;
		}
	}

	this(U)(U x) if(is(U: typeof(this.v)))
	{
		this.v = x;
	}

	@property inout(T)* ptr() inout
	{
		return v.ptr;
	}

	ref Matrix opAssign(U : T)(U x)
	{
		static foreach(i; 0 .. N)
		{
			v[i] = x;
		}

		return this;
	}

	ref Matrix opAssign(U : Matrix)(U x)
	{
		static foreach(i; 0 .. N)
		{
			v[i] = x.v[i];
		}

		return this;
	}

	ref Matrix opAssign(U)(U x) if(IsMatrixInstantiation!(U) && is(U._T : _T) && (!is(U: Matrix) && (U.N != N)))
	{
		static foreach(i; 0 .. N)
		{
			v[i] = x.v[i];
		}

		return this;
	}

	ref T opIndex(size_t i, size_t j)
	{
		return v[(i * D) + j];
	}

	T opIndexAssign(U: T)(U x, size_t i, size_t j)
	{
		return v[(i * D) + j] = x;
	}

	bool opEquals(U)(U other) if(is(U: Matrix!(T, D)))
	{
		bool result = true;

		static foreach(i; 0 .. N)
		{
			if(fabsf(this.v[i] - other.v[i]) > 0.0000009)
			{
				result = false;
			}
		}

		return result;
	}

	Matrix opBinary(string op)(T scalar) if(op == "*")
	{
		Matrix result;

		static foreach(i; 0 .. N)
		{
			result.v[i] = v[i] * scalar;
		}

		return result;
	}

	static if(D == 4)
	{
		Vec4 opBinary(string op, U)(U x) if(is(U: Vec4) && is(T: f32) && (op == "*"))
		{
			Vec4 result = 0.0;
			glm_mat4_mulv(glm_mat.ptr, x.v.ptr, result.v.ptr);
			return result;
		}

		Matrix opBinary(string op, U)(U x) if(is(U: Matrix!(T, D)) && is(T: f32) && D == 4 && (op == "*")) 
		{
			Matrix result;
			MatZero(&result);

			Mat4MulASM(&this, &x, &result);

			return result;
		}
	}

	template IsTypeAssignable(U)
	{
		enum bool IsTypeAssignable = std.traits.isAssignable!(T, U);
	}

	template IsMatrixInstantiation(U)
	{
		static void IsMatrix(T, int D)(Matrix!(T, D) x) {}
		
		enum bool IsMatrixInstantiation = is(typeof(IsMatrix(U.init)));
	}
}

struct Quat
{
	union
	{
		f32[4] v = 0;
		Vec4 vec;
		struct
		{
			f32 x;
			f32 y;
			f32 z;
			f32 w;
		};
	};

	this(f32 w, f32 x, f32 y, f32 z)
	{
		vec.x = x;
		vec.y = y;
		vec.z = z;
		vec.w = w;
	}

	U opCast(U)() if(is(U: Mat4))
	{
		Mat4 result;
		glm_quat_mat4(vec.ptr, result.glm_mat.ptr);
		return result;
	}

	Quat opBinary(string op, U)(U r) if(op == "*" && is(U: Quat))
	{
		Quat q;

		q.x = this.w * r.x + this.x * r.w + this.y * r.z - this.z * r.y;
		q.y = this.w * r.y - this.x * r.z + this.y * r.w + this.z * r.x;
		q.z = this.w * r.z + this.x * r.y - this.y * r.x + this.z * r.w;
		q.w = this.w * r.w - this.x * r.x - this.y * r.y - this.z * r.z;

		return q;
	}
}

void
Mat4MulASM(Mat4* l, Mat4* r, Mat4* result)
{
	asm @trusted 
	{
		mov    R8,   l;
		mov    R9,   r;
		mov    R10,  result;

		movups XMM0, [R8];
		movups XMM1, [R9+00];
		movups XMM2, [R9+16];
		movups XMM3, [R9+32];
		movups XMM4, [R9+48];

		movups XMM6, XMM1;
		shufps XMM6, XMM6, 0; // XMM5 = vec.xxxx;
		mulps  XMM6, XMM0; // XMM6 = col1;

		movups XMM7, XMM2;
		shufps XMM7, XMM7, 0;
		mulps  XMM7, XMM0; // XMM7 = col2;

		movups XMM8, XMM3;
		shufps XMM8, XMM8, 0;
		mulps  XMM8, XMM0; // XMM8 = col3;

		movups XMM9, XMM4;
		shufps XMM9, XMM9, 0;
		mulps  XMM9, XMM0; // XMM9 = col4;

		movups XMM0, [R8+16];

		movups XMM5, XMM1;
		shufps XMM5, XMM5, 85; // XMM5 = vec.yyyy;
		mulps  XMM5, XMM0;
		addps  XMM6, XMM5;

		movups XMM5, XMM2;
		shufps XMM5, XMM5, 85;
		mulps  XMM5, XMM0;
		addps  XMM7, XMM5;

		movups XMM5, XMM3;
		shufps XMM5, XMM5, 85;
		mulps  XMM5, XMM0;
		addps  XMM8, XMM5;

		movups XMM5, XMM4;
		shufps XMM5, XMM5, 85;
		mulps  XMM5, XMM0;
		addps  XMM9, XMM5;

		movups XMM0, [R8+32];

		movups XMM5, XMM1;
		shufps XMM5, XMM5, 170; // XMM5 = vec.zzzz;
		mulps  XMM5, XMM0;
		addps  XMM6, XMM5;

		movups XMM5, XMM2;
		shufps XMM5, XMM5, 170;
		mulps  XMM5, XMM0;
		addps  XMM7, XMM5;

		movups XMM5, XMM3;
		shufps XMM5, XMM5, 170;
		mulps  XMM5, XMM0;
		addps  XMM8, XMM5;
 
		movups XMM5, XMM4;
		shufps XMM5, XMM5, 170;
		mulps  XMM5, XMM0;
		addps  XMM9, XMM5;

		movups XMM0, [R8+48];

		movups XMM5, XMM1;
		shufps XMM5, XMM5, 255; // XMM5 = vec.wwww;
		mulps  XMM5, XMM0;
		addps  XMM6, XMM5;

		movups XMM5, XMM2;
		shufps XMM5, XMM5, 255;
		mulps  XMM5, XMM0;
		addps  XMM7, XMM5;

		movups XMM5, XMM3;
		shufps XMM5, XMM5, 255;
		mulps  XMM5, XMM0;
		addps  XMM8, XMM5;

		movups XMM5, XMM4;
		shufps XMM5, XMM5, 255;
		mulps  XMM5, XMM0;
		addps  XMM9, XMM5;

		movups [R10+00], XMM6;
		movups [R10+16], XMM7;
		movups [R10+32], XMM8;
		movups [R10+48], XMM9;
	}
}

pragma(inline) Mat4 
Mat4Identity()
{
	return Mat4(
		1.0, 0.0, 0.0, 0.0,
		0.0, 1.0, 0.0, 0.0,
		0.0, 0.0, 1.0, 0.0,
		0.0, 0.0, 0.0, 1.0
	);
}

pragma(inline) void
Mat4Identity(Mat4* mat)
{
	MatZero(mat);

	(*mat)[0, 0] = 1.0;
	(*mat)[1, 1] = 1.0;
	(*mat)[2, 2] = 1.0;
	(*mat)[3, 3] = 1.0;
}

 pragma(inline) Mat3 
Mat3Identity()
{
	return Mat3(
		1.0, 0.0, 0.0,
		0.0, 1.0, 0.0,
		0.0, 0.0, 1.0
	);
}

 pragma(inline) Mat2
Mat2Identity()
{
	return Mat2(
		1.0, 0.0,
		0.0, 1.0
	);
}

pragma(inline) Quat
QuatFromAxis(f32 angle, Vec3 axis)
{
	Quat q;
	glm_quatv(q.vec.ptr, angle, axis.v.ptr);
	return q;
}

pragma(inline) f32
Dot(Vec2* l, Vec2* r)
{
	return l.x * r.x + l.y * r.y;
}

pragma(inline) f32
Dot(Vec3* l, Vec3* r)
{
	return l.x * r.x + l.y * r.y + l.z * r.z;
}

pragma(inline) f32
Dot(Vec4* l, Vec4* r)
{
	// TODO: SIMD this
	return l.x * r.x + l.y * r.y + l.z * r.z + l.w * r.w;
}

pragma(inline) f32
Norm(Vec3* v)
{
	return sqrtf(Dot(v, v));
}

pragma(inline) f32
Norm(Vec4* v)
{
	// TODO: SIMD this
	return sqrtf(Dot(v, v));
}

pragma(inline) void
Normalize(T)(T* vec) if(is(T: Vec2) || is(T: Vec3) || is(T: Vec4))
{
	f32 length = Norm(vec);

	if(length < f32.epsilon)
	{
		mixin(GenerateLoop!("vec.v[@] = 0.0;", vec._N)());
	}
	else
	{
		mixin(GenerateLoop!("vec.v[@] *= (1.0 / length);", vec._N)());
	}
}

pragma(inline) T
Normalize(T)(T vec) if(is(T: Vec2) || is(T: Vec3) || is(T: Vec4))
{
	Normalize(&vec);
	return vec;
}

pragma(inline) Quat
Normalize(Quat q)
{
	f32 dot = Norm(&q.vec);
 
	if(dot <= 0.0)
	{
		q = Quat(1.0, 0.0, 0.0, 0.0);
	}

	q.vec *= 1.0 / sqrtf(dot);

	return q;
}

pragma(inline) Mat4
Perspective(f32 fov, f32 aspect, f32 near, f32 far)
{
	Mat4 res;
	MatZero(&res);
	glm_perspective(fov, aspect, near, far, res.glm_mat.ptr);
	res[1, 1] *= -1.0;
	return res;
}

void
Ortho(Mat4* mat, f32 left, f32 bottom, f32 right, f32 top, f32 near, f32 far)
{
	MatZero(mat);
	glm_ortho(left, right, bottom, top, near, far, mat.glm_mat.ptr);
}

Mat4
Ortho(f32 left, f32 bottom, f32 right, f32 top, f32 near, f32 far)
{
	Mat4 mat;
	MatZero(&mat);
	glm_ortho(left, right, bottom, top, near, far, mat.glm_mat.ptr);
	return mat;
}

pragma(inline) Vec3
Rotate(Quat q, Vec3 vec)
{
	Quat p = Normalize(q);

	Vec3 i = p.vec.xyz;
	f32 r = p.vec.w;

	Vec3 v1 = i * (2.0 * Dot(&i, &vec));
	Vec3 v2 = i * (r * r - Dot(&i, &i));
	v1 += v2;

	v2 = i * vec;
	v2 = v2 * (2.0 * r);

	return v1 + v2;
}

pragma(inline) Mat4
LookAt(Vec3 eye, Vec3 center, Vec3 up)
{
	Mat4 result;
	MatZero(&result);
	glm_lookat(eye.v.ptr, center.v.ptr, up.v.ptr, result.glm_mat.ptr);
	return result;
}

pragma(inline) Mat4
Look(Vec3 eye, Vec3 dir, Vec3 up)
{
	return LookAt(eye, eye + dir, up);
}

pragma(inline) Vec3
Cross(Vec3 a, Vec3 b)
{
	Vec3 c;
	Cross(a, b, &c);
	return c;
}

pragma(inline) Vec3
CrossN(Vec3 a, Vec3 b)
{
	Vec3 c;
	Cross(a, b, &c);
	Normalize(&c);
	return c;
}

pragma(inline) void
CrossN(Vec3 a, Vec3 b, Vec3* dst)
{
	Cross(a, b, dst);
	glm_vec3_normalize(dst.v.ptr);
}

pragma(inline) void
Cross(Vec3 a, Vec3 b, Vec3* dst)
{
	glm_vec3_cross(a.v.ptr, b.v.ptr, dst.v.ptr);
}

pragma(inline) void
MatZero(Mat4* mat)
{
	auto v = &mat.vec;
	asm 
	{
		mov R8, v;
		xorps XMM0, XMM0;
		movups mat.vec.offsetof[R8]+00, XMM0;
		movups mat.vec.offsetof[R8]+16, XMM0;
		movups mat.vec.offsetof[R8]+32, XMM0;
		movups mat.vec.offsetof[R8]+48, XMM0;
	}
}

pragma(inline) void
Translate(Mat4* mat, Vec3 vec)
{
	glm_translate(mat.glm_mat.ptr, vec.v.ptr);
}

pragma(inline) Mat4
Inverse(Mat4 mat)
{
	Mat4 res;
	MatZero(&res);
	glm_mat4_inv(mat.glm_mat.ptr, res.glm_mat.ptr);
	return res;
}

void
Transform(Vec3* vec, Mat4* mat, f32 last = 1.0)
{
	Vec4 tvec = Vec4(*vec, last);
	tvec = *mat * tvec;
	*vec = tvec.xyz;
}

pragma(inline) f32
Lerp(f32 x, f32 y, f32 a)
{
	return x * (1 - a) + y * a;
}

pragma(inline) f32
InverseLerp(f32 v, f32 min, f32 max)
{
	return (v - min) / (max - min);
}

pragma(inline) f32
Remap(f32 v, f32 in_min, f32 in_max, f32 out_min, f32 out_max)
{
	f32 t = InverseLerp(v, in_min, in_max);
	return Lerp(out_min, out_max, t);
}

version(DLIB_TEST) unittest
{
	enum FLOAT_MAX = f32.max;
	enum FLOAT_MIN = -f32.max;

	/*
	import core.stdc.stdio;
	import core.stdc.stdlib;
	import core.stdc.time;
	import std.range : take;
	import std.algorithm.iteration : sum;

	void PrintMatrix(Mat4 mat)
	{
		foreach(i; 0 .. mat.N)
		{
			if(i % 4 == 0)
			{
				printf("\n");
			}
			printf("%.08f ", mat.v[i]);
		}
		printf("\n");
	}

	srand(cast(u32)time(null));

	f32 RandomFloat()
	{
		return cast(f32)(rand())/cast(f32)(RAND_MAX + 1.0);	
	}

	{ // Vec2 arithmetic
		Vec2 v1 = Vec2(5.0, 10.0);
		Vec2 v2 = Vec2(2.5, 5.0);

		Vec2 result = v1 * v2;

		assert(result == Vec2(12.5, 50.0), "Vec2 mul failure");

		result = v1 + v2;

		assert(result == Vec2(7.5, 15.0), "Vec2 add failure");

		result = v1 - v2;

		assert(result == Vec2(2.5, 5.0), "Vec2 sub failure");

		result = v1 / v2;

		assert(result == Vec2(2.0), "Vec2 div failure");
	}

	{ // Vec3 Arithmetic
		Vec3 v1 = Vec3(5.0, 10.0, 15.0);
		Vec3 v2 = Vec3(2.5, 5.0, 7.5);

		Vec3 result = v1 * v2;

		assert(result == Vec3(12.5, 50.0, 112.5), "Vec3 mul failure");

		result = v1 + v2;

		assert(result == Vec3(7.5, 15.0, 22.5), "Vec3 add failure");

		result = v1 - v2;

		assert(result == Vec3(2.5, 5.0, 7.5), "Vec3 sub failure");

		result = v1 / v2;

		assert(result == Vec3(2.0), "Vec3 div failure");
	}

	{ // Vec3 Arithmetic
		Vec4 v1 = Vec4(5.0, 10.0, 15.0, 20.0);
		Vec4 v2 = Vec4(2.5, 5.0, 7.5, 10.0);

		Vec4 result = v1 * v2;

		assert(result == Vec4(12.5, 50.0, 112.5, 200.0), "Vec4 mul failure");

		result = v1 + v2;

		assert(result == Vec4(7.5, 15.0, 22.5, 30.0), "Vec4 add failure");

		result = v1 - v2;

		assert(result == Vec4(2.5, 5.0, 7.5, 10.0), "Vec4 sub failure");

		result = v1 / v2;

		assert(result == Vec4(2.0), "Vec4 div failure");
	}

	{ // Mat4 Arithmetic
		Mat4 m1 = RandomMat4();
		Mat4 m2 = RandomMat4();
		Mat4 m3 = m1 * m2;
		Mat4 m4;
		
		MatZero(&m4);

		for(u32 i = 0; i < 4; i += 1)
		{
			for(u32 j = 0; j < 4; j += 1)
			{
				for(u32 k = 0; k < 4; k += 1)
				{
					m4.rows[i][j] += m1.rows[k][j] * m2.rows[i][k];
				}
			}
		}

		assert(m3 == m4, "Mat4 mul failure");
	}

	{ // Translate
		Mat4 mat = Mat4Identity();
		Vec4 vec = Vec4(1.0, 2.0, 3.0, 1.0);

		Translate(&mat, Vec3(13.0, 11.0, 7.0));
		Vec4 result = mat * vec;

		assert(result == Vec4(14.0, 13.0, 10.0, 1.0));

		mat = Mat4Identity();
		Translate(&mat, Vec3(1.0, -1.0, -5.0));
		result = mat * result;

		assert(result == Vec4(15.0, 12.0, 5.0, 1.0));
	}

	{ // Identity
		Mat4 identity = Mat4(
				1.0, 0.0, 0.0, 0.0,
				0.0, 1.0, 0.0, 0.0,
				0.0, 0.0, 1.0, 0.0,
				0.0, 0.0, 0.0, 1.0
				);
		Mat4 mat = Mat4Identity();

		assert(identity == mat);
	}


	{ // Inverse
		foreach(i; 0 .. 1000)
		{
			Mat4 m1 = RandomMat4();

			Mat4 m1_inv = Inverse(m1);
			Mat4 m1_reinv = Inverse(m1_inv);

			assert(m1 == m1_reinv, "Inverse test failed");
		}
	}

	{ // Cross
		Vec3 v1 = Vec3(2.0, -3.0, 4.0);
		Vec3 v2 = Vec3(12.0, -31.0, 43.0);

		Vec3 v3 = Cross(v1, v2);

		Vec3 v4 = Vec3(
			v1.y * v2.z - v1.z * v2.y,
			v1.z * v2.x - v1.x * v2.z,
			v1.x * v2.y - v1.y * v2.x
		);

		assert(v3 == v4, "Vec3 Cross failure");

		v3 = CrossN(v1, v2);

		Normalize(&v4);

		assert(v3 == v4, "Vec3 CrossN failure");
	}
	*/

	{ // Initializers
		u32[4] arr = [1, 2, 3, 4];
		Vec4 vec = Vec4(arr);
		assert(vec == Vec4(1.0, 2.0, 3.0, 4.0));

		Mat2 mat = Mat2(1.0, 0.0, 0.0, 1.0);

		f32[16] floats = 22.0;
		Mat4 mat4 = Mat4(floats);
		assert(mat4.v == floats);

		Quat quat = Quat(1.0, 1.0, 1.0, 1.0);

		Quat quat2;
	}

	{
		Vec3 vec = Vec3(1.0, 2.0, 3.0);
		Mat4 mat = Mat4(
			1.0, 1.0, 1.0, 1.0,
			2.0, 2.0, 2.0, 2.0,
			3.0, 3.0, 3.0, 3.0,
			4.0, 4.0, 4.0, 4.0
		);

		Transform(&vec, &mat, 1.0);

		assert(vec == Vec3(18.0));
	}
}
