package ru.dbotthepony.mc.otm.menu.decorative

import net.minecraft.world.SimpleContainer
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.*
import net.minecraft.world.level.material.Fluids
import net.minecraftforge.common.capabilities.ForgeCapabilities
import net.minecraftforge.fluids.FluidStack
import net.minecraftforge.fluids.capability.IFluidHandler
import ru.dbotthepony.mc.otm.block.entity.decorative.PainterBlockEntity
import ru.dbotthepony.mc.otm.capability.matteryPlayer
import ru.dbotthepony.mc.otm.container.MatteryContainer
import ru.dbotthepony.mc.otm.core.ISubscriptable
import ru.dbotthepony.mc.otm.core.addAll
import ru.dbotthepony.mc.otm.core.collect.SupplierMap
import ru.dbotthepony.mc.otm.core.collect.filter
import ru.dbotthepony.mc.otm.core.collect.maybe
import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.core.util.ResourceLocationValueCodec
import ru.dbotthepony.mc.otm.core.value
import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.menu.MatterySlot
import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback
import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput
import ru.dbotthepony.mc.otm.recipe.AbstractPainterRecipe
import ru.dbotthepony.mc.otm.registry.MMenus
import ru.dbotthepony.mc.otm.registry.MRecipes
import java.util.*
import java.util.function.IntConsumer
import kotlin.collections.ArrayList

class PainterMenu(
	containerId: Int, inventory: Inventory, tile: PainterBlockEntity? = null
) : MatteryMenu(MMenus.PAINTER, containerId, inventory, tile) {
	val dyeStored = (DyeColor.entries.toMutableList<DyeColor?>().also { it.add(0, null) }).associateWith { dye ->
		mSynchronizer.ComputedIntField({ tile?.dyeStored(dye) ?: 0 }).also { it.addListener(IntConsumer { rescan() }) }
	}

	val dyeStoredDirect = SupplierMap(dyeStored)
	val itemConfig = ItemConfigPlayerInput(this, tile?.config)

	val inputContainer = MatteryContainer(::rescan, 1)
	val outputContainer = MatteryContainer(1)
	private var lastRecipe: AbstractPainterRecipe? = null
	var selectedRecipe by mSynchronizer.Field(null, ResourceLocationValueCodec.nullable).also { it.addListener { rescan() } }

	val isBulk = BooleanInputWithFeedback(this, tile?.let { it::isBulk })

	val selectRecipe = PlayerInput(ResourceLocationValueCodec) {
		selectedRecipe = it
	}

	val inputSlot = object : MatterySlot(inputContainer, 0) {
		override fun mayPlace(itemStack: ItemStack): Boolean {
			if (!itemStack.isEmpty && itemStack.item is DyeableArmorItem) {
				return super.mayPlace(itemStack)
			}

			return super.mayPlace(itemStack) && inventory.player.level.recipeManager.byType(MRecipes.PAINTER).values.any { it.value.matches(itemStack) }
		}
	}

	val outputSlot = object : MatterySlot(outputContainer, 0) {
		override fun tryRemove(p_150642_: Int, p_150643_: Int, p_150644_: Player): Optional<ItemStack> {
			rescan()
			return super.tryRemove(p_150642_, p_150643_, p_150644_)
		}

		override fun onTake(player: Player, itemStack: ItemStack) {
			if (itemStack.isNotEmpty) {
				lastRecipe?.value?.dyes?.let { tile?.takeDyes(it) }

				if (isBulk.value) {
					val found = player.matteryPlayer!!.inventoryAndExopack
						.slotIterator()
						.filter { !it.isForbiddenForAutomation && ItemStack.isSameItemSameTags(it.item, inputSlot.item) }
						.maybe()

					if (found != null) {
						found.remove(1)
						rescan()
					} else {
						inputContainer.removeItem(0, 1)
					}
				} else {
					inputContainer.removeItem(0, 1)
				}
			}

			super.onTake(player, itemStack)
		}

		override fun mayPlace(itemStack: ItemStack): Boolean {
			return false
		}
	}

	val dyeSlot = object : MatterySlot(tile?.dyeInput ?: SimpleContainer(1), 0) {
		override fun mayPlace(itemStack: ItemStack): Boolean {
			return super.mayPlace(itemStack) && ((
				itemStack.getCapability(ForgeCapabilities.FLUID_HANDLER_ITEM).resolve().map {
					dyeStoredDirect[null]!! < PainterBlockEntity.MAX_WATER_STORAGE && it.drain(FluidStack(Fluids.WATER, PainterBlockEntity.MAX_WATER_STORAGE - dyeStoredDirect[null]!!), IFluidHandler.FluidAction.SIMULATE).isNotEmpty
				}.orElse(false)
			) || (DyeColor.getColor(itemStack)?.let { dyeStoredDirect[it]!! + PainterBlockEntity.HUE_PER_ITEM <= PainterBlockEntity.MAX_STORAGE } ?: false))
		}
	}

	init {
		addSlot(outputSlot)
		addSlot(dyeSlot)

		addStorageSlot(dyeSlot)
		addStorageSlot(inputSlot)
		mapQuickMoveToInventory(outputSlot)
		mapQuickMoveToInventory(dyeSlot)

		addInventorySlots()
	}

	override fun removed(p_38940_: Player) {
		super.removed(p_38940_)
		clearContainer(p_38940_, inputContainer)
	}

	val listeners = ISubscriptable.Impl<Unit>()
	val possibleRecipes = ArrayList<AbstractPainterRecipe>()

	private fun rescan() {
		possibleRecipes.clear()
		possibleRecipes.addAll(inventory.player.level.recipeManager.byType(MRecipes.PAINTER).values.iterator().filter { it.value.matches(inputContainer[0]) })

		listeners.accept(Unit)
		if (tile !is PainterBlockEntity) return

		if (inputContainer.isEmpty || selectedRecipe == null) {
			outputContainer.clearContent()
		} else {
			val recipe = inventory.player.level.recipeManager.byKey(selectedRecipe!!).get() as AbstractPainterRecipe?

			if (recipe == null || !recipe.value.canCraft(dyeStoredDirect) || !recipe.value.matches(inputContainer, inventory.player.level)) {
				outputContainer.clearContent()
			} else {
				outputContainer[0] = recipe.value.assemble(inputContainer)
				lastRecipe = recipe
			}
		}
	}
}
