package ru.dbotthepony.kstarbound.defs.world

import com.google.common.collect.ImmutableList
import ru.dbotthepony.kommons.collect.filterNotNull
import ru.dbotthepony.kommons.gson.get
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.kommons.util.Either
import ru.dbotthepony.kommons.vector.Vector2d
import ru.dbotthepony.kstarbound.defs.image.Image
import ru.dbotthepony.kstarbound.json.builder.JsonFactory
import ru.dbotthepony.kstarbound.util.Directives
import ru.dbotthepony.kstarbound.util.random.nextRange
import java.util.random.RandomGenerator

@JsonFactory
class Parallax(
	// ah yes storing useless data once again
	val seed: Long = 0L,
	val verticalOrigin: Double,
	val hueShift: Double,
	val imageDirectory: String,
	val parallaxTreeVariant: TreeVariant?,
	val layers: ImmutableList<Layer> = ImmutableList.of(),
) {
	@JsonFactory
	data class Data(
		val verticalOrigin: Double = 0.0,
		val layers: ImmutableList<DataLayer>,
	) {
		fun create(random: RandomGenerator, verticalOrigin: Double, hueShift: Double, treeVariant: TreeVariant?): Parallax {
			return Parallax(
				seed = random.nextLong(),
				verticalOrigin = verticalOrigin + this.verticalOrigin,
				hueShift = hueShift,
				imageDirectory = "/parallax/images/",
				parallaxTreeVariant = treeVariant,
				layers = layers.stream()
					.filter { it.frequency >= random.nextDouble() }
					.map { it.create(random, treeVariant, verticalOrigin + this.verticalOrigin, hueShift) }
					.filterNotNull()
					.collect(ImmutableList.toImmutableList())
			)
		}
	}

	fun fadeToSkyColor(color: RGBAColor) {
		layers.forEach { it.fadeToSkyColor(color) }
	}

	@JsonFactory
	data class DataLayer(
		val kind: String,
		val frequency: Double = 1.0,
		val baseCount: Int = 1,
		val modCount: Int = 0,
		val parallax: Either<Double, Vector2d>,
		val repeatY: Boolean = false,
		val repeatX: Boolean = true,
		val tileLimitTop: Double? = null,
		val tileLimitBottom: Double? = null,
		val offset: Vector2d = Vector2d.ZERO, // shift from bottom left to horizon level in the image
		val noRandomOffset: Boolean = false,
		val timeOfDayCorrelation: String = "",
		val minSpeed: Double = 0.0,
		val maxSpeed: Double = 0.0,
		val unlit: Boolean = false,
		val lightMapped: Boolean = false,
		val directives: String = "",
		val fadePercent: Double = 0.0,
		val nohueshift: Boolean = false,
	) {
		init {
			require(baseCount >= 1) { "non positive baseCount: $baseCount" }
		}

		fun create(random: RandomGenerator, treeVariant: TreeVariant?, verticalOrigin: Double, hueShift: Double): Layer? {
			val isFoliage = kind.startsWith("foliage/")
			val isStem = kind.startsWith("stem/")

			var texPath = "/parallax/images/$kind/"

			if (isFoliage) {
				if (treeVariant == null) return null
				if (!treeVariant.foliageSettings.asJsonObject.get("parallaxFoliage", false)) return null
				texPath = "${treeVariant.foliageDirectory}/parallax/${kind.replace("foliage/", "")}/"
			} else if (isStem) {
				if (treeVariant == null) return null
				texPath = "${treeVariant.stemDirectory}/parallax/${kind.replace("stem/", "")}/"
			}

			val textures = ArrayList<String>()
			val base = if (baseCount <= 1) 1 else random.nextInt(baseCount - 1) + 1
			textures.add("${texPath}base/$base.png")

			val modCount = if (modCount == 0) 0 else random.nextInt(modCount) + 1
			if (modCount != 0)
				textures.add("${texPath}mod/${modCount + 1}.png")

			var directives = directives

			if (isFoliage)
				directives += "?hueshift=${treeVariant!!.foliageHueShift}"
			else if (isStem)
				directives += "?hueshift=${treeVariant!!.stemHueShift}"
			else if (!nohueshift)
				directives += "?hueshift=$hueShift"

			var offset = offset

			if (!noRandomOffset) {
				val width = Image.get(textures.first())?.width ?: 0

				if (width > 0) {
					offset += Vector2d(x = random.nextInt(width).toDouble())
				}
			}

			return Layer(
				parallaxValue = parallax.map({ Vector2d(it, it) }, { it }),
				repeat = (if (repeatX) 1 else 0) to (if (repeatY) 1 else 0),
				tileLimitTop = tileLimitTop,
				tileLimitBottom = tileLimitBottom,
				verticalOrigin = verticalOrigin,
				zLevel = parallax.map({ it * 2.0 }, { it.x + it.y }),
				parallaxOffset = offset,
				timeOfDayCorrelation = timeOfDayCorrelation,
				speed = random.nextRange(Vector2d(minSpeed, maxSpeed)),
				unlit = unlit,
				lightMapped = lightMapped,
				fadePercent = fadePercent,
				alpha = 1.0,
				directives = Directives(directives),
				textures = ImmutableList.copyOf(textures),
			)
		}
	}

	@JsonFactory
	data class Layer(
		var directives: Directives,
		val textures: ImmutableList<String>,
		val alpha: Double = 1.0,
		val parallaxValue: Vector2d,
		// treat as booleans because original engine stuff.
		val repeat: Pair<Int, Int>,
		val tileLimitTop: Double? = null,
		val tileLimitBottom: Double? = null,
		val verticalOrigin: Double,
		val zLevel: Double,
		val parallaxOffset: Vector2d,
		val timeOfDayCorrelation: String,
		val speed: Double,
		val unlit: Boolean,
		val lightMapped: Boolean,
		val fadePercent: Double,
	) {
		fun fadeToSkyColor(color: RGBAColor) {
			if (fadePercent > 0.0) {
				directives = directives.add("fade", color.toHexStringRGB().substring(1) + "=$fadePercent")
			}
		}
	}
}
