package ru.dbotthepony.mc.otm.recipe

import com.mojang.serialization.Codec
import com.mojang.serialization.MapCodec
import com.mojang.serialization.codecs.RecordCodecBuilder
import net.minecraft.core.HolderLookup
import net.minecraft.core.NonNullList
import net.minecraft.network.RegistryFriendlyByteBuf
import net.minecraft.network.codec.ByteBufCodecs
import net.minecraft.network.codec.StreamCodec
import net.minecraft.util.valueproviders.ConstantFloat
import net.minecraft.util.valueproviders.FloatProvider
import net.minecraft.world.Container
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.crafting.Ingredient
import net.minecraft.world.item.crafting.Recipe
import net.minecraft.world.item.crafting.RecipeInput
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.OverdriveThatMatters
import ru.dbotthepony.mc.otm.container.get
import ru.dbotthepony.mc.otm.core.get
import ru.dbotthepony.mc.otm.registry.game.MRecipes
import ru.dbotthepony.mc.otm.core.registryName
import ru.dbotthepony.mc.otm.data.codec.minRange
import ru.dbotthepony.mc.otm.network.streamCodec
import ru.dbotthepony.mc.otm.registry.game.MItems

class PlatePressRecipe(
	val input: Ingredient,
	val output: Ingredient,
	val count: Int = 1,
	val workTime: Int = 200,
	val experience: FloatProvider = ConstantFloat.ZERO
) : Recipe<RecipeInput> {
	override fun matches(container: RecipeInput, p_44003_: Level): Boolean {
		if (isIncomplete)
			return false

		return input.test(container[0])
	}

	fun matches(container: Container, slot: Int): Boolean {
		if (isIncomplete)
			return false

		return input.test(container[slot])
	}

	private val outputStack: ItemStack by lazy {
		if (isIncomplete) {
			ItemStack.EMPTY
		} else {
			val items = output.items
			val bestMatch = items.firstOrNull { it.item.registryName?.namespace == OverdriveThatMatters.MOD_ID } ?: items[0]
			bestMatch.copy().also { it.count = this.count }
		}
	}

	override fun getIngredients(): NonNullList<Ingredient> {
		if (isIncomplete)
			return super.getIngredients()

		return NonNullList.of(Ingredient.EMPTY, input)
	}

	override fun isIncomplete(): Boolean {
		return input.hasNoItems() || output.hasNoItems()
	}

	override fun assemble(p_44001_: RecipeInput, registry: HolderLookup.Provider): ItemStack = outputStack.copy()
	override fun canCraftInDimensions(p_43999_: Int, p_44000_: Int) = true
	override fun getResultItem(registry: HolderLookup.Provider): ItemStack = outputStack

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

	override fun getType(): RecipeType<PlatePressRecipe> = MRecipes.PLATE_PRESS

	override fun getToastSymbol(): ItemStack {
		return ItemStack(MItems.TWIN_PLATE_PRESS[null]!!)
	}

	companion object : RecipeSerializer<PlatePressRecipe> {
		private val codec = RecordCodecBuilder.mapCodec {
			it.group(
				Ingredient.CODEC_NONEMPTY.fieldOf("input").forGetter(PlatePressRecipe::input),
				Ingredient.CODEC_NONEMPTY.fieldOf("output").forGetter(PlatePressRecipe::output),
				Codec.INT.minRange(1).optionalFieldOf("count", 1).forGetter(PlatePressRecipe::count),
				Codec.INT.minRange(0).optionalFieldOf("workTime", 200).forGetter(PlatePressRecipe::workTime),
				FloatProvider.CODEC.optionalFieldOf("experience", ConstantFloat.ZERO).forGetter(PlatePressRecipe::experience)
			).apply(it, ::PlatePressRecipe)
		}

		private val streamCodec = StreamCodec.composite(
			Ingredient.CONTENTS_STREAM_CODEC, PlatePressRecipe::input,
			Ingredient.CONTENTS_STREAM_CODEC, PlatePressRecipe::output,
			ByteBufCodecs.INT, PlatePressRecipe::count,
			ByteBufCodecs.INT, PlatePressRecipe::workTime,
			FloatProvider.CODEC.streamCodec(), PlatePressRecipe::experience,
			::PlatePressRecipe
		)

		override fun codec(): MapCodec<PlatePressRecipe> {
			return codec
		}

		override fun streamCodec(): StreamCodec<RegistryFriendlyByteBuf, PlatePressRecipe> {
			return streamCodec
		}
	}
}

