package ru.dbotthepony.kstarbound.defs.animation

import com.google.gson.Gson
import com.google.gson.JsonNull
import com.google.gson.JsonObject
import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import ru.dbotthepony.kommons.gson.consumeNull
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.kommons.vector.Vector2d
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.AssetPath
import ru.dbotthepony.kstarbound.fromJson
import java.util.random.RandomGenerator
import kotlin.math.PI

// this struct is a complete mess
// mostly, because it represents all possible particle types
// and, of course, used directly when working with particles
// (instancing happens just by copying this structure)
data class Particle(
	var type: ParticleConfig.Type = ParticleConfig.Type.VARIANCE,
	// Defaults to 1.0, 1.0 will produce a reasonable size particle for whatever
	// the type is.
	var size: Double = 0.0,
	var baseSize: Double = 0.0, // track the original size for shrink destruction action
	var color: RGBAColor = RGBAColor.WHITE,
	var light: RGBAColor = RGBAColor.TRANSPARENT_BLACK,
	// Used differently depending on the type of the particle.
	var string: String = "",
	var fade: Double = 0.0,
	var fullbright: Boolean = false,
	var position: Vector2d = Vector2d.ZERO,
	var velocity: Vector2d = Vector2d.ZERO,
	var finalVelocity: Vector2d = Vector2d.ZERO,
	var approach: Vector2d = Vector2d.ZERO,
	var rotation: Double = 0.0,
	var angularVelocity: Double = 0.0,
	var timeToLive: Double = 0.0,
	var layer: ParticleConfig.Layer = ParticleConfig.Layer.MIDDLE,
	var collidesForeground: Boolean = true,
	var collidesLiquid: Boolean = true,
	var underwaterOnly: Boolean = false,
	var ignoreWind: Boolean = true,
	var length: Double = 0.0,
	var destructionAction: ParticleConfig.DestructionAction = ParticleConfig.DestructionAction.NONE,
	var destructionTime: Double = 0.0,
	var trail: Boolean = false,
	var flippable: Boolean = true,
	var flip: Boolean = false,
	var directives: String = "",
	var destructionImage: String = "",
) {
	constructor(data: ParticleConfig.JsonData) : this() {
		type = data.type

		if (type == ParticleConfig.Type.VARIANCE) {
			size = 0.0
			color = RGBAColor.TRANSPARENT_BLACK
		} else {
			size = 1.0
			color = RGBAColor.WHITE
		}

		size = data.size ?: size
		baseSize = size

		string = data.image?.fullPath ?: data.text ?: data.animation?.fullPath ?: data.string ?: ""

		if (data.color != null)
			color = data.color

		light = data.light
		fade = data.fade
		fullbright = data.fullbright
		position = data.position
		velocity = data.velocity ?: data.initialVelocity ?: Vector2d.ZERO

		if (type == ParticleConfig.Type.VARIANCE)
			finalVelocity = Vector2d.ZERO
		else
			finalVelocity = velocity

		finalVelocity = data.finalVelocity ?: finalVelocity
		approach = data.approach

		flip = data.flip
		flippable = data.flippable
		rotation = data.rotation * PI / 180.0
		angularVelocity = data.angularVelocity * PI / 180.0
		length = data.length
		destructionAction = data.destructionAction

		if (destructionAction == ParticleConfig.DestructionAction.IMAGE)
			destructionImage = data.destructionImage?.fullPath ?: ""
		else
			destructionImage = data.destructionImage?.path ?: ""

		destructionTime = data.destructionTime
		timeToLive = data.timeToLive
		layer = data.layer
		underwaterOnly = data.underwaterOnly
		collidesForeground = data.collidesForeground ?: (layer != ParticleConfig.Layer.FRONT || underwaterOnly)
		collidesLiquid = data.collidesLiquid ?: (type == ParticleConfig.Type.EMBER)
		ignoreWind = data.ignoreWind
		trail = data.trail
	}

	constructor(data: JsonObject) : this(Starbound.gson.fromJson(data, ParticleConfig.JsonData::class.java))

	fun toJson(): JsonObject {
		val json = Starbound.gson.toJsonTree(toJsonData()) as JsonObject

		for (k in json.keySet().filter { json[it] == JsonNull.INSTANCE }) {
			json.remove(k)
		}

		return json
	}

	fun toJsonData(): ParticleConfig.JsonData {
		return ParticleConfig.JsonData(
			type = type,
			size = size,
			string = string,
			color = color,
			light = light,
			fade = fade,
			fullbright = fullbright,
			position = position,
			velocity = velocity,
			finalVelocity = finalVelocity,
			approach = approach,
			flip = flip,
			flippable = flippable,
			rotation = rotation,
			angularVelocity = angularVelocity,
			length = length,
			destructionAction = destructionAction,
			destructionImage = AssetPath(destructionImage),
			destructionTime = destructionTime,
			timeToLive = timeToLive,
			layer = layer,
			underwaterOnly = underwaterOnly,
			collidesForeground = collidesForeground,
			collidesLiquid = collidesLiquid,
			ignoreWind = ignoreWind,
			trail = trail,
		)
	}

	fun applyVariance(variance: Particle, random: RandomGenerator): Particle {
		size += variance.size * random.nextDouble(-1.0, 1.0)
		position += Vector2d(variance.position.x * random.nextDouble(-1.0, 1.0), variance.position.y * random.nextDouble(-1.0, 1.0))
		velocity += Vector2d(variance.velocity.x * random.nextDouble(-1.0, 1.0), variance.velocity.y * random.nextDouble(-1.0, 1.0))
		finalVelocity += Vector2d(variance.finalVelocity.x * random.nextDouble(-1.0, 1.0), variance.finalVelocity.y * random.nextDouble(-1.0, 1.0))
		rotation += variance.rotation * random.nextDouble(-1.0, 1.0)
		angularVelocity += variance.angularVelocity * random.nextDouble(-1.0, 1.0)
		length += variance.length * random.nextDouble(-1.0, 1.0)
		timeToLive += variance.timeToLive * random.nextDouble(-1.0, 1.0)
		return this
	}

	fun initializeAnimation() {

	}

	class Adapter(gson: Gson) : TypeAdapter<Particle>() {
		private val data = gson.getAdapter(ParticleConfig.JsonData::class.java)

		override fun write(out: JsonWriter, value: Particle?) {
			if (value == null)
				out.nullValue()
			else
				data.write(out, value.toJsonData())
		}

		override fun read(`in`: JsonReader): Particle? {
			if (`in`.consumeNull())
				return null

			return Particle(data.read(`in`))
		}
	}
}
