package ru.dbotthepony.kstarbound.math

import ru.dbotthepony.kommons.math.intersectRectangles
import ru.dbotthepony.kommons.math.rectangleContainsRectangle
import ru.dbotthepony.kommons.util.IStruct2i
import ru.dbotthepony.kstarbound.math.vector.Vector2i

data class AABBi(val mins: Vector2i, val maxs: Vector2i) {
	init {
		require(mins.x <= maxs.x) { "mins.x ${mins.x} is more than maxs.x ${maxs.x}" }
		require(mins.y <= maxs.y) { "mins.y ${mins.y} is more than maxs.y ${maxs.y}" }
	}

	operator fun plus(other: AABBi) = AABBi(mins + other.mins, maxs + other.maxs)
	operator fun minus(other: AABBi) = AABBi(mins - other.mins, maxs - other.maxs)
	operator fun times(other: AABBi) = AABBi(mins * other.mins, maxs * other.maxs)
	operator fun div(other: AABBi) = AABBi(mins / other.mins, maxs / other.maxs)

	operator fun plus(other: Vector2i) = AABBi(mins + other, maxs + other)
	operator fun minus(other: Vector2i) = AABBi(mins - other, maxs - other)
	operator fun times(other: Vector2i) = AABBi(mins * other, maxs * other)
	operator fun div(other: Vector2i) = AABBi(mins / other, maxs / other)

	operator fun times(other: Int) = AABBi(mins * other, maxs * other)
	operator fun div(other: Int) = AABBi(mins / other, maxs / other)

	val xSpan get() = maxs.x - mins.x
	val ySpan get() = maxs.y - mins.y
	val centre get() = (mins.toDoubleVector() + maxs.toDoubleVector()) * 0.5

	val A get() = mins
	val B get() = Vector2i(mins.x, maxs.y)
	val C get() = maxs
	val D get() = Vector2i(maxs.x, mins.y)

	val bottomLeft get() = A
	val topLeft get() = B
	val topRight get() = C
	val bottomRight get() = D

	val width get() = (maxs.x - mins.x) / 2
	val height get() = (maxs.y - mins.y) / 2

	val diameter get() = mins.distance(maxs)
	val radius get() = diameter / 2.0

	val perimeter get() = (xSpan + ySpan) * 2

	fun isInside(point: IStruct2i): Boolean {
		return point.component1() in mins.x .. maxs.x && point.component2() in mins.y .. maxs.y
	}

	fun isInside(x: Int, y: Int): Boolean {
		return x in mins.x .. maxs.x && y in mins.y .. maxs.y
	}

	operator fun contains(point: IStruct2i) = isInside(point)

	/**
	 * Checks whenever is this AABBi intersect with [other]
	 *
	 * This method consider they intersect even if only one of axis are equal
	 */
	fun intersect(other: AABBi): Boolean {
		return intersectRectangles(mins, maxs, other.mins, other.maxs)
	}

	/**
	 * Returns whenever [other] is contained (encased) inside this AABBi
	 */
	operator fun contains(other: AABBi): Boolean {
		return rectangleContainsRectangle(mins, maxs, other.mins, other.maxs)
	}

	/**
	 * Checks whenever is this AABB intersect with [other]
	 *
	 * This method DOES NOT consider they intersect if only one of axis are equal
	 */
	fun intersectWeak(other: AABBi): Boolean {
		if (maxs.x == other.mins.x || mins.x == other.maxs.x || maxs.y == other.mins.y || mins.y == other.maxs.y)
			return false

		return intersectRectangles(mins, maxs, other.mins, other.maxs)
	}

	fun toDoubleAABB() = AABB(mins.toDoubleVector(), maxs.toDoubleVector())

	/**
	 * Returns AABB which contains this AABB and specified point
	 */
	fun expand(x: Int, y: Int): AABBi {
		if (isInside(x, y))
			return this

		return AABBi(
			mins.coerceAtMost(x, y),
			maxs.coerceAtLeast(x, y)
		)
	}

	fun padded(x: Int, y: Int) = expand(x, y)
	fun padded(value: IStruct2i) = expand(value.component1(), value.component2())
	fun expand(value: IStruct2i) = expand(value.component1(), value.component2())

	/**
	 * Returns AABB which contains both AABBs
	 */
	fun combine(other: AABBi): AABBi {
		if (contains(other)) return this

		val minX = mins.x.coerceAtMost(other.mins.x)
		val minY = mins.y.coerceAtMost(other.mins.y)
		val maxX = maxs.x.coerceAtLeast(other.maxs.x)
		val maxY = maxs.y.coerceAtLeast(other.maxs.y)

		return AABBi(Vector2i(minX, minY), Vector2i(maxX, maxY))
	}

	companion object {
		/**
		 * Rectangle with given [width] and [height]
		 */
		fun rectangle(pos: IStruct2i, width: Int, height: Int = width): AABBi {
			val (x, y) = pos

			return AABBi(
				Vector2i(x - width / 2, y - height / 2),
				Vector2i(x + width / 2, y + height / 2),
			)
		}

		/**
		 * Rectangle with given [width] * 2 and [height] * 2
		 */
		fun withSide(pos: IStruct2i, width: Int, height: Int = width): AABBi {
			val (x, y) = pos

			return AABBi(
				Vector2i(x - width, y - height),
				Vector2i(x + width, y + height),
			)
		}

		/**
		 * Rectangle with given [width] * 2 and [height] * 2
		 */
		fun withSide(pos: IStruct2i, size: IStruct2i): AABBi {
			val (width, height) = size
			return withSide(pos, width, height)
		}

		@JvmField val ZERO = AABBi(Vector2i.ZERO, Vector2i.ZERO)
	}
}
