package ru.dbotthepony.kstarbound.world

import ru.dbotthepony.kstarbound.math.roundByAbsoluteValue
import ru.dbotthepony.kstarbound.world.api.CHUNK_SIZE_BITS
import ru.dbotthepony.kvector.api.IStruct2d
import ru.dbotthepony.kvector.api.IStruct2i
import ru.dbotthepony.kvector.vector.Vector2i

private fun circulate(value: Int, bounds: Int): Int {
	require(bounds > 0) { "Bounds must be positive ($bounds given)" }

	if (value >= bounds) {
		return value % bounds
	} else if (value < 0) {
		return bounds + value % bounds
	}

	return value
}

/**
 * Сетка чанков идёт как и сетка тайлов.
 *
 * * Вправо у нас положительный X
 * * Влево у нас отрицательный X
 * * Вверх у нас положительный Y
 * * Вниз у нас отрицательный Y
 */
class ChunkPos(val x: Int, val y: Int) : Comparable<ChunkPos> {
	constructor(pos: IStruct2i) : this(pos.component1(), pos.component2())

	val firstBlock get() = Vector2i(tileX, tileY)
	val lastBlock get() = Vector2i(((x + 1) shl CHUNK_SIZE_BITS) - 1, ((y + 1) shl CHUNK_SIZE_BITS) - 1)

	/**
	 * Координата тайла на 0 позиции по оси X внутри чанка в мире
	 */
	val tileX: Int get() = x shl CHUNK_SIZE_BITS

	/**
	 * Координата тайла на 0 позиции по оси Y внутри чанка в мире
	 */
	val tileY: Int get() = y shl CHUNK_SIZE_BITS

	val top: ChunkPos
		get() {
		return ChunkPos(x, y + 1)
	}

	val bottom: ChunkPos
		get() {
		return ChunkPos(x, y - 1)
	}

	val left: ChunkPos
		get() {
		return ChunkPos(x - 1, y)
	}

	val topLeft: ChunkPos
		get() {
		return ChunkPos(x - 1, y + 1)
	}

	val topRight: ChunkPos
		get() {
		return ChunkPos(x + 1, y + 1)
	}

	val bottomLeft: ChunkPos
		get() {
		return ChunkPos(x - 1, y - 1)
	}

	val bottomRight: ChunkPos
		get() {
		return ChunkPos(x + 1, y - 1)
	}

	val right: ChunkPos
		get() {
		return ChunkPos(x + 1, y)
	}

	override fun equals(other: Any?): Boolean {
		if (other is ChunkPos)
			return other.x == x && other.y == y

		return false
	}

	override fun hashCode(): Int {
		return (y shl 16) xor x
	}

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

	override fun compareTo(other: ChunkPos): Int {
		if (x > other.x) {
			return 1
		} else if (x < other.x) {
			return -1
		}

		return y.compareTo(other.y)
	}

	fun circular(xWrap: Int): ChunkPos {
		val x = circulate(x, xWrap)

		if (x == this.x) {
			return this
		}

		return ChunkPos(x, y)
	}

	fun toLong(): Long {
		return toLong(x, y)
	}

	companion object {
		val ZERO = ChunkPos(0, 0)

		fun toLong(x: Int, y: Int): Long {
			return x.toLong() or (y.toLong() shl 32)
		}

		fun longFromPosition(x: Int, y: Int): Long {
			return toLong(component(x), component(y))
		}

		fun fromPosition(input: IStruct2i): ChunkPos {
			val (x, y) = input
			return ChunkPos(component(x), component(y))
		}

		fun fromPosition(input: IStruct2d): ChunkPos {
			val (x, y) = input
			return fromPosition(x, y)
		}

		fun fromPosition(x: Int, y: Int): ChunkPos {
			return ChunkPos(component(x), component(y))
		}

		fun fromPosition(x: Int, y: Int, xWrap: Int): ChunkPos {
			return ChunkPos(circulate(component(x), xWrap), component(y))
		}

		fun fromPosition(x: Double, y: Double): ChunkPos {
			return ChunkPos(
				component(roundByAbsoluteValue(x)),
				component(roundByAbsoluteValue(y))
			)
		}

		fun fromPosition(x: Double, y: Double, xWrap: Int): ChunkPos {
			return ChunkPos(
				circulate(component(roundByAbsoluteValue(x)), xWrap),
				component(roundByAbsoluteValue(y))
			)
		}

		fun component(value: Int): Int {
			if (value < 0) {
				return -((-value) shr CHUNK_SIZE_BITS) - 1
			}

			return value shr CHUNK_SIZE_BITS
		}
	}
}