package ru.dbotthepony.mc.otm.recipe

import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import net.minecraft.core.NonNullList
import net.minecraft.core.RegistryAccess
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.resources.ResourceLocation
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.RecipeSerializer
import net.minecraft.world.item.crafting.RecipeType
import net.minecraft.world.level.Level
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.mc.otm.OverdriveThatMatters
import ru.dbotthepony.mc.otm.block.entity.tech.PlatePressBlockEntity
import ru.dbotthepony.mc.otm.container.get
import ru.dbotthepony.mc.otm.core.fromJsonStrict
import ru.dbotthepony.mc.otm.core.isActuallyEmpty
import ru.dbotthepony.mc.otm.registry.MRecipes
import ru.dbotthepony.mc.otm.core.registryName
import ru.dbotthepony.mc.otm.core.toJsonStrict
import ru.dbotthepony.mc.otm.core.util.readJson
import ru.dbotthepony.mc.otm.core.util.writeJson

class PlatePressRecipe(
	private val id: ResourceLocation,
	val input: Ingredient,
	val output: Ingredient,
	val count: Int,
	val workTime: Int = 200,
	val experience: FloatProvider = ConstantFloat.ZERO
) : Recipe<Container> {
	override fun matches(container: Container, p_44003_: Level): Boolean {
		if (output.isActuallyEmpty || input.isActuallyEmpty)
			return false

		return input.test(container[0])
	}

	fun matches(container: Container, slot: Int): Boolean {
		if (output.isActuallyEmpty || input.isActuallyEmpty)
			return false

		return input.test(container[slot])
	}

	private val outputStack: ItemStack by lazy {
		if (output.isActuallyEmpty || input.isActuallyEmpty) {
			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 (input.isActuallyEmpty || output.isActuallyEmpty)
			return super.getIngredients()

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

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

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

	override fun getId() = id

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

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

	companion object : RecipeSerializer<PlatePressRecipe> {
		private val EMPTY = PlatePressRecipe(ResourceLocation(OverdriveThatMatters.MOD_ID, "empty"), Ingredient.EMPTY, Ingredient.EMPTY, 1)
		private val LOGGER = LogManager.getLogger()

		override fun fromJson(loc: ResourceLocation, obj: JsonObject): PlatePressRecipe {
			val input = try {
				Ingredient.fromJson(obj["input"] ?: throw IllegalStateException("Recipe $loc has no input field defined"))
			} catch (err: Throwable) {
				if (err.message?.lowercase()?.contains("unknown item tag") == true) {
					LOGGER.warn("Ignoring recipe Input of $loc deserialization error, defaulting to empty recipe")
					LOGGER.warn(err)
					return EMPTY
				} else {
					throw IllegalStateException("Input of $loc is malformed", err)
				}
			}

			val result = try {
				Ingredient.fromJson(obj["result"] ?: throw IllegalStateException("Recipe $loc has no result field defined"))
			} catch (err: Throwable) {
				if (err.message?.lowercase()?.contains("unknown item tag") == true) {
					LOGGER.warn("Ignoring recipe Output of $loc deserialization error, defaulting to empty recipe")
					LOGGER.warn(err)
					return EMPTY
				} else {
					throw IllegalStateException("Result of $loc is malformed", err)
				}
			}

			val workTime = (obj["work_time"] as? JsonPrimitive)?.let { return@let try {it.asInt} catch(err: Throwable) {throw IllegalStateException("Invalid work_time")} } ?: 200

			check(workTime >= 0) { "work_time of $loc does not make any sense" }

			val count = ((obj["result"] as JsonObject)["count"]  as? JsonPrimitive)?.let { return@let try {it.asInt} catch(err: Throwable) {throw IllegalStateException("Invalid result.count")} } ?: 1

			val experience = obj["experience"]?.let { FloatProvider.CODEC.fromJsonStrict(it) } ?: ConstantFloat.ZERO
			return PlatePressRecipe(loc, input, result, count, workTime, experience)
		}

		override fun fromNetwork(loc: ResourceLocation, buff: FriendlyByteBuf): PlatePressRecipe {
			return PlatePressRecipe(loc, Ingredient.fromNetwork(buff), Ingredient.fromNetwork(buff), buff.readInt(), buff.readInt(), FloatProvider.CODEC.fromJsonStrict(buff.readJson()))
		}

		override fun toNetwork(buff: FriendlyByteBuf, recipe: PlatePressRecipe) {
			recipe.input.toNetwork(buff)
			recipe.output.toNetwork(buff)
			buff.writeInt(recipe.count)
			buff.writeInt(recipe.workTime)
			buff.writeJson(FloatProvider.CODEC.toJsonStrict(recipe.experience))
		}
	}
}

