package ru.dbotthepony.mc.otm.data

import com.mojang.datafixers.util.Either
import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.RandomSource
import net.minecraftforge.eventbus.api.IEventBus
import net.minecraftforge.registries.DeferredRegister
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.nextDecimal
import ru.dbotthepony.mc.otm.registry.RegistryDelegate

fun interface SampledDecimal {
	fun sample(source: RandomSource): Decimal
}

abstract class DecimalProvider : SampledDecimal {
	interface Type<T : DecimalProvider> {
		val codec: Codec<T>
	}

	abstract val minValue: Decimal
	abstract val maxValue: Decimal
	abstract val type: Type<*>

	companion object {
		private val registryHolder = RegistryDelegate<Type<*>>("decimal_provider_type") {
			setDefaultKey(ResourceLocation(OverdriveThatMatters.MOD_ID, "zero"))
			disableSaving()
		}

		val CODEC: Codec<DecimalProvider> by lazy {
			Codec
				.either(DecimalCodec, registry.codec.dispatch({ it.type }, { it.codec }))
				.xmap(
					{ c -> c.map(::ConstantDecimal, { it }) },
					{ if (it.type === ConstantDecimal.Companion) Either.left(it.minValue) else Either.right(it) }
				)
		}

		val registry by registryHolder
		val registryKey get() = registryHolder.key

		private val registror = DeferredRegister.create(registryKey, OverdriveThatMatters.MOD_ID)

		init {
			registror.register("zero") { ConstantDecimal.Zero }
			registror.register("constant") { ConstantDecimal.Companion }
			registror.register("uniform") { UniformDecimal.Companion }
		}

		internal fun register(bus: IEventBus) {
			bus.addListener(registryHolder::build)
			registror.register(bus)
		}
	}
}

class ConstantDecimal(val value: Decimal) : DecimalProvider() {
	object Zero : DecimalProvider(), Type<Zero> {
		override fun sample(source: RandomSource): Decimal {
			return Decimal.ZERO
		}

		override val codec: Codec<Zero> = Codec.unit(this)

		override val minValue: Decimal
			get() = Decimal.ZERO
		override val maxValue: Decimal
			get() = Decimal.ZERO
		override val type: Type<*>
			get() = this
	}

	override fun sample(source: RandomSource): Decimal {
		return value
	}

	override val minValue: Decimal
		get() = value
	override val maxValue: Decimal
		get() = value
	override val type: Type<*>
		get() = Companion

	companion object : Type<ConstantDecimal> {
		override val codec: Codec<ConstantDecimal> = RecordCodecBuilder.create {
			it.group(DecimalCodec.fieldOf("value").forGetter(ConstantDecimal::value)).apply(it, ::ConstantDecimal)
		}
	}
}

class UniformDecimal(override val minValue: Decimal, override val maxValue: Decimal, val round: Boolean = true) : DecimalProvider() {
	constructor(range: ClosedRange<Decimal>, round: Boolean = true) : this(range.start, range.endInclusive, round)

	init {
		require(minValue <= maxValue) { "Invalid range provided: $minValue .. $maxValue" }
	}

	override fun sample(source: RandomSource): Decimal {
		return source.nextDecimal(minValue, maxValue, round = round)
	}

	override val type: Type<*>
		get() = Companion

	companion object : Type<UniformDecimal> {
		override val codec: Codec<UniformDecimal> = RecordCodecBuilder.create {
			it.group(
				DecimalCodec.fieldOf("minValue").forGetter(UniformDecimal::minValue),
				DecimalCodec.fieldOf("maxValue").forGetter(UniformDecimal::maxValue),
				Codec.BOOL.optionalFieldOf("round", true).forGetter(UniformDecimal::round)
			).apply(it, ::UniformDecimal)
		}
	}
}
