package ru.dbotthepony.mc.otm.menu.storage

import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.Container
import net.minecraft.world.SimpleContainer
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.block.entity.storage.IItemMonitorPlayerSettings
import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorBlockEntity
import ru.dbotthepony.mc.otm.block.entity.storage.ItemMonitorPlayerSettings
import ru.dbotthepony.mc.otm.container.get
import ru.dbotthepony.mc.otm.core.collect.map
import ru.dbotthepony.mc.otm.core.collect.reduce
import ru.dbotthepony.mc.otm.core.isNotEmpty
import ru.dbotthepony.mc.otm.menu.MatteryPoweredMenu
import ru.dbotthepony.mc.otm.menu.MatterySlot
import ru.dbotthepony.mc.otm.menu.data.INetworkedItemViewProvider
import ru.dbotthepony.mc.otm.menu.data.NetworkedItemView
import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback
import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback
import ru.dbotthepony.mc.otm.menu.makeSlots
import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
import ru.dbotthepony.mc.otm.registry.MMenus
import ru.dbotthepony.mc.otm.storage.*
import java.util.*

private class ResultSlot(container: Container) : MatterySlot(container, 0) {
	override fun mayPlace(itemStack: ItemStack): Boolean {
		return false
	}

	override fun remove(p_40227_: Int): ItemStack {
		if (container[0].count != p_40227_) {
			return ItemStack.EMPTY
		}

		return super.remove(p_40227_)
	}

	override fun safeTake(p_150648_: Int, p_150649_: Int, p_150650_: Player): ItemStack {
		val container = container

		if (container is ItemMonitorBlockEntity.CraftingResultContainer && p_150650_ is ServerPlayer) {
			container.craftingPlayer = p_150650_
		}

		val result: ItemStack

		try {
			result = super.safeTake(p_150648_, p_150649_, p_150650_)
		} finally {
			if (container is ItemMonitorBlockEntity.CraftingResultContainer) {
				container.craftingPlayer = null
			}
		}

		return result
	}

	override fun tryRemove(a: Int, b: Int, c: Player): Optional<ItemStack> {
		val container = container

		if (container is ItemMonitorBlockEntity.CraftingResultContainer && c is ServerPlayer) {
			container.craftingPlayer = c
		}

		val result: Optional<ItemStack>

		try {
			result = super.tryRemove(a, b, c)
		} finally {
			if (container is ItemMonitorBlockEntity.CraftingResultContainer) {
				container.craftingPlayer = null
			}
		}

		return result
	}
}

class ItemMonitorMenu(
	containerId: Int,
	inventory: Inventory,
	tile: ItemMonitorBlockEntity? = null
) : MatteryPoweredMenu(MMenus.ITEM_MONITOR, containerId, inventory, tile), INetworkedItemViewProvider {
	override val networkedItemView = NetworkedItemView(inventory.player, this, tile == null)
	val profiledEnergy = ProfiledLevelGaugeWidget(this, tile?.energy, energyWidget)

	val settings = object : IItemMonitorPlayerSettings {
		override var ingredientPriority by EnumInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(player as ServerPlayer)::ingredientPriority })
		override var resultTarget by EnumInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(player as ServerPlayer)::resultTarget })
		override var craftingAmount by EnumInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(player as ServerPlayer)::craftingAmount })
		override var sorting by EnumInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(player as ServerPlayer)::sorting }).also { it.addListener { changes() } }
		override var ascendingSort by BooleanInputWithFeedback(this@ItemMonitorMenu, true, tile?.let { it.getSettings(player as ServerPlayer)::ascendingSort }).also { it.addListener { changes() } }

		private fun changes() {
			if (ascendingSort) {
				networkedItemView.sorter = sorting
			} else {
				networkedItemView.sorter = sorting.reversed()
			}
		}
	}

	val craftingResult: MatterySlot
	val craftingSlots: List<MatterySlot>

	init {
		if (tile != null) {
			networkedItemView.component = tile.poweredView
		}

		craftingResult = ResultSlot(tile?.craftingResultContainer ?: SimpleContainer(1))
		craftingSlots = makeSlots(tile?.craftingGrid ?: SimpleContainer(9), ::MatterySlot)
		craftingSlots.forEach(this::addSlot)
		addSlot(craftingResult)

		addInventorySlots()
	}

	override fun removed(player: Player) {
		super.removed(player)
		networkedItemView.removed()
	}

	override fun broadcastChanges() {
		super.broadcastChanges()
		networkedItemView.network()
	}

	private fun moveCrafting(view: IStorageComponent<ItemStorageStack>, simulate: Boolean): Boolean {
		val itemStack = craftingResult.item
		var remaining: ItemStack

		if (settings.resultTarget == ItemMonitorPlayerSettings.ResultTarget.ALL_SYSTEM) {
			remaining = view.insertStack(ItemStorageStack(itemStack), simulate).toItemStack()
			remaining = moveItemStackToSlots(remaining, playerInventorySlots, simulate)
		} else {
			remaining = moveItemStackToSlots(itemStack, playerInventorySlots, simulate)
			remaining = view.insertStack(ItemStorageStack(remaining), simulate).toItemStack()
		}

		if (!simulate && remaining.isNotEmpty) {
			player.spawnAtLocation(remaining)
		}

		if (!simulate) {
			craftingResult.remove(itemStack.count)
		}

		return remaining.count != itemStack.count
	}

	override fun quickMoveStack(ply: Player, slotIndex: Int): ItemStack {
		if (playerInventorySlots.any { it.index == slotIndex }) {
			if (tile == null) return ItemStack.EMPTY
			val slot = slots[slotIndex]
			if (slot.item.isEmpty) return ItemStack.EMPTY

			val leftover = networkedItemView.component?.insertStack(ItemStorageStack(slot.item), false)?.toItemStack() ?: slot.item

			if (leftover.count == slot.item.count) {
				return ItemStack.EMPTY
			}

			val old = slot.item.copy()
			slot.item.count = leftover.count
			return old
		} else if (craftingSlots.any { it.index == slotIndex }) {
			// from crafting grid to inventory
			val item = slots[slotIndex].item
			if (item.isEmpty) return ItemStack.EMPTY

			var remainder = item

			when (settings.ingredientPriority) {
				ItemMonitorPlayerSettings.IngredientPriority.SYSTEM, ItemMonitorPlayerSettings.IngredientPriority.SYSTEM_FIRST -> {
					remainder = networkedItemView.component?.insertStack(ItemStorageStack(remainder), false)?.toItemStack() ?: remainder
				}

				else -> {}
			}

			remainder = moveItemStackToSlots(remainder, playerInventorySlots)

			slots[slotIndex].set(remainder)
			return if (remainder.count != item.count) item else ItemStack.EMPTY
		} else if (slotIndex == craftingResult.index) {
			var item = craftingResult.item
			if (item.isEmpty) return ItemStack.EMPTY
			val tile = tile as ItemMonitorBlockEntity? ?: return ItemStack.EMPTY

			if (tile.lastCraftingRecipe(ply) != null && tile.craftingRecipe != tile.lastCraftingRecipe(ply)) {
				// рецепт изменился
				return ItemStack.EMPTY
			}

			val crafted = tile.howMuchPlayerCrafted(ply)

			if (settings.craftingAmount == ItemMonitorPlayerSettings.Amount.ONE) {
				if (crafted > 0) {
					return ItemStack.EMPTY
				}
			} else {
				if (settings.craftingAmount == ItemMonitorPlayerSettings.Amount.STACK || tile.craftingGrid.any { !it.isStackable }) {
					if (crafted > 0 && (crafted + 1) * item.count > item.maxStackSize) {
						return ItemStack.EMPTY
					}
				} else {
					if (crafted > 0 && crafted >= tile.craftingGrid.iterator().map { it.maxStackSize }.reduce(Int.MAX_VALUE, Int::coerceAtMost)) {
						return ItemStack.EMPTY
					}
				}
			}

			tile.craftingResultContainer.craftingPlayer = ply as ServerPlayer

			try {
				if (moveCrafting(tile.poweredView ?: return ItemStack.EMPTY, true)) {
					item = item.copy()
					moveCrafting(tile.poweredView ?: return ItemStack.EMPTY, false)
					return item
				} else {
					return ItemStack.EMPTY
				}
			} finally {
				tile.craftingResultContainer.craftingPlayer = null
			}
		}

		return super.quickMoveStack(ply, slotIndex)
	}
}
