package ru.dbotthepony.kstarbound.world.physics

import com.google.common.collect.ImmutableList
import it.unimi.dsi.fastutil.ints.IntList
import ru.dbotthepony.kommons.vector.Vector2d

private val POLY_VERTICES: ImmutableList<Vector2d> = ImmutableList.of(
	Vector2d(0.5, 0.5),
	Vector2d(0.5, 1.0),
	Vector2d(0.5, 1.5),
	Vector2d(1.0, 1.5),
	Vector2d(1.5, 1.5),
	Vector2d(1.5, 1.0),
	Vector2d(1.5, 0.5),
	Vector2d(1.0, 0.5),
	Vector2d(1.0, 1.0)
)

private val POLIES = listOf(
	IntList.of(1, 2, 3),
	IntList.of(3, 4, 5),
	IntList.of(1, 2, 4, 5),
	IntList.of(7, 5, 6),
	IntList.of(1, 2, 3, 5, 6, 7),
	IntList.of(7, 3, 4, 6),
	IntList.of(1, 2, 4, 6, 7),
	IntList.of(0, 1, 7),
	IntList.of(0, 2, 3, 7),
	IntList.of(0, 1, 3, 4, 5, 7),
	IntList.of(0, 2, 4, 5, 7),
	IntList.of(0, 1, 5, 6),
	IntList.of(0, 2, 3, 5, 6),
	IntList.of(0, 1, 3, 4, 6),
	IntList.of(0, 2, 4, 6),
	// special cases for squared off top corners
	IntList.of(5, 6, 7, 8), // top left corner
	IntList.of(0, 1, 8, 7), // top right corner
	// special cases for hollowed out bottom corners
	IntList.of(0, 2, 3, 8), // lower left corner part 1
	IntList.of(0, 8, 5, 6), // lower left corner part 2
	IntList.of(0, 1, 8, 6), // lower right corner part 1
	IntList.of(6, 8, 3, 4) // lower right corner part 2
).map { Poly(it.map { POLY_VERTICES[it] }) }

fun interface CollisionTypeGetter {
	fun collisionType(x: Int, y: Int): CollisionType
}

private fun MutableList<CollisionPoly>.addBlock(x: Int, y: Int, getter: CollisionTypeGetter, index: Int) {
	val poly = POLIES.getOrNull(index - 1) ?: return

	add(CollisionPoly(
		poly + Vector2d(x.toDouble(), y.toDouble()),
		getter.collisionType(x, y).coerceAtLeast(getter.collisionType(x + 1, y)).coerceAtLeast(getter.collisionType(x + 1, y + 1)).coerceAtLeast(getter.collisionType(x, y + 1))
	))
}

private fun MutableList<CollisionPoly>.addBlock(poly: List<Vector2d>, kind: CollisionType) {
	add(CollisionPoly(
		Poly(poly),
		kind
	))
}

fun getBlockPlatforms(x: Int, y: Int, getter: CollisionTypeGetter, kind: CollisionType, target: MutableList<CollisionPoly> = arrayListOf()): MutableList<CollisionPoly> {
	// This was once simple and elegant and made sense but then I made it
	// match the actual platform rendering more closely and now it's a big
	// shitty pile of special cases again. RIP.

	if (getter.collisionType(x, y) != kind) return target

	val right = getter.collisionType(x + 1, y) == kind
	val left = getter.collisionType(x - 1, y) == kind

	val downRight = getter.collisionType(x + 1, y - 1) == kind && getter.collisionType(x + 1, y) != kind
	val downLeft = getter.collisionType(x - 1, y - 1) == kind && getter.collisionType(x - 1, y) != kind

	val upRight = getter.collisionType(x + 1, y + 1) == kind && !left && !right
	val upLeft = getter.collisionType(x - 1, y + 1) == kind && !left && !right

	val above = getter.collisionType(x, y + 1) == kind
	val below = getter.collisionType(x, y - 1) == kind

	val dx = x.toDouble()
	val dy = y.toDouble()

	if (downRight && downLeft && upRight && upLeft) {
		target.addBlock(listOf(Vector2d(dx, dy), Vector2d(dx + 1, dy + 1)), kind)
		target.addBlock(listOf(Vector2d(dx + 1, dy), Vector2d(dx, dy + 1)), kind)
	} else if (above && below) {
		target.addBlock(listOf(Vector2d(dx, dy + 1), Vector2d(dx + 1, dy + 1)), kind)
	} else if (upLeft && downLeft && !upRight && !downRight) {
		target.addBlock(listOf(Vector2d(dx + 1, dy), Vector2d(dx, dy + 1)), kind)
	} else if (upRight && downRight && !upLeft) { // Fix: copy-paste typo in original sources
		target.addBlock(listOf(Vector2d(dx, dy), Vector2d(dx + 1, dy + 1)), kind)
	} else if (upRight && downLeft) {
		target.addBlock(listOf(Vector2d(dx, dy), Vector2d(dx + 1, dy + 1)), kind)

	// special case block for connecting flat platform above
	if (above && getter.collisionType(x + 1, y + 1) == kind)
		target.addBlock(listOf(Vector2d(dx + 1, dy + 1), Vector2d(dx + 2, dy + 2)), kind)
	} else if (upLeft && downRight) {
		target.addBlock(listOf(Vector2d(dx + 1, dy), Vector2d(dx, dy + 1)), kind)

	// special case block for connecting flat platform above
	if (above && getter.collisionType(x - 1, y + 1) == kind)
		target.addBlock(listOf(Vector2d(dx, dy + 1), Vector2d(dx - 1, dy + 2)), kind)
	} else if (above && !downRight && !downLeft) {
		target.addBlock(listOf(Vector2d(dx, dy + 1), Vector2d(dx + 1, dy + 1)), kind)
	} else if (upLeft && !upRight) {
		target.addBlock(listOf(Vector2d(dx + 1, dy), Vector2d(dx, dy + 1)), kind)
	} else if (upRight && !upLeft) {
		target.addBlock(listOf(Vector2d(dx, dy), Vector2d(dx + 1, dy + 1)), kind)
	} else if (downRight && (left || !below)) {
		target.addBlock(listOf(Vector2d(dx + 1, dy), Vector2d(dx, dy + 1)), kind)
	} else if (downLeft && (right || !below)) {
		target.addBlock(listOf(Vector2d(dx, dy), Vector2d(dx + 1, dy + 1)), kind)
	} else {
		target.addBlock(listOf(Vector2d(dx, dy + 1), Vector2d(dx + 1, dy + 1)), kind)
	}

	return target
}

fun getBlocksMarchingSquares(x: Int, y: Int, getter: CollisionTypeGetter, kind: CollisionType, target: MutableList<CollisionPoly> = arrayListOf()): MutableList<CollisionPoly> {
	var neighborMask = 0

	if (getter.collisionType(x, y + 1) >= kind) neighborMask = neighborMask or 1
	if (getter.collisionType(x + 1, y + 1) >= kind) neighborMask = neighborMask or 2
	if (getter.collisionType(x + 1, y) >= kind) neighborMask = neighborMask or 4
	if (getter.collisionType(x, y) >= kind) neighborMask = neighborMask or 8

	when (neighborMask) {
		4 -> {
			if (getter.collisionType(x + 2, y) >= kind &&
				getter.collisionType(x + 2, y + 1) < kind &&
				getter.collisionType(x, y - 1) < kind) {

				target.addBlock(x, y, getter, 16)
				return target
			}
		}

		8 -> {
			if (getter.collisionType(x - 1, y) >= kind &&
				getter.collisionType(x - 1, y + 1) < kind &&
				getter.collisionType(x + 1, y - 1) < kind) {

				target.addBlock(x, y, getter, 17)
				return target
			}
		}

		13 -> {
			if (getter.collisionType(x, y + 2) >= kind &&
				getter.collisionType(x + 1, y + 2) < kind &&
				getter.collisionType(x + 2, y) >= kind) {

				target.addBlock(x, y, getter, 18)
				target.addBlock(x, y, getter, 19)
				return target
			}
		}

		14 -> {
			if (getter.collisionType(x, y + 2) < kind &&
				getter.collisionType(x + 1, y + 2) >= kind &&
				getter.collisionType(x - 1, y) >= kind) {

				target.addBlock(x, y, getter, 20)
				target.addBlock(x, y, getter, 21)
				return target
			}
		}
	}

	if (neighborMask != 0) {
		target.addBlock(x, y, getter, neighborMask)
	}

	return target
}
