package ru.dbotthepony.kstarbound.world

import ru.dbotthepony.kommons.util.IStruct2i
import ru.dbotthepony.kommons.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
}

/**
 * Coordinate, representing direct positions of chunks in [World.ChunkMap], with some
 * helper methods and properties
 */
data class ChunkPos(val x: Int, val y: Int) : IStruct2i, Comparable<ChunkPos> {
	constructor(pos: IStruct2i) : this(pos.component1(), pos.component2())
	constructor(pos: Long) : this(pos.toInt(), (pos ushr 32).toInt())

	val tileX = x shl CHUNK_SIZE_BITS
	val tileY = y shl CHUNK_SIZE_BITS
	val tile = Vector2i(tileX, tileY)

	operator fun plus(other: ChunkPos): ChunkPos {
		return ChunkPos(x + other.x, y + other.y)
	}

	fun tile(x: Int, y: Int): Vector2i {
		return Vector2i(tileX + x, tileY + y)
	}

	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)
	}

	fun neighbours(): List<ChunkPos> {
		return listOf(top, bottom, left, right, topLeft, topRight, bottomLeft, bottomRight)
	}

	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)
		}
	}
}