package ru.dbotthepony.kstarbound.math.matrix

import ru.dbotthepony.kommons.arrays.Float2DArray
import ru.dbotthepony.kommons.arrays.mulMatrixComponents
import ru.dbotthepony.kommons.util.IStruct2f
import ru.dbotthepony.kommons.util.IStruct3f
import ru.dbotthepony.kommons.util.IStruct4f
import ru.dbotthepony.kstarbound.math.vector.Vector2f
import ru.dbotthepony.kstarbound.math.vector.Vector3f
import ru.dbotthepony.kstarbound.math.vector.Vector4f
import java.nio.ByteBuffer
import java.nio.FloatBuffer
import kotlin.math.cos
import kotlin.math.sin
import kotlin.math.tan

sealed class Matrix4f : Float2DArray() {
	abstract var c00: Float
	abstract var c01: Float
	abstract var c02: Float
	abstract var c03: Float
	abstract var c10: Float
	abstract var c11: Float
	abstract var c12: Float
	abstract var c13: Float
	abstract var c20: Float
	abstract var c21: Float
	abstract var c22: Float
	abstract var c23: Float
	abstract var c30: Float
	abstract var c31: Float
	abstract var c32: Float
	abstract var c33: Float

	fun c00() = c00
	fun c00(value: Float): Matrix4f { c00 = value; return this }
	fun c01() = c01
	fun c01(value: Float): Matrix4f { c01 = value; return this }
	fun c02() = c02
	fun c02(value: Float): Matrix4f { c02 = value; return this }
	fun c03() = c03
	fun c03(value: Float): Matrix4f { c03 = value; return this }
	fun c10() = c10
	fun c10(value: Float): Matrix4f { c10 = value; return this }
	fun c11() = c11
	fun c11(value: Float): Matrix4f { c11 = value; return this }
	fun c12() = c12
	fun c12(value: Float): Matrix4f { c12 = value; return this }
	fun c13() = c13
	fun c13(value: Float): Matrix4f { c13 = value; return this }
	fun c20() = c20
	fun c20(value: Float): Matrix4f { c20 = value; return this }
	fun c21() = c21
	fun c21(value: Float): Matrix4f { c21 = value; return this }
	fun c22() = c22
	fun c22(value: Float): Matrix4f { c22 = value; return this }
	fun c23() = c23
	fun c23(value: Float): Matrix4f { c23 = value; return this }
	fun c30() = c30
	fun c30(value: Float): Matrix4f { c30 = value; return this }
	fun c31() = c31
	fun c31(value: Float): Matrix4f { c31 = value; return this }
	fun c32() = c32
	fun c32(value: Float): Matrix4f { c32 = value; return this }
	fun c33() = c33
	fun c33(value: Float): Matrix4f { c33 = value; return this }

	var r00: Float get() = c00; set(value) { c00 = value } // row 0 column 0
	var r01: Float get() = c10; set(value) { c10 = value } // row 1 column 0
	var r02: Float get() = c20; set(value) { c20 = value } // row 2 column 0
	var r03: Float get() = c30; set(value) { c30 = value } // row 3 column 0
	var r10: Float get() = c01; set(value) { c01 = value } // row 0 column 1
	var r11: Float get() = c11; set(value) { c11 = value } // row 1 column 1
	var r12: Float get() = c21; set(value) { c21 = value } // row 2 column 1
	var r13: Float get() = c31; set(value) { c31 = value } // row 3 column 1
	var r20: Float get() = c02; set(value) { c02 = value } // row 0 column 2
	var r21: Float get() = c12; set(value) { c12 = value } // row 1 column 2
	var r22: Float get() = c22; set(value) { c22 = value } // row 2 column 2
	var r23: Float get() = c32; set(value) { c32 = value } // row 3 column 2
	var r30: Float get() = c03; set(value) { c03 = value } // row 0 column 3
	var r31: Float get() = c13; set(value) { c13 = value } // row 1 column 3
	var r32: Float get() = c23; set(value) { c23 = value } // row 2 column 3
	var r33: Float get() = c33; set(value) { c33 = value } // row 3 column 3

	fun r00() = r00
	fun r00(value: Float): Matrix4f { r00 = value; return this }
	fun r01() = r01
	fun r01(value: Float): Matrix4f { r01 = value; return this }
	fun r02() = r02
	fun r02(value: Float): Matrix4f { r02 = value; return this }
	fun r03() = r03
	fun r03(value: Float): Matrix4f { r03 = value; return this }
	fun r10() = r10
	fun r10(value: Float): Matrix4f { r10 = value; return this }
	fun r11() = r11
	fun r11(value: Float): Matrix4f { r11 = value; return this }
	fun r12() = r12
	fun r12(value: Float): Matrix4f { r12 = value; return this }
	fun r13() = r13
	fun r13(value: Float): Matrix4f { r13 = value; return this }
	fun r20() = r20
	fun r20(value: Float): Matrix4f { r20 = value; return this }
	fun r21() = r21
	fun r21(value: Float): Matrix4f { r21 = value; return this }
	fun r22() = r22
	fun r22(value: Float): Matrix4f { r22 = value; return this }
	fun r23() = r23
	fun r23(value: Float): Matrix4f { r23 = value; return this }
	fun r30() = r30
	fun r30(value: Float): Matrix4f { r30 = value; return this }
	fun r31() = r31
	fun r31(value: Float): Matrix4f { r31 = value; return this }
	fun r32() = r32
	fun r32(value: Float): Matrix4f { r32 = value; return this }
	fun r33() = r33
	fun r33(value: Float): Matrix4f { r33 = value; return this }

	final override val rows: Int
		get() = 4
	final override val columns: Int
		get() = 4

	final override val transposed get() =
		columnMajor(
			c00 = r00,
			c01 = r01,
			c02 = r02,
			c03 = r03,
			c10 = r10,
			c11 = r11,
			c12 = r12,
			c13 = r13,
			c20 = r20,
			c21 = r21,
			c22 = r22,
			c23 = r23,
			c30 = r30,
			c31 = r31,
			c32 = r32,
			c33 = r33,
		)

	final override fun get(column: Int, row: Int): Float {
		return when (column or (row shl 2)) {
			C00 -> c00
			C01 -> c01
			C02 -> c02
			C03 -> c03
			C10 -> c10
			C11 -> c11
			C12 -> c12
			C13 -> c13
			C20 -> c20
			C21 -> c21
			C22 -> c22
			C23 -> c23
			C30 -> c30
			C31 -> c31
			C32 -> c32
			C33 -> c33
			else -> throw IndexOutOfBoundsException("Column $column; Row $row; while size of this matrix are 4x4")
		}
	}

	override fun set(column: Int, row: Int, value: Float) {
		when (column or (row shl 2)) {
			C00 -> c00 = value
			C01 -> c01 = value
			C02 -> c02 = value
			C03 -> c03 = value
			C10 -> c10 = value
			C11 -> c11 = value
			C12 -> c12 = value
			C13 -> c13 = value
			C20 -> c20 = value
			C21 -> c21 = value
			C22 -> c22 = value
			C23 -> c23 = value
			C30 -> c30 = value
			C31 -> c31 = value
			C32 -> c32 = value
			C33 -> c33 = value
			else -> throw IndexOutOfBoundsException("Column $column; Row $row; while size of this matrix are 4x4")
		}
	}

	fun mul(other: Matrix4f): Matrix4f {
		val c00 = mulMatrixComponents(this, other, 0, 0)
		val c01 = mulMatrixComponents(this, other, 0, 1)
		val c02 = mulMatrixComponents(this, other, 0, 2)
		val c03 = mulMatrixComponents(this, other, 0, 3)
		val c10 = mulMatrixComponents(this, other, 1, 0)
		val c11 = mulMatrixComponents(this, other, 1, 1)
		val c12 = mulMatrixComponents(this, other, 1, 2)
		val c13 = mulMatrixComponents(this, other, 1, 3)
		val c20 = mulMatrixComponents(this, other, 2, 0)
		val c21 = mulMatrixComponents(this, other, 2, 1)
		val c22 = mulMatrixComponents(this, other, 2, 2)
		val c23 = mulMatrixComponents(this, other, 2, 3)
		val c30 = mulMatrixComponents(this, other, 3, 0)
		val c31 = mulMatrixComponents(this, other, 3, 1)
		val c32 = mulMatrixComponents(this, other, 3, 2)
		val c33 = mulMatrixComponents(this, other, 3, 3)
		this.c00 = c00
		this.c01 = c01
		this.c02 = c02
		this.c03 = c03
		this.c10 = c10
		this.c11 = c11
		this.c12 = c12
		this.c13 = c13
		this.c20 = c20
		this.c21 = c21
		this.c22 = c22
		this.c23 = c23
		this.c30 = c30
		this.c31 = c31
		this.c32 = c32
		this.c33 = c33
		return this
	}

	fun mulIntoOther(other: Matrix4f): Matrix4f {
		val c00 = mulMatrixComponents(this, other, 0, 0)
		val c01 = mulMatrixComponents(this, other, 0, 1)
		val c02 = mulMatrixComponents(this, other, 0, 2)
		val c03 = mulMatrixComponents(this, other, 0, 3)
		val c10 = mulMatrixComponents(this, other, 1, 0)
		val c11 = mulMatrixComponents(this, other, 1, 1)
		val c12 = mulMatrixComponents(this, other, 1, 2)
		val c13 = mulMatrixComponents(this, other, 1, 3)
		val c20 = mulMatrixComponents(this, other, 2, 0)
		val c21 = mulMatrixComponents(this, other, 2, 1)
		val c22 = mulMatrixComponents(this, other, 2, 2)
		val c23 = mulMatrixComponents(this, other, 2, 3)
		val c30 = mulMatrixComponents(this, other, 3, 0)
		val c31 = mulMatrixComponents(this, other, 3, 1)
		val c32 = mulMatrixComponents(this, other, 3, 2)
		val c33 = mulMatrixComponents(this, other, 3, 3)
		other.c00 = c00
		other.c01 = c01
		other.c02 = c02
		other.c03 = c03
		other.c10 = c10
		other.c11 = c11
		other.c12 = c12
		other.c13 = c13
		other.c20 = c20
		other.c21 = c21
		other.c22 = c22
		other.c23 = c23
		other.c30 = c30
		other.c31 = c31
		other.c32 = c32
		other.c33 = c33
		return other
	}

	/**
	 * this * other, writes result into this
	 */
	fun mul(
		c00: Float = 1f,
		c01: Float = 0f,
		c02: Float = 0f,
		c03: Float = 0f,
		c10: Float = 0f,
		c11: Float = 1f,
		c12: Float = 0f,
		c13: Float = 0f,
		c20: Float = 0f,
		c21: Float = 0f,
		c22: Float = 1f,
		c23: Float = 0f,
		c30: Float = 0f,
		c31: Float = 0f,
		c32: Float = 0f,
		c33: Float = 1f,
	): Matrix4f {
		val fc00 = this.c00 * c00 + this.c10 * c01 + this.c20 * c02 + this.c30 * c03
		val fc01 = this.c01 * c00 + this.c11 * c01 + this.c21 * c02 + this.c31 * c03
		val fc02 = this.c02 * c00 + this.c12 * c01 + this.c22 * c02 + this.c32 * c03
		val fc03 = this.c03 * c00 + this.c13 * c01 + this.c23 * c02 + this.c33 * c03
		val fc10 = this.c00 * c10 + this.c10 * c11 + this.c20 * c12 + this.c30 * c13
		val fc11 = this.c01 * c10 + this.c11 * c11 + this.c21 * c12 + this.c31 * c13
		val fc12 = this.c02 * c10 + this.c12 * c11 + this.c22 * c12 + this.c32 * c13
		val fc13 = this.c03 * c10 + this.c13 * c11 + this.c23 * c12 + this.c33 * c13
		val fc20 = this.c00 * c20 + this.c10 * c21 + this.c20 * c22 + this.c30 * c23
		val fc21 = this.c01 * c20 + this.c11 * c21 + this.c21 * c22 + this.c31 * c23
		val fc22 = this.c02 * c20 + this.c12 * c21 + this.c22 * c22 + this.c32 * c23
		val fc23 = this.c03 * c20 + this.c13 * c21 + this.c23 * c22 + this.c33 * c23
		val fc30 = this.c00 * c30 + this.c10 * c31 + this.c20 * c32 + this.c30 * c33
		val fc31 = this.c01 * c30 + this.c11 * c31 + this.c21 * c32 + this.c31 * c33
		val fc32 = this.c02 * c30 + this.c12 * c31 + this.c22 * c32 + this.c32 * c33
		val fc33 = this.c03 * c30 + this.c13 * c31 + this.c23 * c32 + this.c33 * c33
		this.c00 = fc00
		this.c01 = fc01
		this.c02 = fc02
		this.c03 = fc03
		this.c10 = fc10
		this.c11 = fc11
		this.c12 = fc12
		this.c13 = fc13
		this.c20 = fc20
		this.c21 = fc21
		this.c22 = fc22
		this.c23 = fc23
		this.c30 = fc30
		this.c31 = fc31
		this.c32 = fc32
		this.c33 = fc33
		return this
	}

	/**
	 * other * this, writes result into this
	 */
	fun mulIntoThis(
		c00: Float = 1f,
		c01: Float = 0f,
		c02: Float = 0f,
		c03: Float = 0f,
		c10: Float = 0f,
		c11: Float = 1f,
		c12: Float = 0f,
		c13: Float = 0f,
		c20: Float = 0f,
		c21: Float = 0f,
		c22: Float = 1f,
		c23: Float = 0f,
		c30: Float = 0f,
		c31: Float = 0f,
		c32: Float = 0f,
		c33: Float = 1f,
	): Matrix4f {
		val fc00 = c00 * this.c00 + c10 * this.c01 + c20 * this.c02 + c30 * this.c03
		val fc01 = c01 * this.c00 + c11 * this.c01 + c21 * this.c02 + c31 * this.c03
		val fc02 = c02 * this.c00 + c12 * this.c01 + c22 * this.c02 + c32 * this.c03
		val fc03 = c03 * this.c00 + c13 * this.c01 + c23 * this.c02 + c33 * this.c03
		val fc10 = c00 * this.c10 + c10 * this.c11 + c20 * this.c12 + c30 * this.c13
		val fc11 = c01 * this.c10 + c11 * this.c11 + c21 * this.c12 + c31 * this.c13
		val fc12 = c02 * this.c10 + c12 * this.c11 + c22 * this.c12 + c32 * this.c13
		val fc13 = c03 * this.c10 + c13 * this.c11 + c23 * this.c12 + c33 * this.c13
		val fc20 = c00 * this.c20 + c10 * this.c21 + c20 * this.c22 + c30 * this.c23
		val fc21 = c01 * this.c20 + c11 * this.c21 + c21 * this.c22 + c31 * this.c23
		val fc22 = c02 * this.c20 + c12 * this.c21 + c22 * this.c22 + c32 * this.c23
		val fc23 = c03 * this.c20 + c13 * this.c21 + c23 * this.c22 + c33 * this.c23
		val fc30 = c00 * this.c30 + c10 * this.c31 + c20 * this.c32 + c30 * this.c33
		val fc31 = c01 * this.c30 + c11 * this.c31 + c21 * this.c32 + c31 * this.c33
		val fc32 = c02 * this.c30 + c12 * this.c31 + c22 * this.c32 + c32 * this.c33
		val fc33 = c03 * this.c30 + c13 * this.c31 + c23 * this.c32 + c33 * this.c33
		this.c00 = fc00
		this.c01 = fc01
		this.c02 = fc02
		this.c03 = fc03
		this.c10 = fc10
		this.c11 = fc11
		this.c12 = fc12
		this.c13 = fc13
		this.c20 = fc20
		this.c21 = fc21
		this.c22 = fc22
		this.c23 = fc23
		this.c30 = fc30
		this.c31 = fc31
		this.c32 = fc32
		this.c33 = fc33
		return this
	}

	final override fun add(other: Float): Matrix4f {
		return super.add(other) as Matrix4f
	}

	final override fun sub(other: Float): Matrix4f {
		return super.sub(other) as Matrix4f
	}

	final override fun mul(other: Float): Matrix4f {
		return super.mul(other) as Matrix4f
	}

	final override fun div(other: Float): Matrix4f {
		return super.div(other) as Matrix4f
	}

	final override fun add(other: Float2DArray): Matrix4f {
		return super.add(other) as Matrix4f
	}

	final override fun sub(other: Float2DArray): Matrix4f {
		return super.sub(other) as Matrix4f
	}

	fun to2f() = Matrix2f.from(this)
	fun to3f() = Matrix3f.from(this)

	fun copy(): Matrix4f {
		return Impl(c00, c01, c02, c03, c10, c11, c12, c13, c20, c21, c22, c23, c30, c31, c32, c33)
	}

	@JvmOverloads
	fun translate(x: Float = 0f, y: Float = 0f, z: Float = 0f): Matrix4f {
		return mul(c30 = x, c31 = y, c32 = z)
	}

	fun translate(value: IStruct2f) = translate(value.component1(), value.component2())
	fun translate(value: IStruct3f) = translate(value.component1(), value.component2(), value.component3())

	fun translate(value: Vector2f) = translate(value.component1(), value.component2())
	fun translate(value: Vector3f) = translate(value.component1(), value.component2(), value.component3())

	@JvmOverloads
	fun preTranslate(x: Float = 0f, y: Float = 0f, z: Float = 0f): Matrix4f {
		return mulIntoThis(c30 = x, c31 = y, c32 = z)
	}

	fun preTranslate(value: IStruct2f) = preTranslate(value.component1(), value.component2())
	fun preTranslate(value: IStruct3f) = preTranslate(value.component1(), value.component2(), value.component3())

	fun preTranslate(value: Vector2f) = preTranslate(value.component1(), value.component2())
	fun preTranslate(value: Vector3f) = preTranslate(value.component1(), value.component2(), value.component3())

	@JvmOverloads
	fun scale(x: Float = 1f, y: Float = 1f, z: Float = 1f, w: Float = 1f): Matrix4f {
		return mul(c00 = x, c11 = y, c22 = z, c33 = w)
	}

	fun scale(value: IStruct2f) = scale(value.component1(), value.component2())
	fun scale(value: IStruct3f) = scale(value.component1(), value.component2(), value.component3())
	fun scale(value: IStruct4f) = scale(value.component1(), value.component2(), value.component3(), value.component3())

	fun scale(value: Vector2f) = scale(value.component1(), value.component2())
	fun scale(value: Vector3f) = scale(value.component1(), value.component2(), value.component3())
	fun scale(value: Vector4f) = scale(value.component1(), value.component2(), value.component3(), value.component3())

	@JvmOverloads
	fun preScale(x: Float = 1f, y: Float = 1f, z: Float = 1f, w: Float = 1f): Matrix4f {
		return mulIntoThis(c00 = x, c11 = y, c22 = z, c33 = w)
	}

	fun preScale(value: IStruct2f) = preScale(value.component1(), value.component2())
	fun preScale(value: IStruct3f) = preScale(value.component1(), value.component2(), value.component3())
	fun preScale(value: IStruct4f) = preScale(value.component1(), value.component2(), value.component3(), value.component3())

	fun preScale(value: Vector2f) = preScale(value.component1(), value.component2())
	fun preScale(value: Vector3f) = preScale(value.component1(), value.component2(), value.component3())
	fun preScale(value: Vector4f) = preScale(value.component1(), value.component2(), value.component3(), value.component3())

	fun rotateAroundX(angle: Float): Matrix4f {
		val cos = cos(angle)
		val sin = sin(angle)

		return mul(
			c11 = cos,
			c21 = -sin,
			c12 = sin,
			c22 = cos,
		)
	}

	fun rotateAroundY(angle: Float): Matrix4f {
		val cos = cos(angle)
		val sin = sin(angle)

		return mul(
			c00 = cos,
			c20 = sin,
			c02 = -sin,
			c22 = cos,
		)
	}

	fun rotateAroundZ(angle: Float): Matrix4f {
		val cos = cos(angle)
		val sin = sin(angle)

		return mul(
			c00 = cos,
			c10 = -sin,
			c01 = sin,
			c11 = cos,
		)
	}

	fun preRotateAroundX(angle: Float): Matrix4f {
		val cos = cos(angle)
		val sin = sin(angle)

		return mulIntoThis(
			c11 = cos,
			c21 = -sin,
			c12 = sin,
			c22 = cos,
		)
	}

	fun preRotateAroundY(angle: Float): Matrix4f {
		val cos = cos(angle)
		val sin = sin(angle)

		return mulIntoThis(
			c00 = cos,
			c20 = sin,
			c02 = -sin,
			c22 = cos,
		)
	}

	fun preRotateAroundZ(angle: Float): Matrix4f {
		val cos = cos(angle)
		val sin = sin(angle)

		return mulIntoThis(
			c00 = cos,
			c10 = -sin,
			c01 = sin,
			c11 = cos,
		)
	}

	private data class Impl(
		override var c00: Float,
		override var c01: Float,
		override var c02: Float,
		override var c03: Float,
		override var c10: Float,
		override var c11: Float,
		override var c12: Float,
		override var c13: Float,
		override var c20: Float,
		override var c21: Float,
		override var c22: Float,
		override var c23: Float,
		override var c30: Float,
		override var c31: Float,
		override var c32: Float,
		override var c33: Float,
	) : Matrix4f() {
		override fun toString(): String {
			return "Matrix4f" + super.toString()
		}

		override fun equals(other: Any?): Boolean {
			if (other !is Matrix4f) return false

			return this === other || c00 == other.c00 &&
				c01 == other.c01 &&
				c02 == other.c02 &&
				c03 == other.c03 &&
				c10 == other.c10 &&
				c11 == other.c11 &&
				c12 == other.c12 &&
				c13 == other.c13 &&
				c20 == other.c20 &&
				c21 == other.c21 &&
				c22 == other.c22 &&
				c23 == other.c23 &&
				c30 == other.c30 &&
				c31 == other.c31 &&
				c32 == other.c32 &&
				c33 == other.c33
		}
	}

	private class View(private val parent: Matrix4f) : Matrix4f() {
		override var c00: Float get() = parent.c00; set(_) { throw UnsupportedOperationException("Read-Only view") }
		override var c01: Float get() = parent.c01; set(_) { throw UnsupportedOperationException("Read-Only view") }
		override var c02: Float get() = parent.c02; set(_) { throw UnsupportedOperationException("Read-Only view") }
		override var c03: Float get() = parent.c03; set(_) { throw UnsupportedOperationException("Read-Only view") }
		override var c10: Float get() = parent.c10; set(_) { throw UnsupportedOperationException("Read-Only view") }
		override var c11: Float get() = parent.c11; set(_) { throw UnsupportedOperationException("Read-Only view") }
		override var c12: Float get() = parent.c12; set(_) { throw UnsupportedOperationException("Read-Only view") }
		override var c13: Float get() = parent.c13; set(_) { throw UnsupportedOperationException("Read-Only view") }
		override var c20: Float get() = parent.c20; set(_) { throw UnsupportedOperationException("Read-Only view") }
		override var c21: Float get() = parent.c21; set(_) { throw UnsupportedOperationException("Read-Only view") }
		override var c22: Float get() = parent.c22; set(_) { throw UnsupportedOperationException("Read-Only view") }
		override var c23: Float get() = parent.c23; set(_) { throw UnsupportedOperationException("Read-Only view") }
		override var c30: Float get() = parent.c30; set(_) { throw UnsupportedOperationException("Read-Only view") }
		override var c31: Float get() = parent.c31; set(_) { throw UnsupportedOperationException("Read-Only view") }
		override var c32: Float get() = parent.c32; set(_) { throw UnsupportedOperationException("Read-Only view") }
		override var c33: Float get() = parent.c33; set(_) { throw UnsupportedOperationException("Read-Only view") }

		override fun set(column: Int, row: Int, value: Float) {
			throw UnsupportedOperationException("Read-Only view")
		}

		override fun equals(other: Any?): Boolean {
			return other === this || parent == other
		}

		override fun hashCode(): Int {
			return parent.hashCode()
		}

		override fun toString(): String {
			return "View = $parent"
		}
	}

	companion object {
		@JvmStatic
		fun all(value: Float) = construct4(value, Companion::columnMajor)

		@JvmStatic
		fun zero() = all(0f)

		@JvmStatic
		fun identity() = columnMajor()

		@JvmStatic
		fun unmodifiable(value: Matrix4f): Matrix4f {
			if (value is View)
				return value
			else
				return View(value)
		}

		@JvmStatic
		fun columnMajor(
			c00: Float = 1f, c01: Float = 0f, c02: Float = 0f, c03: Float = 0f,
			c10: Float = 0f, c11: Float = 1f, c12: Float = 0f, c13: Float = 0f,
			c20: Float = 0f, c21: Float = 0f, c22: Float = 1f, c23: Float = 0f,
			c30: Float = 0f, c31: Float = 0f, c32: Float = 0f, c33: Float = 1f,
		): Matrix4f {
			return Impl(c00, c01, c02, c03, c10, c11, c12, c13, c20, c21, c22, c23, c30, c31, c32, c33)
		}

		@JvmStatic
		fun rowMajor(
			r00: Float = 1f, r01: Float = 0f, r02: Float = 0f, r03: Float = 0f,
			r10: Float = 0f, r11: Float = 1f, r12: Float = 0f, r13: Float = 0f,
			r20: Float = 0f, r21: Float = 0f, r22: Float = 1f, r23: Float = 0f,
			r30: Float = 0f, r31: Float = 0f, r32: Float = 0f, r33: Float = 1f
		): Matrix4f {
			return Impl(
				r00, r10, r20, r30,
				r01, r11, r21, r31,
				r02, r12, r22, r32,
				r03, r13, r23, r33
			)
		}

		@JvmStatic
		fun from(matrix: Float2DArray, filler: Float) = construct4(matrix, Float2DArray::get, Companion::columnMajor, filler)
		@JvmStatic
		fun from(matrix: Float2DArray) = construct4(matrix, Float2DArray::get, Companion::columnMajor, 0f)
		@JvmStatic
		fun from(matrix: Matrix4f) = construct4(matrix, Float2DArray::get, Companion::columnMajor)

		@JvmStatic
		fun fromTransposed(matrix: Float2DArray) = construct4(matrix, Float2DArray::get, Companion::rowMajor, 0f)
		@JvmStatic
		fun fromTransposed(matrix: Float2DArray, filler: Float) = construct4(matrix, Float2DArray::get, Companion::rowMajor, filler)
		@JvmStatic
		fun fromTransposed(matrix: Matrix4f) = construct4(matrix, Float2DArray::get, Companion::rowMajor)

		@JvmStatic
		fun fromColumnMajor(buffer: FloatBuffer) = construct4(buffer, FloatBuffer::get, Companion::columnMajor)
		@JvmStatic
		fun fromColumnMajor(buffer: ByteBuffer) = construct4(buffer, ByteBuffer::getFloat, Companion::columnMajor)
		@JvmStatic
		fun fromRowMajor(buffer: FloatBuffer) = construct4(buffer, FloatBuffer::get, Companion::rowMajor)
		@JvmStatic
		fun fromRowMajor(buffer: ByteBuffer) = construct4(buffer, ByteBuffer::getFloat, Companion::rowMajor)

		/**
		 * Constructs new ortho projection matrix, with Y coordinate flipped (useful for OpenGL GUI drawing).
		 *
		 * Inversion of Y means that X 0 Y 0 will be top left position on screen (in OpenGL), not bottom left.
		 */
		@JvmStatic
		fun ortho(left: Float, right: Float, bottom: Float, top: Float, zNear: Float, zFar: Float): Matrix4f {
			return rowMajor(
				r00 = 2f / (right - left),
				r11 = -2f / (top - bottom),
				r22 = 2f / (zFar - zNear),
				r03 = -(right + left) / (right - left),
				r13 = -(top + bottom) / (top - bottom) + 2f,
				r23 = -(zFar + zNear) / (zFar - zNear)
			)
		}

		/**
		 * Constructs new ortho projection matrix
		 */
		@JvmStatic
		fun orthoDirect(left: Float, right: Float, bottom: Float, top: Float, zNear: Float, zFar: Float): Matrix4f {
			return rowMajor(
				r00 = 2f / (right - left),
				r11 = 2f / (top - bottom),
				r22 = 2f / (zFar - zNear),
				r03 = -(right + left) / (right - left),
				r13 = -(top + bottom) / (top - bottom),
				r23 = -(zFar + zNear) / (zFar - zNear)
			)
		}

		/**
		 * Constructs new perspective matrix
		 */
		@JvmStatic
		fun perspective(fov: Float, zFar: Float, zNear: Float): Matrix4f {
			val scale = (1.0 / (tan(Math.toRadians(fov.toDouble()) / 2.0))).toFloat()
			val r = zFar - zNear

			return rowMajor(
				r00 = scale,
				r11 = scale,
				r22 = -zFar / r,
				r23 = -1f,
				r32 = -zFar * zNear / r,
			)
		}

		// kotlin compiler bug? it refuses to use TABLESWITCH if these are inlined
		const val C00 = (0 or (0 shl 2))
		const val C01 = (0 or (1 shl 2))
		const val C02 = (0 or (2 shl 2))
		const val C03 = (0 or (3 shl 2))
		const val C10 = (1 or (0 shl 2))
		const val C11 = (1 or (1 shl 2))
		const val C12 = (1 or (2 shl 2))
		const val C13 = (1 or (3 shl 2))
		const val C20 = (2 or (0 shl 2))
		const val C21 = (2 or (1 shl 2))
		const val C22 = (2 or (2 shl 2))
		const val C23 = (2 or (3 shl 2))
		const val C30 = (3 or (0 shl 2))
		const val C31 = (3 or (1 shl 2))
		const val C32 = (3 or (2 shl 2))
		const val C33 = (3 or (3 shl 2))
	}
}
