package ru.dbotthepony.mc.otm.recipe

import com.google.common.collect.ImmutableList
import com.google.gson.JsonObject
import com.google.gson.JsonPrimitive
import net.minecraft.core.NonNullList
import net.minecraft.core.RegistryAccess
import net.minecraft.nbt.CompoundTag
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import net.minecraft.util.GsonHelper
import net.minecraft.world.inventory.CraftingContainer
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.crafting.CraftingBookCategory
import net.minecraft.world.item.crafting.CraftingRecipe
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.item.crafting.ShapedRecipe
import net.minecraft.world.level.Level
import net.minecraftforge.common.crafting.IShapedRecipe
import ru.dbotthepony.mc.otm.container.stream
import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.core.registryName
import ru.dbotthepony.mc.otm.core.set
import ru.dbotthepony.mc.otm.core.tagNotNull
import ru.dbotthepony.mc.otm.core.util.readJson
import ru.dbotthepony.mc.otm.core.util.writeJson
import ru.dbotthepony.mc.otm.core.collect.stream
import java.util.stream.Stream

class UpgradeRecipe(
	val parent: ShapedRecipe,
	copyPaths: Stream<Op>,
	val source: ResourceLocation,
) : CraftingRecipe, IShapedRecipe<CraftingContainer> by parent {
	override fun matches(p_44002_: CraftingContainer, p_44003_: Level): Boolean {
		return parent.matches(p_44002_, p_44003_)
	}

	override fun canCraftInDimensions(p_43999_: Int, p_44000_: Int): Boolean {
		return parent.canCraftInDimensions(p_43999_, p_44000_)
	}

	override fun getResultItem(p_267052_: RegistryAccess): ItemStack {
		return parent.getResultItem(p_267052_)
	}

	override fun getRemainingItems(p_44004_: CraftingContainer): NonNullList<ItemStack> {
		return parent.getRemainingItems(p_44004_)
	}

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

	override fun isSpecial(): Boolean {
		return parent.isSpecial
	}

	override fun showNotification(): Boolean {
		return parent.showNotification()
	}

	override fun getGroup(): String {
		return parent.group
	}

	override fun getToastSymbol(): ItemStack {
		return parent.toastSymbol
	}

	override fun getId(): ResourceLocation {
		return parent.id
	}

	override fun isIncomplete(): Boolean {
		return parent.isIncomplete
	}

	override fun getType(): RecipeType<*> {
		return parent.type
	}

	override fun category(): CraftingBookCategory {
		return parent.category()
	}

	enum class OpType {
		DIRECT {
			override fun deserialize(obj: JsonObject): Op {
				return Direct(GsonHelper.getAsString(obj, "path"))
			}
		}, INDIRECT {
			override fun deserialize(obj: JsonObject): Op {
				return Indirect(GsonHelper.getAsString(obj, "source"), GsonHelper.getAsString(obj, "destination"))
			}
		};

		abstract fun deserialize(obj: JsonObject): Op
	}

	interface Op {
		val type: OpType
		fun apply(source: CompoundTag, destination: CompoundTag)
		fun serialize(json: JsonObject)

		fun serialize(): JsonObject {
			return JsonObject().also {
				it["type"] = JsonPrimitive(type.name)
				serialize(it)
			}
		}
	}

	data class Direct(val path: String) : Op {
		private val split = path.split('.')
		override val type: OpType
			get() = OpType.DIRECT

		override fun serialize(json: JsonObject) {
			json["path"] = JsonPrimitive("path")
		}

		override fun apply(source: CompoundTag, destination: CompoundTag) {
			var a = source
			var b = destination

			for (i in 0 until split.size - 1) {
				val value = split[i]

				if (value !in a) {
					a[value] = CompoundTag()
				}

				if (value !in b) {
					b[value] = CompoundTag()
				}

				a = a[value] as CompoundTag
				b = b[value] as CompoundTag
			}

			val value = split.last()

			if (value in a) {
				b[value] = a[value]!!.copy()
			}
		}
	}

	data class Indirect(val pathSource: String, val pathDestination: String) : Op {
		private val splitSource = pathSource.split('.')
		private val splitDestination = pathDestination.split('.')

		override val type: OpType
			get() = OpType.INDIRECT

		override fun serialize(json: JsonObject) {
			json["source"] = JsonPrimitive(pathSource)
			json["destination"] = JsonPrimitive(pathDestination)
		}

		override fun apply(source: CompoundTag, destination: CompoundTag) {
			var a = source
			var b = destination

			for (i in 0 until splitSource.size - 1) {
				val value = splitSource[i]

				if (value !in a) {
					a[value] = CompoundTag()
				}

				a = a[value] as CompoundTag
			}

			for (i in 0 until splitDestination.size - 1) {
				val value = splitDestination[i]

				if (value !in b) {
					b[value] = CompoundTag()
				}

				b = b[value] as CompoundTag
			}

			val valueA = splitSource.last()
			val valueB = splitDestination.last()

			if (valueA in a) {
				b[valueB] = a[valueA]!!.copy()
			}
		}
	}

	val copyPaths: ImmutableList<Op> = copyPaths.collect(ImmutableList.toImmutableList())

	override fun assemble(pInv: CraftingContainer, registryAccess: RegistryAccess): ItemStack {
		val result = parent.assemble(pInv, registryAccess)

		if (result.isEmpty) {
			return result
		}

		val sources = pInv.stream().filter { !it.isEmpty && it.item.registryName == source }.toList()

		if (sources.size != 1) {
			return ItemStack.EMPTY
		}

		val source = sources.first()

		for (op in copyPaths) {
			op.apply(source.tagNotNull, result.tagNotNull)
		}

		return result
	}

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

	companion object : RecipeSerializer<UpgradeRecipe> {
		fun deserializeOp(json: JsonObject): Op {
			return OpType.valueOf(GsonHelper.getAsString(json, "type")).deserialize(json)
		}

		override fun fromJson(id: ResourceLocation, data: JsonObject): UpgradeRecipe {
			return UpgradeRecipe(
				ShapedRecipe.Serializer.SHAPED_RECIPE.fromJson(id, data),
				GsonHelper.getAsJsonArray(data, "copyPaths").stream().map { deserializeOp(it as JsonObject) },
				ResourceLocation(GsonHelper.getAsString(data, "source"))
			)
		}

		override fun fromNetwork(id: ResourceLocation, buff: FriendlyByteBuf): UpgradeRecipe? {
			val recipe = ShapedRecipe.Serializer.SHAPED_RECIPE.fromNetwork(id, buff) ?: return null

			return UpgradeRecipe(
				recipe,
				buff.readCollection({ ArrayList<Op>(it) }, { deserializeOp(it.readJson() as JsonObject) }).stream(),
				buff.readResourceLocation()
			)
		}

		override fun toNetwork(buff: FriendlyByteBuf, value: UpgradeRecipe) {
			ShapedRecipe.Serializer.SHAPED_RECIPE.toNetwork(buff, value.parent)
			buff.writeCollection(value.copyPaths) { it, v -> it.writeJson(v.serialize()) }
			buff.writeResourceLocation(value.source)
		}
	}
}
