#if USE_SIMD
import simd
#endif


#if USE_SIMD

public typealias Matrix4x4 = simd_float4x4
public extension Matrix4x4
{
	typealias T = Float
}

#else

public struct Matrix4x4<T: FloatingPoint>: Equatable
{
	public typealias Scalar = T

	public var m00: T, m01: T, m02: T, m03: T
	public var m10: T, m11: T, m12: T, m13: T
	public var m20: T, m21: T, m22: T, m23: T
	public var m30: T, m31: T, m32: T, m33: T

	public init()
	{
		self.m00 = 1; self.m01 = 0; self.m02 = 0; self.m03 = 0
		self.m10 = 0; self.m11 = 1; self.m12 = 0; self.m13 = 0
		self.m20 = 0; self.m21 = 0; self.m22 = 1; self.m23 = 0
		self.m30 = 0; self.m31 = 0; self.m32 = 0; self.m33 = 1
	}

	public init(
		_ a00: T, _ a01: T, _ a02: T, _ a03: T,
		_ a10: T, _ a11: T, _ a12: T, _ a13: T,
		_ a20: T, _ a21: T, _ a22: T, _ a23: T,
		_ a30: T, _ a31: T, _ a32: T, _ a33: T)
	{
		self.m00 = a00; self.m01 = a01; self.m02 = a02; self.m03 = a03
		self.m10 = a10; self.m11 = a11; self.m12 = a12; self.m13 = a13
		self.m20 = a20; self.m21 = a21; self.m22 = a22; self.m23 = a23
		self.m30 = a30; self.m31 = a31; self.m32 = a32; self.m33 = a33
	}
}

public extension Matrix4x4
{
	init(diagonal d: Vector4<T>)
	{
		self.m00 = d.x; self.m01 =   0; self.m02 =   0; self.m03 =   0
		self.m10 =   0; self.m11 = d.y; self.m12 =   0; self.m13 =   0
		self.m20 =   0; self.m21 =   0; self.m22 = d.z; self.m23 =   0
		self.m30 =   0; self.m31 =   0; self.m32 =   0; self.m33 = d.w
		
	}

	init(_ row0: Vector4<T>, _ row1: Vector4<T>, _ row2: Vector4<T>, _ row3: Vector4<T>)
	{
		self.m00 = row0.x; self.m01 = row0.y; self.m02 = row0.z; self.m03 = row0.w
		self.m10 = row1.x; self.m11 = row1.y; self.m12 = row1.z; self.m13 = row1.w
		self.m20 = row2.x; self.m21 = row2.y; self.m22 = row2.z; self.m23 = row2.w
		self.m30 = row3.x; self.m31 = row3.y; self.m32 = row3.z; self.m33 = row3.w
	}

	/*
	init(_ col0: Vector4<T>, _ col1: Vector4<T>, _ col2: Vector4<T>, _ col3: Vector4<T>)
	{
		self.m00 = col0.x; self.m01 = col1.x; self.m02 = col2.x; self.m03 = col3.x
		self.m10 = col0.y; self.m11 = col1.y; self.m12 = col2.y; self.m13 = col3.y
		self.m20 = col0.z; self.m21 = col1.z; self.m22 = col2.z; self.m23 = col3.z
		self.m30 = col0.w; self.m31 = col1.w; self.m32 = col2.w; self.m33 = col3.w
	}
	*/

	subscript(index: Int) -> Vector4<T>
	{
		get
		{
			switch index
			{
			case 0: rows.0
			case 1: rows.1
			case 2: rows.2
			case 3: rows.3
			default: fatalError()
			}
		}
	}

	var rows: (Vector4<T>, Vector4<T>, Vector4<T>, Vector4<T>)
	{(
		.init(m00, m01, m02, m03),
		.init(m10, m11, m12, m13),
		.init(m20, m21, m22, m23),
		.init(m30, m31, m32, m33))
	}

	var columns: (Vector4<T>, Vector4<T>, Vector4<T>, Vector4<T>)
	{(
		.init(m00, m10, m20, m30),
		.init(m01, m11, m21, m31),
		.init(m02, m12, m22, m32),
		.init(m03, m13, m23, m33))
	}

	var transpose: Self
	{
		Self(
			m00, m10, m20, m30,
			m01, m11, m21, m31,
			m02, m12, m22, m32,
			m03, m13, m23, m33)
	}

	static func * (lhs: Self, rhs: Self) -> Self
	{
		let l = rhs, r = lhs
		return Self(
			l.m00 * r.m00 + l.m01 * r.m10 + l.m02 * r.m20 + l.m03 * r.m30,
			l.m00 * r.m01 + l.m01 * r.m11 + l.m02 * r.m21 + l.m03 * r.m31,
			l.m00 * r.m02 + l.m01 * r.m12 + l.m02 * r.m22 + l.m03 * r.m32,
			l.m00 * r.m03 + l.m01 * r.m13 + l.m02 * r.m23 + l.m03 * r.m33,
			l.m10 * r.m00 + l.m11 * r.m10 + l.m12 * r.m20 + l.m13 * r.m30,
			l.m10 * r.m01 + l.m11 * r.m11 + l.m12 * r.m21 + l.m13 * r.m31,
			l.m10 * r.m02 + l.m11 * r.m12 + l.m12 * r.m22 + l.m13 * r.m32,
			l.m10 * r.m03 + l.m11 * r.m13 + l.m12 * r.m23 + l.m13 * r.m33,
			l.m20 * r.m00 + l.m21 * r.m10 + l.m22 * r.m20 + l.m23 * r.m30,
			l.m20 * r.m01 + l.m21 * r.m11 + l.m22 * r.m21 + l.m23 * r.m31,
			l.m20 * r.m02 + l.m21 * r.m12 + l.m22 * r.m22 + l.m23 * r.m32,
			l.m20 * r.m03 + l.m21 * r.m13 + l.m22 * r.m23 + l.m23 * r.m33,
			l.m30 * r.m00 + l.m31 * r.m10 + l.m32 * r.m20 + l.m33 * r.m30,
			l.m30 * r.m01 + l.m31 * r.m11 + l.m32 * r.m21 + l.m33 * r.m31,
			l.m30 * r.m02 + l.m31 * r.m12 + l.m32 * r.m22 + l.m33 * r.m32,
			l.m30 * r.m03 + l.m31 * r.m13 + l.m32 * r.m23 + l.m33 * r.m33)
	}
}

#endif

#if USE_SIMD

public extension Matrix4x4
{
	init(quat q: Quaternion<T>)
	{
		self = .init(simd_quatf(ix: q.x, iy: q.y, iz: q.z, r: q.w)).transpose
		/*
		let FUCK = simd_matrix4x4(simd_quatf(ix: q.x, iy: q.y, iz: q.z, r: q.w))
		self = .init(
			FUCK.columns.0.x, FUCK.columns.1.x, FUCK.columns.2.x, FUCK.columns.3.x,
			FUCK.columns.0.y, FUCK.columns.1.y, FUCK.columns.2.y, FUCK.columns.3.y,
			FUCK.columns.0.z, FUCK.columns.1.z, FUCK.columns.2.z, FUCK.columns.3.z,
			FUCK.columns.0.w, FUCK.columns.1.w, FUCK.columns.2.w, FUCK.columns.3.w).transpose
		*/
	}
}

#else

public extension Matrix4x4 where T == Float
{
	init(_ m3: Matrix3x3<T>)
	{
		self.m00 = m3.m00; self.m01 = m3.m01; self.m02 = m3.m02; self.m03 = 0
		self.m10 = m3.m10; self.m11 = m3.m11; self.m12 = m3.m12; self.m13 = 0
		self.m20 = m3.m20; self.m21 = m3.m21; self.m22 = m3.m22; self.m23 = 0
		self.m30 =      0; self.m31 =      0; self.m32 =      0; self.m33 = 1
	}

	init(quat q: Quaternion<T>)
	{
		/*
		let qq = Quaternion<T>(q.w * q.w, q.x * q.x, q.y * q.y, q.z * q.z)
		return .init(
			.init(
				qq.w + qq.x - qq.y - qq.z,
				2.0 * (q.x * q.y - q.w * q.z),
				2.0 * (q.x * q.z + q.w * q.y),
				0.0),
			.init(
				2.0 * (q.x * q.y + q.w * q.z),
				qq.w - qq.x + qq.y - qq.z,
				2.0 * (q.y * q.z - q.w * q.x),
				0.0),
			.init(
				2.0 * (q.x * q.z - q.w * q.y),
				2.0 * (q.y * q.z + q.w * q.x),
				qq.w - qq.x - qq.y + qq.z,
				0.0),
			.init(0.0, 0.0, 0.0, 1.0))
		*/
		/*
		let qww = q.w * q.w
		let qxx = q.x * q.x
		let qyy = q.y * q.y
		let qzz = q.z * q.z
		.init(
			.init(
				qqw + qqx - qqy - qqz,
				2.0 * (q.x * q.y - q.w * q.z),
				2.0 * (q.x * q.z + q.w * q.y),
				0.0),
			.init(
				2.0 * (q.x * q.y + q.w * q.z),
				qqw - qqx + qqy - qqz,
				2.0 * (q.y * q.z - q.w * q.x),
				0.0),
			.init(
				2.0 * (q.x * q.z - q.w * q.y),
				2.0 * (q.y * q.z + q.w * q.x),
				qqw - qqx - qqy + qqz,
				0.0),
			.init(0.0, 0.0, 0.0, 1.0))
		*/
		let qww = q.w * q.w, qxx = q.x * q.x, qyy = q.y * q.y, qzz = q.z * q.z
		let qxy = q.x * q.y, qxz = q.x * q.z, qyz = q.y * q.z
		let qwx = q.w * q.x, qwy = q.w * q.y, qwz = q.w * q.z
		self = .init(
			.init(qww + qxx - qyy - qzz,       2 * (qxy - qwz),       2 * (qxz + qwy), 0),
			.init(      2 * (qxy + qwz), qww - qxx + qyy - qzz,       2 * (qyz - qwx), 0),
			.init(      2 * (qxz - qwy),       2 * (qyz + qwx), qww - qxx - qyy + qzz, 0),
			.init(                    0,                     0,                     0, 1)).transpose
	}

	@inline(__always) static var identity: Self { Self(diagonal: .one) }
}

#endif


public extension Matrix4x4
{
	// what the fuck lmao
	@inline(__always) static func * (lhs: Self, rhs: Vector3<T>) -> Vector3<T>
	{
		return .init(
			lhs.columns.0.x * rhs.x + lhs.columns.0.y * rhs.y + lhs.columns.0.z * rhs.z,
			lhs.columns.1.x * rhs.x + lhs.columns.1.y * rhs.y + lhs.columns.1.z * rhs.z,
			lhs.columns.2.x * rhs.x + lhs.columns.2.y * rhs.y + lhs.columns.2.z * rhs.z)
	}
}

#if USE_SIMD

public extension Matrix4x4
{
	@inline(__always) static func translate(_ v: Vector3<T>) -> Self
	{
		Self(
			.init(  1,   0,   0, 0),
			.init(  0,   1,   0, 0),
			.init(  0,   0,   1, 0),
			.init(v.x, v.y, v.z, 1))
	}

	@inline(__always) static func scale(_ v: Vector3<T>) -> Self
	{
		Self(
			.init(v.x,   0,   0,   0),
			.init(  0,   v.y, 0,   0),
			.init(  0,   0,   v.z, 0),
			.init(  0,   0,   0,   1))
	}

	@inline(__always) static func scale(scalar v: T) -> Self
	{
		Self(
			.init(v, 0, 0, 0),
			.init(0, v, 0, 0),
			.init(0, 0, v, 0),
			.init(0, 0, 0, 1))
	}

	@inline(__always) static func rotate(yawPitch: Vector2<T>) -> Self { return rotate(yaw: yawPitch.x, pitch: yawPitch.y) }

	static func rotate(yaw ytheta: T, pitch xtheta: T) -> Self
	{
		let xc = xtheta.cosine, xs = xtheta.sine
		let yc = ytheta.cosine, ys = ytheta.sine

		return Self(
			.init(yc, ys *  xs, -ys * xc, 0),
			.init( 0,       xc,       xs, 0),
			.init(ys, yc * -xs,  yc * xc, 0),
			.init( 0,        0,        0, 1))
	}

	@inline(__always) static func rotate(x theta: T) -> Self
	{
		let c = theta.cosine, s = theta.sine
		return Self(
			.init(1,  0, 0, 0),
			.init(0,  c, s, 0),
			.init(0, -s, c, 0),
			.init(0,  0, 0, 1))
	}

	@inline(__always) static func rotate(y theta: T) -> Self
	{
		let c = theta.cosine, s = theta.sine
		return Self(
			.init(c, 0, -s, 0),
			.init(0, 1,  0, 0),
			.init(s, 0,  c, 0),
			.init(0, 0,  0, 1))
	}

	@inline(__always) static func rotate(z theta: T) -> Self
	{
		let c = theta.cosine, s = theta.sine
		return Self(
			.init(c, -s, 0, 0),
			.init(s,  c, 0, 0),
			.init(0,  0, 1, 0),
			.init(0,  0, 0, 1))
	}

	static func perspective(fovY: T, aspect: T, zNear: T, zFar: T) -> Self
	{
		let h = 1 / (fovY * T(0.5)).tangent
		let w = h / aspect
		let invClipRange = 1 / (zFar - zNear)
		let z = -(zFar + zNear) * invClipRange
		let z2 = -(2 * zFar * zNear) * invClipRange
		return Self(
			.init(w, 0,  0,  0),
			.init(0, h,  0,  0),
			.init(0, 0,  z, -1),
			.init(0, 0, z2,  0))
	}

	static func lookAt(from: Vector3<T> = .zero, to: Vector3<T>, up: Vector3<T> = .up) -> Self
	{
		let forward   = (to - from).normalised
		let bitangent = forward.cross(up).normalised
		let tangent   = bitangent.cross(forward).normalised
		let normal    = -forward

		return Self(
			.init(bitangent, 0),
			.init(  tangent, 0),
			.init(   normal, 0),
			.init(0,  0,  0, 1))
	}
}

#else

public extension Matrix4x4 where T == Float
{
	@inline(__always) static func translate(_ v: Vector3<T>) -> Self
	{
		Self(
			.init(  1,   0,   0, 0),
			.init(  0,   1,   0, 0),
			.init(  0,   0,   1, 0),
			.init(v.x, v.y, v.z, 1))
	}

	@inline(__always) static func scale(_ v: Vector3<T>) -> Self
	{
		Self(
			.init(v.x,   0,   0,   0),
			.init(  0,   v.y, 0,   0),
			.init(  0,   0,   v.z, 0),
			.init(  0,   0,   0,   1))
	}

	@inline(__always) static func scale(scalar v: T) -> Self
	{
		Self(
			.init(v, 0, 0, 0),
			.init(0, v, 0, 0),
			.init(0, 0, v, 0),
			.init(0, 0, 0, 1))
	}

	static func rotate(axis v: Vector3<T>, angle theta: T) -> Self
	{
		//FIXME: THIS IS FUCKED UP FOR EVERYTHING OTHER THAN X AXIS ROTATION LOL
		/*
		let vv = v * v
		let xy = v.x * v.y, xz = v.x * v.z, yz = v.y * v.z
		let ts = sin(theta), tc = cos(theta)
		return Self(
			.init(
				vv.x + (tc * (1 - vv.x)),
				(xz - (tc * xy)) + (ts * v.z),
				(xz - (tc * xz)) - (ts * v.y), 0),
			.init(
				(xy - (tc * xy)) - (ts * v.z),
				vv.y + (tc * (1 - vv.y)),
				(xz - (tc * xz)) + (ts * v.x), 0),
			.init(
				(xz - (tc * xz)) + (ts * v.y),
				(xz - (tc * xz)) - (ts * v.y),
				vv.z + (tc * (1 - vv.z)), 1),
			.init(0, 0, 0, 1))
		*/
		let vxx = v.x * v.x, vxy = v.x * v.y, vxz = v.x * v.z
		let vyy = v.y * v.y, vyz = v.y * v.z
		let vzz = v.z * v.z
		let ts = theta.sine, tc = theta.cosine
		let ic = 1 - tc
		return Self(
			.init(
				ic * vxx + tc,
				ic * vxy - v.z * ts,
				ic * vxz + v.z * ts,
				0),
			.init(
				ic * vxy + v.z * ts,
				ic * vyy + tc,
				ic * vyz - v.x * ts,
				0),
			.init(
				ic * vxz - v.y * ts,
				ic * vyz + v.x * ts,
				ic * vzz + tc,
				0),
			.init(0, 0, 0, 1))
	}

	@inline(__always) static func rotate(yawPitch: Vector2<T>) -> Self { return rotate(yaw: yawPitch.x, pitch: yawPitch.y) }

	static func rotate(yaw ytheta: T, pitch xtheta: T) -> Self
	{
		let xc = xtheta.cosine, xs = xtheta.sine
		let yc = ytheta.cosine, ys = ytheta.sine

		return Self(
			.init(yc, ys *  xs, -ys * xc, 0),
			.init( 0,       xc,       xs, 0),
			.init(ys, yc * -xs,  yc * xc, 0),
			.init( 0,        0,        0, 1))
	}

	static func rotate(yaw ytheta: T, pitch xtheta: T, roll ztheta: T) -> Self
	{
		//FIXME: this doesn't null against control
		let xc = xtheta.cosine, xs = xtheta.sine
		let yc = ytheta.cosine, ys = ytheta.sine
		let zc = ztheta.cosine, zs = ztheta.sine

		let ysxs = ys * xs, ycxs = yc * xs

		let result = Mat4f(
			.init(yc * zc + ysxs * zs, yc * -zs + ysxs * zc, -ys * xc, 0),
			.init(            xc * zs,              xc * zc,       xs, 0),
			.init(ys * zc - ycxs * zs, ys * -zs - ycxs * zc,  yc * xc, 0),
			.init(                  0,                    0,        0, 1))
		let shouldBe = .rotate(z: ztheta) * .rotate(x: xtheta) * .rotate(y: ytheta)

		let epsilon: Float = .ulpOfOne
		if (result != shouldBe)
		{
			assert(abs(result[0][0] - shouldBe[0][0]) <= epsilon) // epsilon
			assert(result[1][0] == shouldBe[1][0])
			assert(abs(result[2][0] - shouldBe[2][0]) <= epsilon) // epsilon
			assert(result[3][0] == shouldBe[3][0])
			assert(abs(result[0][1] - shouldBe[0][1]) <= epsilon) // epsilon
			assert(result[1][1] == shouldBe[1][1])
			assert(abs(result[2][1] - shouldBe[2][1]) <= epsilon) // epsilon
			assert(result[3][1] == shouldBe[3][1])
			assert(result[0][2] == shouldBe[0][2])
			assert(result[1][2] == shouldBe[1][2])
			assert(result[2][2] == shouldBe[2][2])
			assert(result[3][2] == shouldBe[3][2])
			assert(result[0][3] == shouldBe[0][3])
			assert(result[1][3] == shouldBe[1][3])
			assert(result[2][3] == shouldBe[2][3])
			assert(result[3][3] == shouldBe[3][3])
		}
		return result
	}

	@inline(__always) static func rotate(x theta: T) -> Self
	{
		let c = theta.cosine, s = theta.sine
		return Self(
			.init(1,  0, 0, 0),
			.init(0,  c, s, 0),
			.init(0, -s, c, 0),
			.init(0,  0, 0, 1))
	}

	@inline(__always) static func rotate(y theta: T) -> Self
	{
		let c = theta.cosine, s = theta.sine
		return Self(
			.init(c, 0, -s, 0),
			.init(0, 1,  0, 0),
			.init(s, 0,  c, 0),
			.init(0, 0,  0, 1))
	}

	@inline(__always) static func rotate(z theta: T) -> Self
	{
		let c = theta.cosine, s = theta.sine
		return Self(
			.init(c, -s, 0, 0),
			.init(s,  c, 0, 0),
			.init(0,  0, 1, 0),
			.init(0,  0, 0, 1))
	}

	static func perspective(fovY: T, aspect: T, zNear: T, zFar: T) -> Self
	{
		let h = 1 / (fovY * 0.5).tangent
		let w = h / aspect
		let invClipRange = 1 / (zFar - zNear)
		let z = -(zFar + zNear) * invClipRange
		let z2 = -(2 * zFar * zNear) * invClipRange
		return Self(
			.init(w, 0,  0,  0),
			.init(0, h,  0,  0),
			.init(0, 0,  z, -1),
			.init(0, 0, z2,  0))
	}

	static func lookAt(from: Vector3<T> = .zero, to: Vector3<T>, up: Vector3<T> = .up) -> Self
	{
		let forward   = (to - from).normalised
		let bitangent = forward.cross(up).normalised
		let tangent   = bitangent.cross(forward).normalised
		let normal    = -forward

		return Self(
			.init(bitangent, 0),
			.init(  tangent, 0),
			.init(   normal, 0),
			.init(0,  0,  0, 1))
	}
}

#endif

#if USE_SIMD
public typealias Mat4f = Matrix4x4
#else
public typealias Mat4f = Matrix4x4<Float>
public typealias Mat4d = Matrix4x4<Double>
#endif
