package ru.dbotthepony.mc.otm.datagen.loot

import net.minecraft.advancements.critereon.StatePropertiesPredicate
import net.minecraft.util.StringRepresentable
import net.minecraft.world.level.ItemLike
import net.minecraft.world.level.block.Block
import net.minecraft.world.level.block.state.properties.Property
import net.minecraft.world.level.storage.loot.LootPool
import net.minecraft.world.level.storage.loot.LootTable
import net.minecraft.world.level.storage.loot.entries.LootItem
import net.minecraft.world.level.storage.loot.entries.LootPoolSingletonContainer
import net.minecraft.world.level.storage.loot.functions.LootItemConditionalFunction
import net.minecraft.world.level.storage.loot.functions.SetItemCountFunction
import net.minecraft.world.level.storage.loot.predicates.LootItemBlockStatePropertyCondition
import net.minecraft.world.level.storage.loot.predicates.LootItemCondition
import net.minecraft.world.level.storage.loot.providers.number.ConstantValue
import net.minecraft.world.level.storage.loot.providers.number.UniformGenerator
import ru.dbotthepony.mc.otm.data.condition.ChanceCondition

inline fun LootTable.Builder.lootPool(configurator: LootPool.Builder.() -> Unit): LootTable.Builder = withPool(LootPool.lootPool().also(configurator))
inline fun LootTable.Builder.singleItem(item: ItemLike, configurator: LootPoolSingletonContainer.Builder<*>.() -> Unit): LootTable.Builder {
	return withPool(LootPool.lootPool().also {
		it.item(item, configurator)
	})
}

inline fun lootPool(configurator: LootPool.Builder.() -> Unit): LootPool = LootPool.lootPool().also(configurator).build()
inline fun singleItem(item: ItemLike, configurator: LootPoolSingletonContainer.Builder<*>.() -> Unit): LootPool = lootPool { item(item, configurator) }

inline fun LootPool.Builder.item(item: ItemLike, configurator: LootPoolSingletonContainer.Builder<*>.() -> Unit) {
	add(LootItem.lootTableItem(item).also(configurator))
}

fun LootPool.Builder.setRolls(count: Int): LootPool.Builder = setRolls(ConstantValue.exactly(count.toFloat()))
fun LootPool.Builder.setRolls(count: Float): LootPool.Builder = setRolls(ConstantValue.exactly(count))
fun LootPool.Builder.setRolls(min: Int, max: Int): LootPool.Builder = setRolls(UniformGenerator.between(min.toFloat(), max.toFloat()))
fun LootPool.Builder.setRolls(min: Float, max: Float): LootPool.Builder = setRolls(UniformGenerator.between(min, max))

fun LootPool.Builder.condition(value: LootItemCondition.Builder): LootPool.Builder = `when`(value)
fun LootPool.Builder.chanceCondition(chance: Double): LootPool.Builder = condition(ChanceCondition(chance))

fun <T : LootPoolSingletonContainer.Builder<*>> T.setCount(count: Int, configurator: LootItemConditionalFunction.Builder<*>.() -> Unit = {}): T {
	apply(SetItemCountFunction.setCount(ConstantValue.exactly(count.toFloat())).also(configurator))
	return this
}

fun <T : LootPoolSingletonContainer.Builder<*>> T.setCount(minimal: Int, maximal: Int, configurator: LootItemConditionalFunction.Builder<*>.() -> Unit = {}): T {
	apply(SetItemCountFunction.setCount(UniformGenerator.between(minimal.toFloat(), maximal.toFloat())).also(configurator))
	return this
}

fun <T : LootPoolSingletonContainer.Builder<*>> T.chanceCondition(chance: Double): T {
	condition(ChanceCondition(chance))
	return this
}

fun <T : LootPoolSingletonContainer.Builder<*>> T.setCount(minimal: Float, maximal: Float, configurator: LootItemConditionalFunction.Builder<*>.() -> Unit = {}): T {
	apply(SetItemCountFunction.setCount(UniformGenerator.between(minimal, maximal)).also(configurator))
	return this
}

fun <T : LootPoolSingletonContainer.Builder<*>> T.condition(condition: LootItemCondition.Builder): T {
	`when`(condition)
	return this
}

fun <T : LootItemConditionalFunction.Builder<*>> T.condition(condition: LootItemCondition.Builder): T {
	`when`(condition)
	return this
}

inline fun <T : LootPoolSingletonContainer.Builder<*>> T.blockStateCondition(block: Block, configurator: StatePropertiesPredicate.Builder.() -> Unit): T {
	condition(
		LootItemBlockStatePropertyCondition.hasBlockStateProperties(block)
			.setProperties(StatePropertiesPredicate.Builder.properties().also(configurator)))
	return this
}

inline fun <T : LootItemConditionalFunction.Builder<*>> T.blockStateCondition(block: Block, configurator: StatePropertiesPredicate.Builder.() -> Unit): T {
	condition(
		LootItemBlockStatePropertyCondition.hasBlockStateProperties(block)
			.setProperties(StatePropertiesPredicate.Builder.properties().also(configurator)))
	return this
}

fun <T : LootItemConditionalFunction.Builder<*>> T.chanceCondition(chance: Double): T {
	condition(ChanceCondition(chance))
	return this
}

operator fun StatePropertiesPredicate.Builder.set(property: Property<*>, value: String): StatePropertiesPredicate.Builder = hasProperty(property, value)
operator fun StatePropertiesPredicate.Builder.set(property: Property<Int>, value: Int): StatePropertiesPredicate.Builder = hasProperty(property, value)
operator fun StatePropertiesPredicate.Builder.set(property: Property<Boolean>, value: Boolean): StatePropertiesPredicate.Builder = hasProperty(property, value)

operator fun <T : Comparable<T>> StatePropertiesPredicate.Builder.set(property: Property<T>, value: T): StatePropertiesPredicate.Builder {
	if (value !is StringRepresentable) {
		throw ClassCastException("Provided type ${value::class.qualifiedName} is not a subtype of ${StringRepresentable::class.qualifiedName}")
	}

	return hasProperty(property, value.serializedName)
}
