#include <math.h>
V2f V2F(F32 x, F32 y) {
	V2f result = { x, y };
	return result;
}

function V2f V2f_Scale(V2f x, F32 scale) {
  return V2F(x.x * scale, x.y * scale);
}

V2f NormaliseV2F(V2f x) {
  F32 magnitude = sqrtf((x.x * x.x) + (x.y * x.y));
  if(magnitude > 0.0){
    F32 inverse = 1.0f/magnitude;
    return V2F(x.x*inverse, x.y*inverse);
  }
  return x;
}

V3f V3F(F32 x, F32 y, F32 z) {
	V3f result = { x, y, z };
	return result;
}

V4f V4F(F32 x, F32 y, F32 z, F32 w) {
	V4f result = { x, y, z, w };
	return result;
}

R2f R2F(V2f min, V2f max) {
	R2f result = { min, max };
	return result;
}

V3f V3f_Neg(V3f x) {
	V3f result = { -x.x, -x.y, -x.z };
	return result;
}

V3f V3f_Scale(V3f x, F32 s) {
	V3f result = { s * x.x, s * x.y, s * x.z };
	return result;
}

V3f V3f_Sub(V3f a, V3f b) {
	V3f result = { a.x - b.x, a.y - b.y, a.z - b.z };
	return result;
}

F32 V3f_Dot(V3f a, V3f b) {
	F32 result = (a.x * b.x) + (a.y * b.y) + (a.z * b.z);
	return result;
}

F32 V4f_Dot(V4f a, V4f b) {
	F32 result = (a.x * b.x) + (a.y * b.y) + (a.z * b.z) + (a.w * b.w);
	return result;
}

Mat4x4F M4x4F_Rows(V3f x, V3f y, V3f z) {
	Mat4x4F result = {
		x.x, x.y, x.z, 0,
		y.x, y.y, y.z, 0,
		z.x, z.y, z.z, 0,
		0,   0,   0,   1
	};

	return result;
}

Mat4x4F M4x4F_Columns(V3f x, V3f y, V3f z) {
	Mat4x4F result = {
		x.x, y.x, z.x, 0,
		x.y, y.y, z.y, 0,
		x.z, y.z, z.z, 0,
		0,   0,   0,   1
	};

	return result;
}

Mat4x4F M4x4F_Mul(Mat4x4F a, Mat4x4F b) {
	Mat4x4F result;

	for (U32 r = 0; r < 4; ++r) {
		for (U32 c = 0; c < 4; ++c) {
			result.m[r][c] =
				(a.m[r][0] * b.m[0][c]) + (a.m[r][1] * b.m[1][c]) +
				(a.m[r][2] * b.m[2][c]) + (a.m[r][3] * b.m[3][c]);
		}
	}

	return result;
}

V4f M4x4F_VMul4(Mat4x4F m, V4f v) {
	V4f result;
	result.x = V4f_Dot(m.r[0], v);
	result.y = V4f_Dot(m.r[1], v);
	result.z = V4f_Dot(m.r[2], v);
	result.w = V4f_Dot(m.r[3], v);

	return result;
}

V3f M4x4F_VMul3(Mat4x4F m, V3f v) {
	V4f tx;
	tx.xyz = v;
	tx.w   = 1.0f;

	V3f result = M4x4F_VMul4(m, tx).xyz;
	return result;
}

Mat4x4FInv M4x4F_Perspective(F32 fov, F32 aspect, F32 nearp, F32 farp) {
	F32 focal_length = 1.0f / tanf(0.5f * (PI_F32 * (fov / 360.0f)));

	F32 a = focal_length;
	F32 b = focal_length * aspect;

	F32 c = -(nearp + farp) / (farp - nearp);
	F32 d = -(2.0f * nearp * farp) / (farp - nearp);

	Mat4x4FInv result = {
		// fwd
		{
			a, 0,  0, 0,
			0, b,  0, 0,
			0, 0,  c, d,
			0, 0, -1, 0
		},
		// inv
		{
			(1 / a),  0,       0,      0,
			0,       (1 / b),  0,      0,
			0,        0,       0,     -1,
			0,        0,      (1/ d), (c / d)
		}
	};

	return result;
}

Mat4x4FInv M4x4F_CameraView(V3f x, V3f y, V3f z, V3f p) {
	Mat4x4FInv result;

	// Construct orthonomal basis from axes
	//
	result.fwd = M4x4F_Rows(x, y, z);
	V3f txp    = V3f_Neg(M4x4F_VMul3(result.fwd, p));

	// Translate by txp
	//
	result.fwd.r[0].w += txp.x;
	result.fwd.r[1].w += txp.y;
	result.fwd.r[2].w += txp.z;

	// Calculate inverse axes
	//
	V3f ix = V3f_Scale(x, 1.0f / V3f_Dot(x, x));
	V3f iy = V3f_Scale(y, 1.0f / V3f_Dot(y, y));
	V3f iz = V3f_Scale(z, 1.0f / V3f_Dot(z, z));

	// Calculate inverse position
	//
	V3f ip;
	ip.x = (txp.x * ix.x) + (txp.y * iy.x) + (txp.z * iz.x);
	ip.y = (txp.x * ix.y) + (txp.y * iy.y) + (txp.z * iz.y);
	ip.z = (txp.x * ix.z) + (txp.y * iy.z) + (txp.z * iz.z);

	result.inv = M4x4F_Columns(ix, iy, iz);

	// Translate by ip
	//
	result.inv.r[0].w -= ip.x;
	result.inv.r[1].w -= ip.y;
	result.inv.r[2].w -= ip.z;

	return result;
}

// Random

Random Random_Seed(U64 seed) {
	Random result = { seed };
	return result;
}

U64 Random_Next(Random *rnd) {
	U64 result = rnd->state;

	result ^= (result << 13);
	result ^= (result >>  7);
	result ^= (result << 17);

	rnd->state = result;

	return result;
}

F32 Random_F32(Random *rnd, F32 min, F32 max) {
	F32 result = min + (Random_Unilateral(rnd) * (max - min));
	return result;
}

F64 Random_F64(Random *rnd, F64 min, F64 max) {
	F64 result = min + (Random_Unilateral(rnd) * (max - min));
	return result;
}

U32 Random_U32(Random *rnd, U32 min, U32 max) {
	U32 result = min + (U32) (Random_Unilateral(rnd) * (max - min));
	return result;
}

F32 Random_Unilateral(Random *rnd) {
	F32 result = Random_Next(rnd) / (F32) U64_MAX;
	return result;
}

F32 Random_Bilateral(Random *rnd) {
	F32 result = -1.0f + (2.0f * Random_Unilateral(rnd));
	return result;
}

V2f V2f_Clip(V2f screen_xy, V2f screen_size) {
    V2f result;
    result.x = ((screen_xy.x / screen_size.w) * 2.0f) - 1.0f;
    result.y = ((screen_xy.y / screen_size.h) * 2.0f) - 1.0f;

    return result;
}

