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.kstarbound.math.vector.Vector2f
import ru.dbotthepony.kstarbound.math.vector.Vector3f
import java.nio.ByteBuffer
import java.nio.FloatBuffer
import kotlin.math.cos
import kotlin.math.sin

sealed class Matrix3f : Float2DArray() {
	abstract var c00: Float
	abstract var c01: Float
	abstract var c02: Float
	abstract var c10: Float
	abstract var c11: Float
	abstract var c12: Float
	abstract var c20: Float
	abstract var c21: Float
	abstract var c22: Float

	fun c00() = c00
	fun c00(value: Float): Matrix3f { c00 = value; return this }
	fun c01() = c01
	fun c01(value: Float): Matrix3f { c01 = value; return this }
	fun c02() = c02
	fun c02(value: Float): Matrix3f { c02 = value; return this }
	fun c10() = c10
	fun c10(value: Float): Matrix3f { c10 = value; return this }
	fun c11() = c11
	fun c11(value: Float): Matrix3f { c11 = value; return this }
	fun c12() = c12
	fun c12(value: Float): Matrix3f { c12 = value; return this }
	fun c20() = c20
	fun c20(value: Float): Matrix3f { c20 = value; return this }
	fun c21() = c21
	fun c21(value: Float): Matrix3f { c21 = value; return this }
	fun c22() = c22
	fun c22(value: Float): Matrix3f { c22 = 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 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 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

	fun r00() = r00
	fun r00(value: Float): Matrix3f { r00 = value; return this }
	fun r01() = r01
	fun r01(value: Float): Matrix3f { r01 = value; return this }
	fun r02() = r02
	fun r02(value: Float): Matrix3f { r02 = value; return this }
	fun r10() = r10
	fun r10(value: Float): Matrix3f { r10 = value; return this }
	fun r11() = r11
	fun r11(value: Float): Matrix3f { r11 = value; return this }
	fun r12() = r12
	fun r12(value: Float): Matrix3f { r12 = value; return this }
	fun r20() = r20
	fun r20(value: Float): Matrix3f { r20 = value; return this }
	fun r21() = r21
	fun r21(value: Float): Matrix3f { r21 = value; return this }
	fun r22() = r22
	fun r22(value: Float): Matrix3f { r22 = value; return this }

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

	final override val determinant: Float
		get() = super.determinant!!
	final override val cofactorMatrix
		get() = from(super.cofactorMatrix!!)
	final override val adjugateMatrix
		get() = from(super.adjugateMatrix!!)
	final override val inversed
		get() = super.inversed?.let { from(it) }

	final override val transposed get() =
		columnMajor(
			c00 = r00,
			c01 = r01,
			c02 = r02,
			c10 = r10,
			c11 = r11,
			c12 = r12,
			c20 = r20,
			c21 = r21,
			c22 = r22,
		)

	final override fun get(column: Int, row: Int): Float {
		return when (column or (row shl 2)) {
			C00 -> c00
			C01 -> c01
			C02 -> c02
			C10 -> c10
			C11 -> c11
			C12 -> c12
			C20 -> c20
			C21 -> c21
			C22 -> c22
			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
			C10 -> c10 = value
			C11 -> c11 = value
			C12 -> c12 = value
			C20 -> c20 = value
			C21 -> c21 = value
			C22 -> c22 = value
			else -> throw IndexOutOfBoundsException("Column $column; Row $row; while size of this matrix are 4x4")
		}
	}

	fun mul(other: Matrix3f): Matrix3f {
		val c00 = mulMatrixComponents(this, other, 0, 0)
		val c01 = mulMatrixComponents(this, other, 0, 1)
		val c02 = mulMatrixComponents(this, other, 0, 2)
		val c10 = mulMatrixComponents(this, other, 1, 0)
		val c11 = mulMatrixComponents(this, other, 1, 1)
		val c12 = mulMatrixComponents(this, other, 1, 2)
		val c20 = mulMatrixComponents(this, other, 2, 0)
		val c21 = mulMatrixComponents(this, other, 2, 1)
		val c22 = mulMatrixComponents(this, other, 2, 2)
		this.c00 = c00
		this.c01 = c01
		this.c02 = c02
		this.c10 = c10
		this.c11 = c11
		this.c12 = c12
		this.c20 = c20
		this.c21 = c21
		this.c22 = c22
		return this
	}

	fun mulIntoOther(other: Matrix3f): Matrix3f {
		val c00 = mulMatrixComponents(this, other, 0, 0)
		val c01 = mulMatrixComponents(this, other, 0, 1)
		val c02 = mulMatrixComponents(this, other, 0, 2)
		val c10 = mulMatrixComponents(this, other, 1, 0)
		val c11 = mulMatrixComponents(this, other, 1, 1)
		val c12 = mulMatrixComponents(this, other, 1, 2)
		val c20 = mulMatrixComponents(this, other, 2, 0)
		val c21 = mulMatrixComponents(this, other, 2, 1)
		val c22 = mulMatrixComponents(this, other, 2, 2)
		other.c00 = c00
		other.c01 = c01
		other.c02 = c02
		other.c10 = c10
		other.c11 = c11
		other.c12 = c12
		other.c20 = c20
		other.c21 = c21
		other.c22 = c22
		return other
	}

	fun mul(
		c00: Float = 1f,
		c01: Float = 0f,
		c02: Float = 0f,
		c10: Float = 0f,
		c11: Float = 1f,
		c12: Float = 0f,
		c20: Float = 0f,
		c21: Float = 0f,
		c22: Float = 1f,
	): Matrix3f {
		val fc00 = this.c00 * c00 + this.c10 * c01 + this.c20 * c02
		val fc01 = this.c01 * c00 + this.c11 * c01 + this.c21 * c02
		val fc02 = this.c02 * c00 + this.c12 * c01 + this.c22 * c02
		val fc10 = this.c00 * c10 + this.c10 * c11 + this.c20 * c12
		val fc11 = this.c01 * c10 + this.c11 * c11 + this.c21 * c12
		val fc12 = this.c02 * c10 + this.c12 * c11 + this.c22 * c12
		val fc20 = this.c00 * c20 + this.c10 * c21 + this.c20 * c22
		val fc21 = this.c01 * c20 + this.c11 * c21 + this.c21 * c22
		val fc22 = this.c02 * c20 + this.c12 * c21 + this.c22 * c22
		this.c00 = fc00
		this.c01 = fc01
		this.c02 = fc02
		this.c10 = fc10
		this.c11 = fc11
		this.c12 = fc12
		this.c20 = fc20
		this.c21 = fc21
		this.c22 = fc22
		return this
	}

	fun mulIntoThis(
		c00: Float = 1f,
		c01: Float = 0f,
		c02: Float = 0f,
		c10: Float = 0f,
		c11: Float = 1f,
		c12: Float = 0f,
		c20: Float = 0f,
		c21: Float = 0f,
		c22: Float = 1f,
	): Matrix3f {
		val fc00 = c00 * this.c00 + c10 * this.c01 + c20 * this.c02
		val fc01 = c01 * this.c00 + c11 * this.c01 + c21 * this.c02
		val fc02 = c02 * this.c00 + c12 * this.c01 + c22 * this.c02
		val fc10 = c00 * this.c10 + c10 * this.c11 + c20 * this.c12
		val fc11 = c01 * this.c10 + c11 * this.c11 + c21 * this.c12
		val fc12 = c02 * this.c10 + c12 * this.c11 + c22 * this.c12
		val fc20 = c00 * this.c20 + c10 * this.c21 + c20 * this.c22
		val fc21 = c01 * this.c20 + c11 * this.c21 + c21 * this.c22
		val fc22 = c02 * this.c20 + c12 * this.c21 + c22 * this.c22
		this.c00 = fc00
		this.c01 = fc01
		this.c02 = fc02
		this.c10 = fc10
		this.c11 = fc11
		this.c12 = fc12
		this.c20 = fc20
		this.c21 = fc21
		this.c22 = fc22
		return this
	}

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

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

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

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

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

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

	fun to2f() = Matrix2f.from(this)
	fun to4f(filler: Float) = Matrix4f.from(this, filler)
	fun to4f() = Matrix4f.from(this)

	fun copy(): Matrix3f {
		return Impl(c00, c01, c02, c10, c11, c12, c20, c21, c22)
	}

	@JvmOverloads
	fun translate(x: Float = 0f, y: Float = 0f): Matrix3f {
		return mul(c20 = x, c21 = y)
	}

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

	@JvmOverloads
	fun preTranslate(x: Float = 0f, y: Float = 0f): Matrix3f {
		return mulIntoThis(c20 = x, c21 = y)
	}

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

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

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

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

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

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

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

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

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

	fun preRotateAroundZ(angle: Float): Matrix3f {
		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 c10: Float,
		override var c11: Float,
		override var c12: Float,
		override var c20: Float,
		override var c21: Float,
		override var c22: Float,
	) : Matrix3f() {
		override fun toString(): String {
			return "Matrix3f" + super.toString()
		}

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

			return  c00 == other.c00 &&
				c01 == other.c01 &&
				c02 == other.c02 &&
				c10 == other.c10 &&
				c11 == other.c11 &&
				c12 == other.c12 &&
				c20 == other.c20 &&
				c21 == other.c21 &&
				c22 == other.c22
		}
	}

	private class View(private val parent: Matrix3f) : Matrix3f() {
		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 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 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 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) = construct3(value, Companion::columnMajor)

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

		@JvmStatic
		fun identity() = columnMajor()

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

		@JvmStatic
		fun columnMajor(
			c00: Float = 1f, c01: Float = 0f, c02: Float = 0f,
			c10: Float = 0f, c11: Float = 1f, c12: Float = 0f,
			c20: Float = 0f, c21: Float = 0f, c22: Float = 1f,
		): Matrix3f {
			return Impl(c00, c01, c02, c10, c11, c12, c20, c21, c22)
		}

		@JvmStatic
		fun rowMajor(
			r00: Float = 1f, r01: Float = 0f, r02: Float = 0f,
			r10: Float = 0f, r11: Float = 1f, r12: Float = 0f,
			r20: Float = 0f, r21: Float = 0f, r22: Float = 1f,
		): Matrix3f {
			return Impl(
				r00, r10, r20,
				r01, r11, r21,
				r02, r12, r22,
			)
		}

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

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

		@JvmStatic
		fun fromColumnMajor(buffer: FloatBuffer) = construct3(buffer, FloatBuffer::get, Companion::columnMajor)
		@JvmStatic
		fun fromColumnMajor(buffer: ByteBuffer) = construct3(buffer, ByteBuffer::getFloat, Companion::columnMajor)
		@JvmStatic
		fun fromRowMajor(buffer: FloatBuffer) = construct3(buffer, FloatBuffer::get, Companion::rowMajor)
		@JvmStatic
		fun fromRowMajor(buffer: ByteBuffer) = construct3(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): Matrix3f {
			return rowMajor(
				r00 = 2f / (right - left),
				r11 = -2f / (top - bottom),
				r02 = -(right + left) / (right - left),
				r12 = -(top + bottom) / (top - bottom) + 2f,
			)
		}

		/**
		 * Constructs new ortho projection matrix
		 */
		@JvmStatic
		fun orthoDirect(left: Float, right: Float, bottom: Float, top: Float): Matrix3f {
			return rowMajor(
				r00 = 2f / (right - left),
				r11 = 2f / (top - bottom),
				r02 = -(right + left) / (right - left),
				r12 = -(top + bottom) / (top - bottom),
			)
		}

		// 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 C10 = (1 or (0 shl 2))
		const val C11 = (1 or (1 shl 2))
		const val C12 = (1 or (2 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))
	}
}
