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.vector.Vector2d
import ru.dbotthepony.kstarbound.Globals
import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.defs.dungeon.DungeonDefinition
import ru.dbotthepony.kstarbound.io.readColor
import ru.dbotthepony.kstarbound.io.readInternedString
import ru.dbotthepony.kstarbound.io.readNullableString
import ru.dbotthepony.kstarbound.io.writeColor
import ru.dbotthepony.kstarbound.io.writeNullableString
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
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 FloatingDungeonWorldParameters : VisitableWorldParameters() {
	var dungeonBaseHeight: Int by Delegates.notNull()
		private set
	var dungeonSurfaceHeight: Int by Delegates.notNull()
		private set
	var dungeonUndergroundLevel: Int by Delegates.notNull()
		private set
	var primaryDungeon: Registry.Ref<DungeonDefinition> by Delegates.notNull()
		private set
	var ambientLightLevel: RGBAColor by Delegates.notNull()
		private set
	var biome: String? = null
		private set
	var dayMusicTrack: String? = null
		private set
	var nightMusicTrack: String? = null
		private set
	var dayAmbientNoises: String? = null
		private set
	var nightAmbientNoises: String? = null
		private set

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

	override fun createLayout(random: RandomGenerator): WorldLayout {
		val layout = WorldLayout()
		layout.worldSize = worldSize

		layout.addLayer(random, 0, WorldLayout.RegionParameters(
			dungeonSurfaceHeight, threatLevel, biome,
			null, null, null, null, null, null,
			WorldLayout.RegionLiquids()
		))

		val color = if (biome != null) {
			// TODO: Original game engine queries biome database but forgets to finalize world layout if biome is present
			// TODO: Is that intentional?
			// (: god damn it.

			Registries.biomes[biome!!]?.value?.skyColoring(random)?.mainColor ?: RGBAColor.BLACK
		} else {
			RGBAColor.BLACK
		}

		layout.finalize(color)
		return layout
	}

	@JsonFactory
	data class JsonData(
		val dungeonBaseHeight: Int,
		val dungeonSurfaceHeight: Int,
		val dungeonUndergroundLevel: Int,
		val primaryDungeon: Registry.Ref<DungeonDefinition>,
		val biome: String? = null,
		val ambientLightLevel: RGBAColor,
		val dayMusicTrack: String? = null,
		val nightMusicTrack: String? = null,
		val dayAmbientNoises: String? = null,
		val nightAmbientNoises: String? = null,
	)

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

		val read = Starbound.gson.fromJson(data, JsonData::class.java)

		dungeonBaseHeight = read.dungeonBaseHeight
		dungeonSurfaceHeight = read.dungeonSurfaceHeight
		dungeonUndergroundLevel = read.dungeonUndergroundLevel
		primaryDungeon = read.primaryDungeon
		biome = read.biome
		ambientLightLevel = read.ambientLightLevel
		dayMusicTrack = read.dayMusicTrack
		nightMusicTrack = read.nightMusicTrack
		dayAmbientNoises = read.dayAmbientNoises
		nightAmbientNoises = read.nightAmbientNoises
	}

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

		val serialize = Starbound.gson.toJsonTree(JsonData(
			dungeonBaseHeight, dungeonSurfaceHeight, dungeonUndergroundLevel, primaryDungeon, biome, ambientLightLevel, dayMusicTrack, nightMusicTrack, dayAmbientNoises, nightAmbientNoises
		)) as JsonObject

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

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

		dungeonBaseHeight = stream.readInt()
		dungeonSurfaceHeight = stream.readInt()
		dungeonUndergroundLevel = stream.readInt()
		primaryDungeon = Registries.dungeons.ref(stream.readInternedString())
		biome = stream.readNullableString()
		ambientLightLevel = stream.readColor()
		dayMusicTrack = stream.readNullableString()
		nightMusicTrack = stream.readNullableString()
		dayAmbientNoises = stream.readNullableString()
		nightAmbientNoises = stream.readNullableString()
	}

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

		stream.writeInt(dungeonBaseHeight)
		stream.writeInt(dungeonSurfaceHeight)
		stream.writeInt(dungeonUndergroundLevel)
		stream.writeBinaryString(primaryDungeon.key.left())
		stream.writeNullableString(biome)
		stream.writeColor(ambientLightLevel)
		stream.writeNullableString(dayMusicTrack)
		stream.writeNullableString(nightMusicTrack)
		stream.writeNullableString(dayAmbientNoises)
		stream.writeNullableString(nightAmbientNoises)
	}

	companion object {
		fun generate(typeName: String): FloatingDungeonWorldParameters {
			val config = Globals.dungeonWorlds[typeName] ?: throw NoSuchElementException("Unknown dungeon world type $typeName!")
			val parameters = FloatingDungeonWorldParameters()

			parameters.typeName = typeName
			parameters.worldSize = config.worldSize
			parameters.threatLevel = config.threatLevel
			parameters.gravity = config.gravity.map({ Vector2d(y = it) }, { it })
			parameters.airless = config.airless
			parameters.environmentStatusEffects = config.environmentStatusEffects

			parameters.overrideTech = config.overrideTech
			parameters.globalDirectives = config.globalDirectives
			parameters.beamUpRule = config.beamUpRule
			parameters.disableDeathDrops = config.disableDeathDrops
			parameters.worldEdgeForceRegions = config.worldEdgeForceRegions
			parameters.weatherPool = config.weatherPool
			parameters.dungeonBaseHeight = config.dungeonBaseHeight
			parameters.dungeonSurfaceHeight = config.dungeonSurfaceHeight
			parameters.dungeonUndergroundLevel = config.dungeonUndergroundLevel
			parameters.primaryDungeon = config.primaryDungeon
			parameters.biome = config.biome
			parameters.ambientLightLevel = config.ambientLightLevel

			if (config.musicTrack != null) {
				parameters.dayMusicTrack = config.musicTrack.fullPath
				parameters.nightMusicTrack = config.musicTrack.fullPath
			} else {
				parameters.dayMusicTrack = config.dayMusicTrack?.fullPath
				parameters.nightMusicTrack = config.nightMusicTrack?.fullPath
			}

			if (config.ambientNoises != null) {
				parameters.dayAmbientNoises = config.ambientNoises.fullPath
				parameters.nightAmbientNoises = config.ambientNoises.fullPath
			} else {
				parameters.dayAmbientNoises = config.dayAmbientNoises?.fullPath
				parameters.nightAmbientNoises = config.nightAmbientNoises?.fullPath
			}

			return parameters
		}
	}
}
