package ru.dbotthepony.kstarbound.defs.world

import com.google.gson.JsonObject
import ru.dbotthepony.kommons.gson.set
import ru.dbotthepony.kommons.io.writeBinaryString
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.kommons.util.AABBi
import ru.dbotthepony.kommons.vector.Vector2d
import ru.dbotthepony.kommons.vector.Vector2i
import ru.dbotthepony.kstarbound.Globals
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.io.readColor
import ru.dbotthepony.kstarbound.io.readInternedString
import ru.dbotthepony.kstarbound.io.writeColor
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.util.random.nextRange
import ru.dbotthepony.kstarbound.util.random.random
import java.io.DataInputStream
import java.io.DataOutputStream
import java.util.random.RandomGenerator
import kotlin.properties.Delegates

class AsteroidsWorldParameters : VisitableWorldParameters() {
	var asteroidTopLevel: Int = 0
		private set
	var asteroidBottomLevel: Int = 0
		private set
	var blendSize: Double = 0.0
		private set
	var asteroidBiome: String by Delegates.notNull()
		private set
	var ambientLightLevel: RGBAColor by Delegates.notNull()
		private set

	init {
		airless = true
	}

	override val type: VisitableWorldParametersType
		get() = VisitableWorldParametersType.ASTEROIDS

	@JsonFactory
	data class StoreData(
		val asteroidTopLevel: Int = 0,
		val asteroidBottomLevel: Int = 0,
		val blendSize: Double = 0.0,
		val asteroidBiome: String,
		val ambientLightLevel: RGBAColor,
	)

	override fun fromJson(data: JsonObject) {
		super.fromJson(data)

		val store = Starbound.gson.fromJson(data, StoreData::class.java)
		asteroidTopLevel = store.asteroidTopLevel
		asteroidBottomLevel = store.asteroidBottomLevel
		blendSize = store.blendSize
		asteroidBiome = store.asteroidBiome
		ambientLightLevel = store.ambientLightLevel
	}

	override fun toJson(data: JsonObject, isLegacy: Boolean) {
		super.toJson(data, isLegacy)

		val store = Starbound.gson.toJsonTree(StoreData(
			asteroidTopLevel = asteroidTopLevel,
			asteroidBottomLevel = asteroidBottomLevel,
			blendSize = blendSize,
			asteroidBiome = asteroidBiome,
			ambientLightLevel = ambientLightLevel,
		)) as JsonObject

		for ((k, v) in store.entrySet())
			data[k] = v
	}

	override fun read0(stream: DataInputStream) {
		super.read0(stream)

		asteroidTopLevel = stream.readInt()
		asteroidBottomLevel = stream.readInt()
		blendSize = stream.readFloat().toDouble()
		asteroidBiome = stream.readInternedString()
		ambientLightLevel = stream.readColor()
	}

	override fun write0(stream: DataOutputStream) {
		super.write0(stream)

		stream.writeInt(asteroidTopLevel)
		stream.writeInt(asteroidBottomLevel)
		stream.writeFloat(blendSize.toFloat())
		stream.writeBinaryString(asteroidBiome)
		stream.writeColor(ambientLightLevel)
	}

	override fun createLayout(random: RandomGenerator): WorldLayout {
		val terrain = Globals.asteroidWorlds.terrains.random(random)

		val layout = WorldLayout()
		layout.worldSize = worldSize

		val asteroidRegion = WorldLayout.RegionParameters(
			worldSize.y / 2,
			threatLevel,
			asteroidBiome,
			terrain.terrainSelector,
			terrain.caveSelector,
			terrain.bgCaveSelector,
			terrain.oreSelector,
			terrain.oreSelector,
			terrain.subBlockSelector,
			WorldLayout.RegionLiquids(),
		)

		val emptyRegion = WorldLayout.RegionParameters(
			worldSize.y / 2,
			threatLevel,
			asteroidBiome,
			Globals.asteroidWorlds.emptyTerrain.terrainSelector,
			Globals.asteroidWorlds.emptyTerrain.caveSelector,
			Globals.asteroidWorlds.emptyTerrain.bgCaveSelector,
			Globals.asteroidWorlds.emptyTerrain.oreSelector,
			Globals.asteroidWorlds.emptyTerrain.oreSelector,
			Globals.asteroidWorlds.emptyTerrain.subBlockSelector,
			WorldLayout.RegionLiquids(),
		)

		layout.addLayer(random, 0, emptyRegion)
		layout.addLayer(random, asteroidBottomLevel, asteroidRegion)
		layout.addLayer(random, asteroidTopLevel, emptyRegion)

		layout.regionBlending = blendSize
		layout.blockNoise = Globals.asteroidWorlds.blockNoise?.build(random)

		layout.playerStartSearchRegions.add(
			AABBi(
				Vector2i(0, asteroidBottomLevel),
				Vector2i(worldSize.x, asteroidTopLevel),
			)
		)

		layout.finalize(RGBAColor.BLACK)
		return layout
	}

	companion object {
		fun generate(random: RandomGenerator): AsteroidsWorldParameters {
			val parameters = AsteroidsWorldParameters()

			parameters.threatLevel = random.nextRange(Globals.asteroidWorlds.threatRange)
			parameters.typeName = "asteroids"
			parameters.worldSize = Globals.asteroidWorlds.worldSize
			parameters.gravity = Vector2d(y = random.nextRange(Globals.asteroidWorlds.gravityRange))
			parameters.environmentStatusEffects = Globals.asteroidWorlds.environmentStatusEffects
			parameters.overrideTech = Globals.asteroidWorlds.overrideTech
			parameters.globalDirectives = Globals.asteroidWorlds.globalDirectives
			parameters.beamUpRule = Globals.asteroidWorlds.beamUpRule
			parameters.disableDeathDrops = Globals.asteroidWorlds.disableDeathDrops
			parameters.worldEdgeForceRegions = Globals.asteroidWorlds.worldEdgeForceRegions
			parameters.asteroidTopLevel = Globals.asteroidWorlds.asteroidsTop
			parameters.asteroidBottomLevel = Globals.asteroidWorlds.asteroidsBottom
			parameters.blendSize = Globals.asteroidWorlds.blendSize
			parameters.ambientLightLevel = Globals.asteroidWorlds.ambientLightLevel
			parameters.asteroidBiome = Globals.asteroidWorlds.biome

			return parameters
		}

		fun generate(seed: Long): AsteroidsWorldParameters {
			return generate(random(seed))
		}
	}
}
