package ru.dbotthepony.kstarbound.math.matrix

import ru.dbotthepony.kommons.arrays.Double2DArray
import ru.dbotthepony.kommons.arrays.mulMatrixComponents
import ru.dbotthepony.kommons.util.IStruct2d
import ru.dbotthepony.kstarbound.math.vector.Vector2d
import java.nio.ByteBuffer
import java.nio.DoubleBuffer

sealed class Matrix2d : Double2DArray() {
	abstract var c00: Double
	abstract var c01: Double
	abstract var c10: Double
	abstract var c11: Double

	fun c00() = c00
	fun c00(value: Double): Matrix2d { c00 = value; return this }
	fun c01() = c01
	fun c01(value: Double): Matrix2d { c01 = value; return this }
	fun c10() = c10
	fun c10(value: Double): Matrix2d { c10 = value; return this }
	fun c11() = c11
	fun c11(value: Double): Matrix2d { c11 = value; return this }

	var r00: Double get() = c00; set(value) { c00 = value } // row 0 column 0
	var r01: Double get() = c10; set(value) { c10 = value } // row 1 column 0
	var r10: Double get() = c01; set(value) { c01 = value } // row 0 column 1
	var r11: Double get() = c11; set(value) { c11 = value } // row 1 column 1

	fun r00() = r00
	fun r00(value: Double): Matrix2d { r00 = value; return this }
	fun r01() = r01
	fun r01(value: Double): Matrix2d { r01 = value; return this }
	fun r10() = r10
	fun r10(value: Double): Matrix2d { r10 = value; return this }
	fun r11() = r11
	fun r11(value: Double): Matrix2d { 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): Double {
		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: Double) {
		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: Double
		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: Matrix2d): Matrix2d {
		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: Matrix2d): Matrix2d {
		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: Double = 1.0,
		c01: Double = 0.0,
		c10: Double = 0.0,
		c11: Double = 1.0,
	): Matrix2d {
		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: Double = 1.0,
		c01: Double = 0.0,
		c10: Double = 0.0,
		c11: Double = 1.0,
	): Matrix2d {
		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: Double): Matrix2d {
		return super.add(other) as Matrix2d
	}

	final override fun sub(other: Double): Matrix2d {
		return super.sub(other) as Matrix2d
	}

	final override fun mul(other: Double): Matrix2d {
		return super.mul(other) as Matrix2d
	}

	final override fun div(other: Double): Matrix2d {
		return super.div(other) as Matrix2d
	}

	final override fun add(other: Double2DArray): Matrix2d {
		return super.add(other) as Matrix2d
	}

	final override fun sub(other: Double2DArray): Matrix2d {
		return super.sub(other) as Matrix2d
	}

	fun to3d(filler: Double) = Matrix3d.from(this, filler)
	fun to3d() = Matrix3d.from(this)
	fun to4d(filler: Double) = Matrix4d.from(this, filler)
	fun to4d() = Matrix4d.from(this)

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

	@JvmOverloads
	fun scale(x: Double = 1.0, y: Double = 1.0): Matrix2d {
		return mul(c00 = x, c11 = y)
	}

	fun scale(value: IStruct2d) = scale(value.component1(), value.component2())
	fun scale(value: Vector2d) = scale(value.component1(), value.component2())

	@JvmOverloads
	fun preScale(x: Double = 1.0, y: Double = 1.0): Matrix2d {
		return mulIntoThis(c00 = x, c11 = y)
	}

	fun preScale(value: IStruct2d) = preScale(value.component1(), value.component2())
	fun preScale(value: Vector2d) = preScale(value.component1(), value.component2())

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

	private data class Impl(
		override var c00: Double = 1.0,
		override var c01: Double = 0.0,
		override var c10: Double = 0.0,
		override var c11: Double = 1.0,
	) : Matrix2d() {
		override fun toString(): String {
			return "Matrix2d" + super.toString()
		}

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

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

	private class View(private val parent: Matrix2d) : Matrix2d() {
		override var c00: Double get() = parent.c00; set(_) { throw UnsupportedOperationException("Read-Only view") }
		override var c01: Double get() = parent.c01; set(_) { throw UnsupportedOperationException("Read-Only view") }
		override var c10: Double get() = parent.c10; set(_) { throw UnsupportedOperationException("Read-Only view") }
		override var c11: Double get() = parent.c11; set(_) { throw UnsupportedOperationException("Read-Only view") }

		override fun set(column: Int, row: Int, value: Double) {
			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: Double) = construct2(value, Companion::columnMajor)

		@JvmStatic
		fun zero() = all(0.0)

		@JvmStatic
		fun identity() = columnMajor()

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

		@JvmStatic
		fun columnMajor(
			c00: Double = 1.0,
			c01: Double = 0.0,
			c10: Double = 0.0,
			c11: Double = 1.0,
		): Matrix2d {
			return Impl(c00, c01, c10, c11)
		}

		@JvmStatic
		fun rowMajor(
			r00: Double = 1.0, r01: Double = 0.0,
			r10: Double = 0.0, r11: Double = 1.0,
		): Matrix2d {
			return Impl(
				r00, r10,
				r01, r11,
			)
		}

		@JvmStatic
		fun from(matrix: Double2DArray, filler: Double) = construct2(matrix, Double2DArray::get, Companion::columnMajor, filler)
		@JvmStatic
		fun from(matrix: Double2DArray) = construct2(matrix, Double2DArray::get, Companion::columnMajor, 0.0)
		@JvmStatic
		fun from(matrix: Matrix2d) = construct2(matrix, Double2DArray::get, Companion::columnMajor)
		@JvmStatic
		fun from(matrix: Matrix3d) = construct2(matrix, Double2DArray::get, Companion::columnMajor)
		@JvmStatic
		fun from(matrix: Matrix4d) = construct2(matrix, Double2DArray::get, Companion::columnMajor)

		@JvmStatic
		fun fromTransposed(matrix: Double2DArray) = construct2(matrix, Double2DArray::get, Companion::rowMajor, 0.0)
		@JvmStatic
		fun fromTransposed(matrix: Double2DArray, filler: Double) = construct2(matrix, Double2DArray::get, Companion::rowMajor, filler)
		@JvmStatic
		fun fromTransposed(matrix: Matrix2d) = construct2(matrix, Double2DArray::get, Companion::rowMajor)
		@JvmStatic
		fun fromTransposed(matrix: Matrix3d) = construct2(matrix, Double2DArray::get, Companion::rowMajor)
		@JvmStatic
		fun fromTransposed(matrix: Matrix4d) = construct2(matrix, Double2DArray::get, Companion::rowMajor)

		@JvmStatic
		fun fromColumnMajor(buffer: DoubleBuffer) = construct2(buffer, DoubleBuffer::get, Companion::columnMajor)
		@JvmStatic
		fun fromColumnMajor(buffer: ByteBuffer) = construct2(buffer, ByteBuffer::getDouble, Companion::columnMajor)
		@JvmStatic
		fun fromRowMajor(buffer: DoubleBuffer) = construct2(buffer, DoubleBuffer::get, Companion::rowMajor)
		@JvmStatic
		fun fromRowMajor(buffer: ByteBuffer) = construct2(buffer, ByteBuffer::getDouble, 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))
	}
}
