package ru.dbotthepony.mc.otm.recipe

import com.mojang.serialization.Codec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.NonNullList
import net.minecraft.core.RegistryAccess
import net.minecraft.core.UUIDUtil
import net.minecraft.data.recipes.FinishedRecipe
import net.minecraft.resources.ResourceLocation
import net.minecraft.world.inventory.CraftingContainer
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.crafting.Ingredient
import net.minecraft.world.item.crafting.RecipeSerializer
import net.minecraft.world.item.crafting.RecipeType
import net.minecraft.world.level.Level
import ru.dbotthepony.mc.otm.capability.matter.matter
import ru.dbotthepony.mc.otm.capability.matteryEnergy
import ru.dbotthepony.mc.otm.container.util.iterator
import ru.dbotthepony.mc.otm.core.collect.filterNotNull
import ru.dbotthepony.mc.otm.core.collect.map
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.core.tagNotNull
import ru.dbotthepony.mc.otm.data.Codec2RecipeSerializer
import ru.dbotthepony.mc.otm.data.DecimalCodec
import ru.dbotthepony.mc.otm.data.IngredientMatrixCodec
import ru.dbotthepony.mc.otm.data.minRange
import ru.dbotthepony.mc.otm.registry.MItems
import ru.dbotthepony.mc.otm.registry.MRecipes
import java.util.Optional
import java.util.UUID
import kotlin.jvm.optionals.getOrElse

interface IMatterEntanglerRecipe : IMatteryRecipe<CraftingContainer> {
	val matter: Decimal
	val ticks: Double
	val ingredients: IIngredientMatrix
	val result: ItemStack
	val experience: Float

	fun preemptivelyMatches(container: CraftingContainer, level: Level): Boolean
}

open class MatterEntanglerRecipe(
	private val id: ResourceLocation,
	override val ingredients: IIngredientMatrix,
	override val matter: Decimal,
	override val ticks: Double,
	override val result: ItemStack,
	override val experience: Float = 0f,
	val uuidKey: String = "uuid",
	val fixedUuid: Optional<UUID> = Optional.empty()
) : IMatterEntanglerRecipe {
	override fun getId(): ResourceLocation {
		return id
	}

	override fun matches(container: CraftingContainer, level: Level): Boolean {
		if (isIncomplete) return false
		return ingredients.test(container)
	}

	override fun preemptivelyMatches(container: CraftingContainer, level: Level): Boolean {
		if (isIncomplete) return false
		return ingredients.preemptiveTest(container)
	}

	override fun assemble(container: CraftingContainer): ItemStack {
		return result.copy().also {
			it.tagNotNull[uuidKey] = fixedUuid.getOrElse { UUID.randomUUID() }
		}
	}

	override fun canCraftInDimensions(width: Int, height: Int): Boolean {
		return width >= ingredients.width && height >= ingredients.height
	}

	override fun getResultItem(): ItemStack {
		return result
	}

	override fun getSerializer(): RecipeSerializer<*> {
		return SERIALIZER
	}

	override fun getType(): RecipeType<*> {
		return MRecipes.MATTER_ENTANGLER
	}

	override fun getIngredients(): NonNullList<Ingredient> {
		return ingredients.ingredients
	}

	override fun isIncomplete(): Boolean {
		return result.isEmpty || ingredients.isIncomplete
	}

	override fun isSpecial(): Boolean {
		return true
	}

	override fun getToastSymbol(): ItemStack {
		return ItemStack(MItems.MATTER_ENTANGLER)
	}

	fun toFinished(): FinishedRecipe {
		return SERIALIZER.toFinished(this)
	}

	fun energetic() = Energy(this)
	fun matter() = Matter(this)

	open class Energy(val parent: MatterEntanglerRecipe) : IMatterEntanglerRecipe by parent {
		override fun assemble(container: CraftingContainer): ItemStack {
			return parent.assemble(container).also { result ->
				container.iterator().map { it.matteryEnergy }.filterNotNull().forEach {
					result.matteryEnergy!!.batteryLevel += it.batteryLevel
				}
			}
		}

		fun toFinished(): FinishedRecipe {
			return ENERGY_SERIALIZER.toFinished(this)
		}

		override fun getSerializer(): RecipeSerializer<*> {
			return ENERGY_SERIALIZER
		}
	}

	open class Matter(val parent: MatterEntanglerRecipe) : IMatterEntanglerRecipe by parent {
		override fun assemble(container: CraftingContainer): ItemStack {
			return parent.assemble(container).also { result ->
				container.iterator().map { it.matter }.filterNotNull().forEach {
					result.matter!!.storedMatter += it.storedMatter
				}
			}
		}

		fun toFinished(): FinishedRecipe {
			return MATTER_SERIALIZER.toFinished(this)
		}

		override fun getSerializer(): RecipeSerializer<*> {
			return MATTER_SERIALIZER
		}
	}

	companion object {
		val SERIALIZER = Codec2RecipeSerializer<MatterEntanglerRecipe> { context ->
			RecordCodecBuilder.create {
				it.group(
					IngredientMatrixCodec(context.ingredients).fieldOf("ingredients").forGetter(MatterEntanglerRecipe::ingredients),
					DecimalCodec.minRange(Decimal.ZERO).fieldOf("matter").forGetter(MatterEntanglerRecipe::matter),
					Codec.DOUBLE.minRange(0.0).fieldOf("ticks").forGetter(MatterEntanglerRecipe::ticks),
					ItemStack.CODEC.fieldOf("result").forGetter(MatterEntanglerRecipe::result),
					Codec.FLOAT.minRange(0f).optionalFieldOf("experience", 0f).forGetter(MatterEntanglerRecipe::experience),
					Codec.STRING.optionalFieldOf("uuidKey", "uuid").forGetter(MatterEntanglerRecipe::uuidKey),
					UUIDUtil.STRING_CODEC.optionalFieldOf("fixedUuid").forGetter(MatterEntanglerRecipe::fixedUuid)
				).apply(it) { a, b, c, d, e, f, g -> MatterEntanglerRecipe(context.id, a, b, c, d, e, f, g) }
			}
		}

		val ENERGY_SERIALIZER = SERIALIZER.xmap(::Energy, Energy::parent)
		val MATTER_SERIALIZER = SERIALIZER.xmap(::Matter, Matter::parent)
	}
}
