package ru.dbotthepony.kstarbound.defs

import com.google.gson.reflect.TypeToken
import ru.dbotthepony.kstarbound.io.readDouble
import ru.dbotthepony.kstarbound.io.readNullableDouble
import ru.dbotthepony.kstarbound.io.readVector2d
import ru.dbotthepony.kstarbound.io.writeDouble
import ru.dbotthepony.kstarbound.io.writeNullableDouble
import ru.dbotthepony.kstarbound.io.writeStruct2d
import ru.dbotthepony.kstarbound.json.builder.DispatchingAdapter
import ru.dbotthepony.kstarbound.json.builder.IStringSerializable
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.json.builder.JsonFlat
import ru.dbotthepony.kstarbound.math.Line2d
import ru.dbotthepony.kstarbound.math.vector.Vector2d
import ru.dbotthepony.kstarbound.network.syncher.legacyCodec
import ru.dbotthepony.kstarbound.network.syncher.nativeCodec
import ru.dbotthepony.kstarbound.world.physics.Poly
import java.io.DataInputStream
import java.io.DataOutputStream

sealed class PhysicsForceRegion {
	// ephemeral property from json
	abstract val enabled: Boolean
	abstract val filter: PhysicsCategoryFilter
	abstract fun write(stream: DataOutputStream, isLegacy: Boolean)

	abstract val type: Type

	enum class Type(override val jsonName: String, val token: TypeToken<out PhysicsForceRegion>) : IStringSerializable {
		DIRECTIONAL("DirectionalForceRegion", TypeToken.get(Directional::class.java)),
		RADIAL("RadialForceRegion", TypeToken.get(Radial::class.java)),
		GRADIENT("GradientForceRegion", TypeToken.get(Gradient::class.java))
	}

	@JsonFactory
	data class Directional(
		val region: Poly,
		val xTargetVelocity: Double?,
		val yTargetVelocity: Double?,
		val controlForce: Double,
		@JsonFlat
		override val filter: PhysicsCategoryFilter,
	    override val enabled: Boolean = true
	) : PhysicsForceRegion() {
		constructor(stream: DataInputStream, isLegacy: Boolean) : this(
			Poly.read(stream, isLegacy),
			stream.readNullableDouble(isLegacy),
			stream.readNullableDouble(isLegacy),
			stream.readDouble(isLegacy),
			PhysicsCategoryFilter(stream, isLegacy)
		)

		override val type: Type
			get() = Type.DIRECTIONAL

		override fun write(stream: DataOutputStream, isLegacy: Boolean) {
			stream.writeByte(0)
			region.write(stream, isLegacy)
			stream.writeNullableDouble(xTargetVelocity, isLegacy)
			stream.writeNullableDouble(yTargetVelocity, isLegacy)
			stream.writeDouble(controlForce, isLegacy)
			filter.write(stream, isLegacy)
		}
	}

	@JsonFactory
	data class Radial(
		val center: Vector2d = Vector2d.ZERO,
		val outerRadius: Double,
		val innerRadius: Double,
		val targetRadialVelocity: Double,
		val controlForce: Double,
		@JsonFlat
		override val filter: PhysicsCategoryFilter,
		override val enabled: Boolean = true
	) : PhysicsForceRegion() {
		constructor(stream: DataInputStream, isLegacy: Boolean) : this(
			stream.readVector2d(isLegacy),
			stream.readDouble(isLegacy),
			stream.readDouble(isLegacy),
			stream.readDouble(isLegacy),
			stream.readDouble(isLegacy),
			PhysicsCategoryFilter(stream, isLegacy)
		)

		override val type: Type
			get() = Type.RADIAL

		override fun write(stream: DataOutputStream, isLegacy: Boolean) {
			stream.writeByte(1)
			stream.writeStruct2d(center, isLegacy)
			stream.writeDouble(outerRadius, isLegacy)
			stream.writeDouble(innerRadius, isLegacy)
			stream.writeDouble(targetRadialVelocity, isLegacy)
			stream.writeDouble(controlForce, isLegacy)
			filter.write(stream, isLegacy)
		}
	}

	@JsonFactory
	data class Gradient(
		val region: Poly,
		val gradient: Line2d,
		val baseTargetVelocity: Double,
		val baseControlForce: Double,
		@JsonFlat
		override val filter: PhysicsCategoryFilter,
		override val enabled: Boolean = true
	) : PhysicsForceRegion() {
		constructor(stream: DataInputStream, isLegacy: Boolean) : this(
			Poly.read(stream, isLegacy),
			Line2d(stream, isLegacy),
			stream.readDouble(isLegacy),
			stream.readDouble(isLegacy),
			PhysicsCategoryFilter(stream, isLegacy)
		)

		override val type: Type
			get() = Type.GRADIENT

		override fun write(stream: DataOutputStream, isLegacy: Boolean) {
			stream.writeByte(2)
			region.write(stream, isLegacy)
			gradient.write(stream, isLegacy)
			stream.writeDouble(baseTargetVelocity, isLegacy)
			stream.writeDouble(baseControlForce, isLegacy)
			filter.write(stream, isLegacy)
		}
	}

	companion object {
		val CODEC = nativeCodec(::read, PhysicsForceRegion::write)
		val LEGACY_CODEC = legacyCodec(::read, PhysicsForceRegion::write)

		val ADAPTER = DispatchingAdapter("type", { type }, { token }, Type.entries)

		fun read(stream: DataInputStream, isLegacy: Boolean): PhysicsForceRegion {
			return when (val type = stream.readUnsignedByte()) {
				0 -> Directional(stream, isLegacy)
				1 -> Radial(stream, isLegacy)
				2 -> Gradient(stream, isLegacy)
				else -> throw IllegalArgumentException("Unknown force region type $type!")
			}
		}
	}
}
