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.kstarbound.math.vector.Vector2f
import java.nio.ByteBuffer
import java.nio.FloatBuffer

sealed class Matrix2f : Float2DArray() {
	abstract var c00: Float
	abstract var c01: Float
	abstract var c10: Float
	abstract var c11: Float

	fun c00() = c00
	fun c00(value: Float): Matrix2f { c00 = value; return this }
	fun c01() = c01
	fun c01(value: Float): Matrix2f { c01 = value; return this }
	fun c10() = c10
	fun c10(value: Float): Matrix2f { c10 = value; return this }
	fun c11() = c11
	fun c11(value: Float): Matrix2f { c11 = 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 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

	fun r00() = r00
	fun r00(value: Float): Matrix2f { r00 = value; return this }
	fun r01() = r01
	fun r01(value: Float): Matrix2f { r01 = value; return this }
	fun r10() = r10
	fun r10(value: Float): Matrix2f { r10 = value; return this }
	fun r11() = r11
	fun r11(value: Float): Matrix2f { r11 = value; return this }

	final override val transposed get() =
		columnMajor(
			c00 = r00,
			c01 = r01,
			c10 = r10,
			c11 = r11,
		)

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

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

	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) }

	fun mul(other: Matrix2f): Matrix2f {
		val c00 = mulMatrixComponents(this, other, 0, 0)
		val c01 = mulMatrixComponents(this, other, 0, 1)
		val c10 = mulMatrixComponents(this, other, 1, 0)
		val c11 = mulMatrixComponents(this, other, 1, 1)
		this.c00 = c00
		this.c01 = c01
		this.c10 = c10
		this.c11 = c11
		return this
	}

	fun mulIntoOther(other: Matrix2f): Matrix2f {
		val c00 = mulMatrixComponents(this, other, 0, 0)
		val c01 = mulMatrixComponents(this, other, 0, 1)
		val c10 = mulMatrixComponents(this, other, 1, 0)
		val c11 = mulMatrixComponents(this, other, 1, 1)
		other.c00 = c00
		other.c01 = c01
		other.c10 = c10
		other.c11 = c11
		return other
	}

	fun mul(
		c00: Float = 1f,
		c01: Float = 0f,
		c10: Float = 0f,
		c11: Float = 1f,
	): Matrix2f {
		val fc00 = this.c00 * c00 + this.c10 * c01
		val fc01 = this.c01 * c00 + this.c11 * c01
		val fc10 = this.c00 * c10 + this.c10 * c11
		val fc11 = this.c01 * c10 + this.c11 * c11
		this.c00 = fc00
		this.c01 = fc01
		this.c10 = fc10
		this.c11 = fc11
		return this
	}

	fun mulIntoThis(
		c00: Float = 1f,
		c01: Float = 0f,
		c10: Float = 0f,
		c11: Float = 1f,
	): Matrix2f {
		val fc00 = c00 * this.c00 + c10 * this.c01
		val fc01 = c01 * this.c00 + c11 * this.c01
		val fc10 = c00 * this.c10 + c10 * this.c11
		val fc11 = c01 * this.c10 + c11 * this.c11
		this.c00 = fc00
		this.c01 = fc01
		this.c10 = fc10
		this.c11 = fc11
		return this
	}

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

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

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

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

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

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

	fun to3f(filler: Float) = Matrix3f.from(this, filler)
	fun to3f() = Matrix3f.from(this)
	fun to4f(filler: Float) = Matrix4f.from(this, filler)
	fun to4f() = Matrix4f.from(this)

	fun copy(): Matrix2f {
		return Impl(c00, c01, c10, c11)
	}

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

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

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

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

	fun translate(x: Float) = mul(c10 = x)
	fun preTranslate(x: Float) = mulIntoThis(c10 = x)

	private data class Impl(
		override var c00: Float = 1f,
		override var c01: Float = 0f,
		override var c10: Float = 0f,
		override var c11: Float = 1f,
	) : Matrix2f() {
		override fun toString(): String {
			return "Matrix2f" + super.toString()
		}

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

			return this === other || c00 == other.c00 &&
				c01 == other.c01 &&
				c10 == other.c10 &&
				c11 == other.c11
		}
	}

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

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

		@JvmStatic
		fun identity() = columnMajor()

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

		@JvmStatic
		fun columnMajor(
			c00: Float = 1f,
			c01: Float = 0f,
			c10: Float = 0f,
			c11: Float = 1f,
		): Matrix2f {
			return Impl(c00, c01, c10, c11)
		}

		@JvmStatic
		fun rowMajor(
			r00: Float = 1f, r01: Float = 0f,
			r10: Float = 0f, r11: Float = 1f,
		): Matrix2f {
			return Impl(
				r00, r10,
				r01, r11,
			)
		}

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

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

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

		// kotlin compiler bug? it refuses to use TABLESWITCH if these are inlined
		const val C00 = (0 or (0 shl 1))
		const val C01 = (0 or (1 shl 1))
		const val C10 = (1 or (0 shl 1))
		const val C11 = (1 or (1 shl 1))
	}
}
