package ru.dbotthepony.mc.otm.data.world

import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.BlockPos
import net.minecraft.util.RandomSource
import net.minecraft.util.valueproviders.FloatProvider
import net.minecraft.util.valueproviders.IntProvider
import net.minecraft.world.level.levelgen.placement.PlacementContext
import net.minecraft.world.level.levelgen.placement.PlacementModifier
import net.minecraft.world.level.levelgen.placement.PlacementModifierType
import ru.dbotthepony.mc.otm.core.math.component1
import ru.dbotthepony.mc.otm.core.math.component2
import ru.dbotthepony.mc.otm.core.math.component3
import ru.dbotthepony.mc.otm.core.math.minus
import ru.dbotthepony.mc.otm.core.math.plus
import ru.dbotthepony.mc.otm.registry.data.MPlacementModifiers
import java.util.stream.Stream
import kotlin.math.PI
import kotlin.math.roundToInt

// aka "cloud placement"
data class EllipsoidPlacement(
	val x: IntProvider,
	val z: IntProvider,
	val y: IntProvider,
	val count: IntProvider,
	val xLength: FloatProvider,
	val zLength: FloatProvider,
	val yLength: FloatProvider,
) : PlacementModifier() {
	init {
		require(xLength.minValue >= 1f) { "Bad ellipsoid x minimal size: $xLength" }
		require(zLength.minValue >= 1f) { "Bad ellipsoid z minimal size: $zLength" }
		require(yLength.minValue >= 1f) { "Bad ellipsoid y minimal size: $yLength" }
	}

	override fun getPositions(
		context: PlacementContext,
		random: RandomSource,
		position: BlockPos
	): Stream<BlockPos> {
		var count = count.sample(random)

		if (count <= 0) {
			return Stream.empty()
		}

		val xLength = xLength.sample(random)
		val zLength = zLength.sample(random)
		val yLength = yLength.sample(random)

		val xPow = xLength * xLength
		val zPow = zLength * zLength
		val yPow = yLength * yLength

		count = minOf(count, (xLength * zLength * yLength * PI * (4.0 / 3.0)).roundToInt())
		count = 600

		return Stream.generate { position + BlockPos(this.x.sample(random), this.y.sample(random), this.z.sample(random)) }
			.limit(count * 10L) // failsafe
			.filter {
				val (ellipsoidX, ellipsoidY, ellipsoidZ) = it - position

				(ellipsoidX * ellipsoidX) / xPow +
				(ellipsoidY * ellipsoidY) / yPow +
				(ellipsoidZ * ellipsoidZ) / zPow <= 1.0f
			}
			.distinct()
			.limit(count.toLong())
	}

	override fun type(): PlacementModifierType<*> {
		return MPlacementModifiers.ELLIPSOID_PLACEMENT
	}

	companion object {
		val CODEC: MapCodec<EllipsoidPlacement> by lazy {
			RecordCodecBuilder.mapCodec {
				it.group(
					IntProvider.CODEC.fieldOf("x").forGetter(EllipsoidPlacement::x),
					IntProvider.CODEC.fieldOf("y").forGetter(EllipsoidPlacement::y),
					IntProvider.CODEC.fieldOf("z").forGetter(EllipsoidPlacement::z),
					IntProvider.CODEC.fieldOf("count").forGetter(EllipsoidPlacement::count),
					FloatProvider.codec(1f, Float.MAX_VALUE).fieldOf("x_size").forGetter(EllipsoidPlacement::xLength),
					FloatProvider.codec(1f, Float.MAX_VALUE).fieldOf("z_size").forGetter(EllipsoidPlacement::zLength),
					FloatProvider.codec(1f, Float.MAX_VALUE).fieldOf("y_size").forGetter(EllipsoidPlacement::yLength),
				).apply(it, ::EllipsoidPlacement)
			}
		}
	}
}
