package ru.dbotthepony.kstarbound.world.entities

import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.kommons.util.AABB
import ru.dbotthepony.kstarbound.client.StarboundClient
import ru.dbotthepony.kstarbound.client.render.LayeredRenderer
import ru.dbotthepony.kstarbound.client.render.RenderLayer
import ru.dbotthepony.kstarbound.world.EntityIndex
import ru.dbotthepony.kstarbound.world.World

/**
 * Entities with dynamics (Player, Drops, Projectiles, NPCs, etc)
 */
abstract class DynamicEntity() : AbstractEntity() {
	private var forceChunkRepos = false

	override var position
		get() = movement.position
		set(value) {
			movement.position = value
		}

	abstract val movement: MovementController

	override val collisionArea: AABB
		get() = movement.computeCollisionAABB()

	override fun onNetworkUpdate() {
		super.onNetworkUpdate()
		movement.updateFixtures()
	}

	private var fixturesChangeset = -1

	override fun tick(delta: Double) {
		super.tick(delta)

		if (isRemote && networkGroup.upstream.isInterpolating) {
			movement.updateFixtures()
		}

		if (fixturesChangeset != movement.fixturesChangeset) {
			fixturesChangeset = movement.fixturesChangeset
			metaFixture!!.move(metaBoundingBox)
		}
	}

	protected var metaFixture: EntityIndex.Entry.Fixture? = null
		private set

	override fun onJoinWorld(world: World<*, *>) {
		super.onJoinWorld(world)
		world.dynamicEntities.add(this)
		movement.initialize(world, spatialEntry)
		forceChunkRepos = true
		metaFixture = spatialEntry!!.Fixture()
	}

	override fun onRemove(world: World<*, *>, reason: RemovalReason) {
		super.onRemove(world, reason)
		world.dynamicEntities.remove(this)
		movement.remove()
		metaFixture?.remove()
		metaFixture = null
	}

	override fun render(client: StarboundClient, layers: LayeredRenderer) {
		layers.add(RenderLayer.Overlay.point()) {
			val hitboxes = movement.computeLocalHitboxes()
			if (hitboxes.isEmpty()) return@add

			hitboxes.forEach { it.render(client) }

			world.queryTileCollisions(
				hitboxes.stream().map { it.aabb }.reduce(AABB::combine).get().enlarge(2.0, 2.0)
			).filter(movement::shouldCollideWithBody).forEach { it.poly.render(client, BLOCK_COLLISION_COLOR) }
		}
	}

	companion object {
		val BLOCK_COLLISION_COLOR = RGBAColor(65, 179, 217)
	}
}
