package ru.dbotthepony.kstarbound.defs.world

import com.google.common.collect.ImmutableList
import com.google.common.collect.ImmutableSet
import ru.dbotthepony.kommons.collect.filterNotNull
import ru.dbotthepony.kstarbound.Registries
import ru.dbotthepony.kstarbound.Registry
import ru.dbotthepony.kstarbound.Starbound
import ru.dbotthepony.kstarbound.collect.WeightedList
import ru.dbotthepony.kstarbound.defs.AssetReference
import ru.dbotthepony.kstarbound.defs.JsonConfigFunction
import ru.dbotthepony.kstarbound.defs.tile.TileDefinition
import ru.dbotthepony.kstarbound.json.NativeLegacy
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.json.pairListAdapter
import ru.dbotthepony.kstarbound.util.random.random
import ru.dbotthepony.kstarbound.world.positiveModulo
import java.util.random.RandomGenerator

@JsonFactory
data class BiomeDefinition(
	val airless: Boolean = false,
	val name: String,
	val statusEffects: ImmutableSet<String> = ImmutableSet.of(),
	val weather: ImmutableList<Pair<Double, ImmutableList<AssetReference<WeightedList<String>>>>> = ImmutableList.of(), // binned reference to other assets
	val hueShiftOptions: ImmutableList<Double> = ImmutableList.of(),
	val skyOptions: ImmutableList<SkyColoring> = ImmutableList.of(),
	val description: String = "...",
	val mainBlock: Registry.Ref<TileDefinition>? = null,
	val subBlocks: ImmutableList<Registry.Ref<TileDefinition>>? = null,
	val ores: Registry.Ref<JsonConfigFunction>? = null,
	val musicTrack: AmbientNoisesDefinition? = null,
	val ambientNoises: AmbientNoisesDefinition? = null,
	val surfacePlaceables: BiomePlaceablesDefinition = BiomePlaceablesDefinition(),
	val undergroundPlaceables: BiomePlaceablesDefinition = BiomePlaceablesDefinition(),
	val parallax: AssetReference<Parallax.Data>? = null,
) {
	data class CreationParams(
		val hueShift: Double,
		val random: RandomGenerator
	)

	fun skyColoring(random: RandomGenerator): SkyColoring {
		if (skyOptions.isEmpty())
			return SkyColoring.BLACK

		return skyOptions.random(random)
	}

	fun hueShift(random: RandomGenerator): Double {
		if (hueShiftOptions.isEmpty())
			return 0.0

		return hueShiftOptions.random(random)
	}

	fun create(random: RandomGenerator, verticalMidPoint: Int, threatLevel: Double): Biome {
		val hueShift = hueShift(random)
		val data = CreationParams(hueShift = hueShift, random = random)
		val surfacePlaceables = surfacePlaceables.create(data)
		val undergroundPlaceables = undergroundPlaceables.create(data)

		return Biome(
			baseName = name,
			description = description,
			hueShift = hueShift,
			materialHueShift = ((positiveModulo(hueShift, 360.0) / 360.0) * 255.0).toInt(),

			mainBlock = NativeLegacy.Tile(mainBlock?.entry),
			subBlocks = subBlocks?.stream()?.map { NativeLegacy.Tile(it.entry) }?.filterNotNull()?.collect(ImmutableList.toImmutableList()) ?: ImmutableList.of(),

			musicTrack = musicTrack,
			ambientNoises = ambientNoises,
			surfacePlaceables = surfacePlaceables,
			undergroundPlaceables = undergroundPlaceables,

			parallax = parallax?.value?.create(random, verticalMidPoint.toDouble(), hueShift, surfacePlaceables.firstTreeVariant()),

			ores = (ores?.value?.evaluate(threatLevel, oresAdapter)?.map {
				it.stream()
					.filter { it.second > 0.0 }
					.map { NativeLegacy.TileMod(Registries.tileModifiers.ref(it.first)) to it.second }
					.filter { it.first.native.isPresent }
					.collect(ImmutableList.toImmutableList())
			}?.orElse(ImmutableList.of()) ?: ImmutableList.of())
		)
	}

	companion object {
		private val oresAdapter by lazy {
			Starbound.gson.pairListAdapter<String, Double>()
		}
	}
}
