package ru.dbotthepony.kstarbound.world

import ru.dbotthepony.kommons.io.readVector2d
import ru.dbotthepony.kommons.io.readVector2f
import ru.dbotthepony.kommons.io.writeStruct2d
import ru.dbotthepony.kommons.io.writeStruct2f
import ru.dbotthepony.kommons.util.getValue
import ru.dbotthepony.kommons.util.setValue
import ru.dbotthepony.kommons.vector.Vector2d
import ru.dbotthepony.kstarbound.defs.tile.TileDamage
import ru.dbotthepony.kstarbound.defs.tile.TileDamageConfig
import ru.dbotthepony.kstarbound.defs.tile.TileDamageType
import ru.dbotthepony.kstarbound.math.Interpolator
import ru.dbotthepony.kstarbound.network.syncher.NetworkedGroup
import ru.dbotthepony.kstarbound.network.syncher.networkedBoolean
import ru.dbotthepony.kstarbound.network.syncher.networkedEnum
import ru.dbotthepony.kstarbound.network.syncher.networkedFixedPoint
import java.io.DataInputStream
import java.io.DataOutputStream

sealed class TileHealth() {
	var damageSource: Vector2d = Vector2d.ZERO
		protected set
	abstract var damagePercent: Double
		protected set
	abstract var damageEffectTimeFactor: Double
		protected set
	abstract var isHarvested: Boolean
		protected set
	abstract var damageType: TileDamageType
		protected set
	var damageEffectPercentage: Double = 0.0
		protected set

	abstract fun copy(): TileHealth

	val isHealthy: Boolean
		get() = damagePercent <= 0.0

	val isDead: Boolean
		get() = damagePercent >= 1.0 && damageType != TileDamageType.PROTECTED

	fun write(stream: DataOutputStream, isLegacy: Boolean) {
		if (isLegacy) {
			stream.writeFloat(damagePercent.toFloat())
			stream.writeFloat(damageEffectTimeFactor.toFloat())
			stream.writeBoolean(isHarvested)
			stream.writeStruct2f(damageSource.toFloatVector())
		} else {
			stream.writeDouble(damagePercent)
			stream.writeDouble(damageEffectTimeFactor)
			stream.writeBoolean(isHarvested)
			stream.writeStruct2d(damageSource)
		}

		stream.writeByte(damageType.ordinal)
	}

	fun read(stream: DataInputStream, isLegacy: Boolean) {
		if (isLegacy) {
			damagePercent = stream.readFloat().toDouble()
			damageEffectTimeFactor = stream.readFloat().toDouble()
			isHarvested = stream.readBoolean()
			damageSource = stream.readVector2f().toDoubleVector()
		} else {
			damagePercent = stream.readDouble()
			damageEffectTimeFactor = stream.readDouble()
			isHarvested = stream.readBoolean()
			damageSource = stream.readVector2d()
		}

		damageType = TileDamageType.entries[stream.readUnsignedByte()]
		damageEffectPercentage = damageEffectTimeFactor.coerceIn(0.0, 1.0) * damagePercent
	}

	fun reset() {
		isHarvested = false
		damageSource = Vector2d.ZERO
		damageType = TileDamageType.PROTECTED
		damagePercent = 0.0
		damageEffectTimeFactor = 0.0
	}

	fun damage(config: TileDamageConfig, source: Vector2d, damage: TileDamage) {
		val actualDamage = config.damageDone(damage) / config.totalHealth
		damagePercent = (damagePercent + actualDamage).coerceAtMost(1.0)
		isHarvested = damage.harvestLevel >= config.harvestLevel
		damageSource = source
		damageType = damage.type

		if (actualDamage > 0.0)
			damageEffectTimeFactor = config.maximumEffectTime

		damageEffectPercentage = damageEffectTimeFactor.coerceIn(0.0, 1.0) * damagePercent
	}

	val isTicking: Boolean
		get() = !isHealthy && !isDead

	fun tick(config: TileDamageConfig, delta: Double): Boolean {
		if (isDead || isHealthy)
			return false

		damagePercent -= config.damageRecovery * delta / config.totalHealth
		damageEffectTimeFactor -= delta

		if (damagePercent <= 0.0) {
			damagePercent = 0.0
			damageEffectTimeFactor = 0.0
			damageType = TileDamageType.PROTECTED
		}

		damageEffectPercentage = damageEffectTimeFactor.coerceIn(0.0, 1.0) * damagePercent
		return damagePercent > 0.0
	}

	class Tile() : TileHealth() {
		constructor(stream: DataInputStream, isLegacy: Boolean) : this() {
			read(stream, isLegacy)
		}

		override var damagePercent: Double = 0.0
		override var damageEffectTimeFactor: Double = 0.0
		override var isHarvested: Boolean = false
		override var damageType: TileDamageType = TileDamageType.PROTECTED

		override fun copy(): Tile {
			val copy = Tile()

			copy.isHarvested = isHarvested
			copy.damageSource = damageSource
			copy.damageType = damageType
			copy.damagePercent = damagePercent
			copy.damageEffectTimeFactor = damageEffectTimeFactor
			copy.damageEffectPercentage = damageEffectPercentage

			return copy
		}
	}

	class TileEntity() : TileHealth() {
		constructor(stream: DataInputStream, isLegacy: Boolean) : this() {
			read(stream, isLegacy)
		}

		val networkGroup = NetworkedGroup()

		override var damagePercent: Double by networkedFixedPoint(0.001).also { networkGroup.add(it); it.interpolator = Interpolator.Linear }
		override var damageEffectTimeFactor: Double by networkedFixedPoint(0.001).also { networkGroup.add(it); it.interpolator = Interpolator.Linear }
		override var isHarvested: Boolean by networkedBoolean().also { networkGroup.add(it) }
		override var damageType: TileDamageType by networkedEnum(TileDamageType.PROTECTED).also { networkGroup.add(it) }

		override fun copy(): TileEntity {
			val copy = TileEntity()

			copy.isHarvested = isHarvested
			copy.damageSource = damageSource
			copy.damageType = damageType
			copy.damagePercent = damagePercent
			copy.damageEffectTimeFactor = damageEffectTimeFactor
			copy.damageEffectPercentage = damageEffectPercentage

			return copy
		}
	}
}
