package ru.dbotthepony.kstarbound.math.vector

import ru.dbotthepony.kommons.util.IStruct2d
import ru.dbotthepony.kommons.util.IStruct2f
import ru.dbotthepony.kommons.util.IStruct2i
import ru.dbotthepony.kommons.util.IStruct3d
import ru.dbotthepony.kommons.util.IStruct3f
import ru.dbotthepony.kommons.util.IStruct3i
import ru.dbotthepony.kommons.util.IStruct4d
import ru.dbotthepony.kommons.util.IStruct4f
import ru.dbotthepony.kommons.util.IStruct4i
import ru.dbotthepony.kstarbound.math.matrix.Matrix4d
import ru.dbotthepony.kstarbound.math.matrix.Matrix4dStack
import kotlin.math.absoluteValue
import kotlin.math.sqrt

data class Vector4d(
	val x: Double = 0.0,
	val y: Double = 0.0,
	val z: Double = 0.0,
	val w: Double = 0.0,
) : IStruct4d, Vector() {
	constructor(value: IStruct2d) : this(value.component1(), value.component2())
	constructor(value: Vector2d) : this(value.x, value.y)
	constructor(value: IStruct2d, z: Double) : this(value.component1(), value.component2(), z)
	constructor(value: Vector2d, z: Double) : this(value.x, value.y, z)
	constructor(value: IStruct2d, z: Double, w: Double) : this(value.component1(), value.component2(), z, w)
	constructor(value: Vector2d, z: Double, w: Double) : this(value.x, value.y, z, w)
	constructor(value: IStruct3d) : this(value.component1(), value.component2(), value.component3())
	constructor(value: Vector3d) : this(value.x, value.y, value.z)
	constructor(value: IStruct3d, w: Double) : this(value.component1(), value.component2(), value.component3(), w)
	constructor(value: Vector3d, w: Double) : this(value.x, value.y, value.z, w)
	constructor(value: IStruct4d) : this(value.component1(), value.component2(), value.component3(), value.component4())
	constructor(value: Vector4d) : this(value.x, value.y, value.z, value.w)

	override val lengthSquared: Double
		get() = x * x + y * y + z * z + w * w

	override val isFinite: Boolean
		get() = x.isFinite() && y.isFinite() && z.isFinite() && w.isFinite()
	override val isNaN: Boolean
		get() = x.isNaN() || y.isNaN() || z.isNaN() || w.isNaN()

	val unitVector: Vector4d
		get() {
		var length = lengthSquared
		if (length == 0.0 || length == 1.0) return this
		length = sqrt(length)
		return Vector4d(x / length, y / length, z / length, w / length)
	}

	val absoluteValue: Vector4d
		get() {
		val x = x.absoluteValue
		val y = y.absoluteValue
		val z = z.absoluteValue
		val w = w.absoluteValue
		if (x != this.x || y != this.y || z != this.z || w != this.w) return Vector4d(x, y, z, w)
		return this
	}

	val r get() = x
	val g get() = y
	val b get() = z
	val a get() = w

	val s get() = x
	val t get() = y
	val p get() = z
	val q get() = w

	fun plus(x: Double = 0.0, y: Double = 0.0, z: Double = 0.0, w: Double = 0.0) = Vector4d(this.x + x, this.y + y, this.z + z, this.w + w)
	fun minus(x: Double = 0.0, y: Double = 0.0, z: Double = 0.0, w: Double = 0.0) = Vector4d(this.x - x, this.y - y, this.z - z, this.w - w)
	fun times(x: Double = 1.0, y: Double = 1.0, z: Double = 1.0, w: Double = 1.0) = Vector4d(this.x * x, this.y * y, this.z * z, this.w * w)
	fun div(x: Double = 1.0, y: Double = 1.0, z: Double = 1.0, w: Double = 1.0) = Vector4d(this.x / x, this.y / y, this.z / z, this.w / w)

	operator fun plus(value: IStruct4d): Vector4d { val (x, y, z, w) = value; return plus(x, y, z, w) }
	operator fun minus(value: IStruct4d): Vector4d { val (x, y, z, w) = value; return minus(x, y, z, w) }
	operator fun times(value: IStruct4d): Vector4d { val (x, y, z, w) = value; return times(x, y, z, w) }
	operator fun div(value: IStruct4d): Vector4d { val (x, y, z, w) = value; return div(x, y, z, w) }
	operator fun plus(value: IStruct3d): Vector4d { val (x, y, z) = value; return plus(x, y, z) }
	operator fun minus(value: IStruct3d): Vector4d { val (x, y, z) = value; return minus(x, y, z) }
	operator fun times(value: IStruct3d): Vector4d { val (x, y, z) = value; return times(x, y, z) }
	operator fun div(value: IStruct3d): Vector4d { val (x, y, z) = value; return div(x, y, z) }
	operator fun plus(value: IStruct2d): Vector4d { val (x, y) = value; return plus(x, y) }
	operator fun minus(value: IStruct2d): Vector4d { val (x, y) = value; return minus(x, y) }
	operator fun times(value: IStruct2d): Vector4d { val (x, y) = value; return times(x, y) }
	operator fun div(value: IStruct2d): Vector4d { val (x, y) = value; return div(x, y) }

	operator fun plus(value: Vector4d): Vector4d { val (x, y, z, w) = value; return plus(x, y, z, w) }
	operator fun minus(value: Vector4d): Vector4d { val (x, y, z, w) = value; return minus(x, y, z, w) }
	operator fun times(value: Vector4d): Vector4d { val (x, y, z, w) = value; return times(x, y, z, w) }
	operator fun div(value: Vector4d): Vector4d { val (x, y, z, w) = value; return div(x, y, z, w) }
	operator fun plus(value: Vector3d): Vector4d { val (x, y, z) = value; return plus(x, y, z) }
	operator fun minus(value: Vector3d): Vector4d { val (x, y, z) = value; return minus(x, y, z) }
	operator fun times(value: Vector3d): Vector4d { val (x, y, z) = value; return times(x, y, z) }
	operator fun div(value: Vector3d): Vector4d { val (x, y, z) = value; return div(x, y, z) }
	operator fun plus(value: Vector2d): Vector4d { val (x, y) = value; return plus(x, y) }
	operator fun minus(value: Vector2d): Vector4d { val (x, y) = value; return minus(x, y) }
	operator fun times(value: Vector2d): Vector4d { val (x, y) = value; return times(x, y) }
	operator fun div(value: Vector2d): Vector4d { val (x, y) = value; return div(x, y) }

	operator fun plus(value: Double) = plus(value, value, value, value)
	operator fun minus(value: Double) = minus(value, value, value, value)
	operator fun times(value: Double) = times(value, value, value, value)
	operator fun div(value: Double) = div(value, value, value, value)

	fun plus(x: Int = 0, y: Int = 0, z: Int = 0, w: Int = 0) = Vector4d(this.x + x, this.y + y, this.z + z, this.w + w)
	fun minus(x: Int = 0, y: Int = 0, z: Int = 0, w: Int = 0) = Vector4d(this.x - x, this.y - y, this.z - z, this.w - w)
	fun times(x: Int = 1, y: Int = 1, z: Int = 1, w: Int = 1) = Vector4d(this.x * x, this.y * y, this.z * z, this.w * w)
	fun div(x: Int = 1, y: Int = 1, z: Int = 1, w: Int = 1) = Vector4d(this.x / x, this.y / y, this.z / z, this.w / w)

	operator fun plus(value: IStruct4i): Vector4d { val (x, y, z, w) = value; return plus(x, y, z, w) }
	operator fun minus(value: IStruct4i): Vector4d { val (x, y, z, w) = value; return minus(x, y, z, w) }
	operator fun times(value: IStruct4i): Vector4d { val (x, y, z, w) = value; return times(x, y, z, w) }
	operator fun div(value: IStruct4i): Vector4d { val (x, y, z, w) = value; return div(x, y, z, w) }
	operator fun plus(value: IStruct3i): Vector4d { val (x, y, z) = value; return plus(x, y, z) }
	operator fun minus(value: IStruct3i): Vector4d { val (x, y, z) = value; return minus(x, y, z) }
	operator fun times(value: IStruct3i): Vector4d { val (x, y, z) = value; return times(x, y, z) }
	operator fun div(value: IStruct3i): Vector4d { val (x, y, z) = value; return div(x, y, z) }
	operator fun plus(value: IStruct2i): Vector4d { val (x, y) = value; return plus(x, y) }
	operator fun minus(value: IStruct2i): Vector4d { val (x, y) = value; return minus(x, y) }
	operator fun times(value: IStruct2i): Vector4d { val (x, y) = value; return times(x, y) }
	operator fun div(value: IStruct2i): Vector4d { val (x, y) = value; return div(x, y) }

	operator fun plus(value: Vector4i): Vector4d { val (x, y, z, w) = value; return plus(x, y, z, w) }
	operator fun minus(value: Vector4i): Vector4d { val (x, y, z, w) = value; return minus(x, y, z, w) }
	operator fun times(value: Vector4i): Vector4d { val (x, y, z, w) = value; return times(x, y, z, w) }
	operator fun div(value: Vector4i): Vector4d { val (x, y, z, w) = value; return div(x, y, z, w) }
	operator fun plus(value: Vector3i): Vector4d { val (x, y, z) = value; return plus(x, y, z) }
	operator fun minus(value: Vector3i): Vector4d { val (x, y, z) = value; return minus(x, y, z) }
	operator fun times(value: Vector3i): Vector4d { val (x, y, z) = value; return times(x, y, z) }
	operator fun div(value: Vector3i): Vector4d { val (x, y, z) = value; return div(x, y, z) }
	operator fun plus(value: Vector2i): Vector4d { val (x, y) = value; return plus(x, y) }
	operator fun minus(value: Vector2i): Vector4d { val (x, y) = value; return minus(x, y) }
	operator fun times(value: Vector2i): Vector4d { val (x, y) = value; return times(x, y) }
	operator fun div(value: Vector2i): Vector4d { val (x, y) = value; return div(x, y) }

	operator fun plus(value: Int) = plus(value, value, value, value)
	operator fun minus(value: Int) = minus(value, value, value, value)
	operator fun times(value: Int) = times(value, value, value, value)
	operator fun div(value: Int) = div(value, value, value, value)

	fun plus(x: Float = 0f, y: Float = 0f, z: Float = 0f, w: Float = 0f) = Vector4d(this.x + x, this.y + y, this.z + z, this.w + w)
	fun minus(x: Float = 0f, y: Float = 0f, z: Float = 0f, w: Float = 0f) = Vector4d(this.x - x, this.y - y, this.z - z, this.w - w)
	fun times(x: Float = 1f, y: Float = 1f, z: Float = 1f, w: Float = 1f) = Vector4d(this.x * x, this.y * y, this.z * z, this.w * w)
	fun div(x: Float = 1f, y: Float = 1f, z: Float = 1f, w: Float = 1f) = Vector4d(this.x / x, this.y / y, this.z / z, this.w / w)

	operator fun plus(value: IStruct4f): Vector4d { val (x, y, z, w) = value; return plus(x, y, z, w) }
	operator fun minus(value: IStruct4f): Vector4d { val (x, y, z, w) = value; return minus(x, y, z, w) }
	operator fun times(value: IStruct4f): Vector4d { val (x, y, z, w) = value; return times(x, y, z, w) }
	operator fun div(value: IStruct4f): Vector4d { val (x, y, z, w) = value; return div(x, y, z, w) }
	operator fun plus(value: IStruct3f): Vector4d { val (x, y, z) = value; return plus(x, y, z) }
	operator fun minus(value: IStruct3f): Vector4d { val (x, y, z) = value; return minus(x, y, z) }
	operator fun times(value: IStruct3f): Vector4d { val (x, y, z) = value; return times(x, y, z) }
	operator fun div(value: IStruct3f): Vector4d { val (x, y, z) = value; return div(x, y, z) }
	operator fun plus(value: IStruct2f): Vector4d { val (x, y) = value; return plus(x, y) }
	operator fun minus(value: IStruct2f): Vector4d { val (x, y) = value; return minus(x, y) }
	operator fun times(value: IStruct2f): Vector4d { val (x, y) = value; return times(x, y) }
	operator fun div(value: IStruct2f): Vector4d { val (x, y) = value; return div(x, y) }

	operator fun plus(value: Vector4f): Vector4d { val (x, y, z, w) = value; return plus(x, y, z, w) }
	operator fun minus(value: Vector4f): Vector4d { val (x, y, z, w) = value; return minus(x, y, z, w) }
	operator fun times(value: Vector4f): Vector4d { val (x, y, z, w) = value; return times(x, y, z, w) }
	operator fun div(value: Vector4f): Vector4d { val (x, y, z, w) = value; return div(x, y, z, w) }
	operator fun plus(value: Vector3f): Vector4d { val (x, y, z) = value; return plus(x, y, z) }
	operator fun minus(value: Vector3f): Vector4d { val (x, y, z) = value; return minus(x, y, z) }
	operator fun times(value: Vector3f): Vector4d { val (x, y, z) = value; return times(x, y, z) }
	operator fun div(value: Vector3f): Vector4d { val (x, y, z) = value; return div(x, y, z) }
	operator fun plus(value: Vector2f): Vector4d { val (x, y) = value; return plus(x, y) }
	operator fun minus(value: Vector2f): Vector4d { val (x, y) = value; return minus(x, y) }
	operator fun times(value: Vector2f): Vector4d { val (x, y) = value; return times(x, y) }
	operator fun div(value: Vector2f): Vector4d { val (x, y) = value; return div(x, y) }

	operator fun plus(value: Float) = plus(value, value, value, value)
	operator fun minus(value: Float) = minus(value, value, value, value)
	operator fun times(value: Float) = times(value, value, value, value)
	operator fun div(value: Float) = div(value, value, value, value)

	operator fun unaryMinus(): Vector4d = Vector4d(-x, -y, -z, -w)

	fun dot(x: Double, y: Double, z: Double, w: Double) = this.x * x + this.y * y + this.z * z + this.w * w
	fun dot(value: IStruct4d): Double { val (x, y, z, w) = value; return dot(x, y, z, w) }
	fun dot(value: Vector4d): Double { val (x, y, z, w) = value; return dot(x, y, z, w) }

	fun distanceSquared(x: Double, y: Double, z: Double, w: Double): Double {
		val dx = (this.x - x)
		val dy = (this.y - y)
		val dz = (this.z - z)
		val dw = (this.w - w)
		return dx * dx + dy * dy + dz * dz + dw * dw
	}

	fun distanceSquared(value: IStruct4d): Double { val (x, y, z, w) = value; return distanceSquared(x, y, z, w) }
	fun distanceSquared(value: Vector4d): Double { val (x, y, z, w) = value; return distanceSquared(x, y, z, w) }
	fun distance(x: Double, y: Double, z: Double, w: Double) = sqrt(distanceSquared(x, y, z, w))
	fun distance(value: IStruct4d): Double { val (x, y, z, w) = value; return sqrt(distanceSquared(x, y, z, w)) }
	fun distance(value: Vector4d): Double { val (x, y, z, w) = value; return sqrt(distanceSquared(x, y, z, w)) }

	fun coerceAtLeast(x: Double, y: Double, z: Double, w: Double) = Vector4d(this.x.coerceAtLeast(x), this.y.coerceAtLeast(y), this.z.coerceAtLeast(z), this.w.coerceAtLeast(w))
	fun coerceAtMost(x: Double, y: Double, z: Double, w: Double) = Vector4d(this.x.coerceAtMost(x), this.y.coerceAtMost(y), this.z.coerceAtMost(z), this.w.coerceAtMost(w))

	fun coerceIn(minimum: Vector4d, maximum: Vector4d) = Vector4d(
		this.x.coerceIn(minimum.x, maximum.x),
		this.y.coerceIn(minimum.y, maximum.y),
		this.z.coerceIn(minimum.z, maximum.z),
		this.w.coerceIn(minimum.w, maximum.w))

	fun coerceIn(minimum: IStruct4d, maximum: IStruct4d) = Vector4d(
		this.x.coerceIn(minimum.component1(), maximum.component1()),
		this.y.coerceIn(minimum.component2(), maximum.component2()),
		this.z.coerceIn(minimum.component3(), maximum.component3()),
		this.w.coerceIn(minimum.component4(), maximum.component4()))

	fun coerceAtLeast(value: IStruct4d): Vector4d { val (x, y, z, w) = value; return coerceAtLeast(x, y, z, w) }
	fun coerceAtMost(value: IStruct4d): Vector4d { val (x, y, z, w) = value; return coerceAtMost(x, y, z, w) }
	fun coerceAtLeast(value: Vector4d): Vector4d { val (x, y, z, w) = value; return coerceAtLeast(x, y, z, w) }
	fun coerceAtMost(value: Vector4d): Vector4d { val (x, y, z, w) = value; return coerceAtMost(x, y, z, w) }

	operator fun times(value: Matrix4d): Vector4d {
		return Vector4d(
			x * value.c00 + y * value.c10 + z * value.c20 + w * value.c30,
			x * value.c01 + y * value.c11 + z * value.c21 + w * value.c31,
			x * value.c02 + y * value.c12 + z * value.c22 + w * value.c32,
			x * value.c03 + y * value.c13 + z * value.c23 + w * value.c33,
		)
	}

	operator fun times(value: Matrix4dStack) = times(value.last())

	fun toDoubleVector() = this
	fun toFloatVector() = Vector4f(x.toFloat(), y.toFloat(), z.toFloat(), w.toFloat())

	override fun toString(): String {
		return "[$x, $y, $z, $w]"
	}

	companion object {
		@JvmField val ZERO = Vector4d()
		@JvmField val POSITIVE_X = Vector4d(x = 1.0)
		@JvmField val NEGATIVE_X = Vector4d(x = -1.0)
		@JvmField val POSITIVE_Y = Vector4d(y = 1.0)
		@JvmField val NEGATIVE_Y = Vector4d(y = -1.0)
		@JvmField val POSITIVE_Z = Vector4d(z = 1.0)
		@JvmField val NEGATIVE_Z = Vector4d(z = -1.0)
		@JvmField val POSITIVE_W = Vector4d(w = 1.0)
		@JvmField val NEGATIVE_W = Vector4d(w = -1.0)
	}
}
