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.inventory.Slot
import net.minecraft.world.item.ItemStack
import net.minecraftforge.network.PacketDistributor
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.nonEmpty
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.network.MenuNetworkChannel
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 @JvmOverloads constructor(
	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 settings: ItemMonitorPlayerSettings = tile?.getSettings(inventory.player as ServerPlayer) ?: ItemMonitorPlayerSettings()
	private var settingsNetworked = false

	val craftingResult: MatterySlot
	val craftingSlots: List<MatterySlot>

	init {
		if (tile != null) {
			networkedItemView.setComponent(tile.poweredView)
			craftingResult = ResultSlot(tile.craftingResultContainer)
			craftingSlots = Collections.unmodifiableList(Array(9) { MatterySlot(tile.craftingGrid, it) }.asList())
		} else {
			craftingResult = ResultSlot(SimpleContainer(1))
			val container = SimpleContainer(9)
			craftingSlots = Collections.unmodifiableList(Array(9) { MatterySlot(container, it) }.asList())
		}

		val slots = craftingSlots.map(this::addSlot)
		addSlot(craftingResult)

		addInventorySlots()
	}

	override fun broadcastFullState() {
		super.broadcastFullState()
		MenuNetworkChannel.send(PacketDistributor.PLAYER.with { ply as ServerPlayer }, settings)
		settingsNetworked = true
	}

	fun sendSettingsToServer() {
		MenuNetworkChannel.sendToServer(settings)
	}

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

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

		if (!settingsNetworked) {
			MenuNetworkChannel.send(PacketDistributor.PLAYER.with { ply as ServerPlayer }, settings)
			settingsNetworked = true
		}
	}

	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.provider?.insertStack(ItemStackWrapper(slot.item), false)?.stack ?: 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 (slotIndex in craftingSlots[0].index .. craftingSlots[craftingSlots.size - 1].index) {
			// from crafting grid to inventory
			val item = slots[slotIndex].item

			if (item.isEmpty) {
				return ItemStack.EMPTY
			}

			var remainder = moveItemStackToSlots(item, playerInventorySlots)
			slots[slotIndex].set(remainder)

			if (remainder.isEmpty) {
				return item
			}

			remainder = networkedItemView.provider?.insertStack(ItemStackWrapper(remainder), false)?.stack ?: remainder
			slots[slotIndex].set(remainder)

			if (remainder.isEmpty) {
				return item
			}

			return if (remainder.count != item.count) item else ItemStack.EMPTY
		} else if (slotIndex == craftingResult.index) {
			// quickcraft... god damn it
			if (!craftingResult.hasItem()) {
				return ItemStack.EMPTY
			}

			val item = craftingResult.item

			val tile = tile as ItemMonitorBlockEntity? ?: return ItemStack.EMPTY

			if (tile.lastCraftingRecipe(ply) != null && tile.craftingRecipe != tile.lastCraftingRecipe(ply)) {
				// recipe has changed
				return ItemStack.EMPTY
			}

			if (settings.craftingAmount == ItemMonitorPlayerSettings.Amount.ONE) {
				if (tile.howMuchPlayerCrafted(ply) > 0) {
					return ItemStack.EMPTY
				}
			} else {
				var hasUnstackables = false
				var maxStack = 64

				if (settings.craftingAmount == ItemMonitorPlayerSettings.Amount.FULL) {
					for (gridItem in tile.craftingGrid.iterator().nonEmpty()) {
						if (!gridItem.isStackable) {
							hasUnstackables = true
							break
						}
					}
				} else {
					maxStack = 0

					for (gridItem in tile.craftingGrid.iterator().nonEmpty()) {
						maxStack = maxStack.coerceAtLeast(gridItem.maxStackSize)
					}
				}

				if (settings.craftingAmount == ItemMonitorPlayerSettings.Amount.STACK || hasUnstackables) {
					val count = tile.howMuchPlayerCrafted(ply)

					if (count > 0 && (count + 1) * craftingResult.item.count > craftingResult.item.maxStackSize) {
						return ItemStack.EMPTY
					}
				} else {
					val count = tile.howMuchPlayerCrafted(ply)

					if (count > 0 && count >= maxStack) {
						return ItemStack.EMPTY
					}
				}
			}

			tile.craftingResultContainer.craftingPlayer = ply as ServerPlayer

			try {
				when (settings.resultTarget) {
					ItemMonitorPlayerSettings.ResultTarget.ALL_SYSTEM -> {
						val wrapper = ItemStackWrapper(item)
						var remaining = tile.poweredView?.insertStack(wrapper, true)?.stack ?: return ItemStack.EMPTY

						if (remaining.isEmpty) {
							tile.poweredView!!.insertStack(wrapper, false)
							craftingResult.remove(item.count)
							return item
						}

						remaining = moveItemStackToSlots(remaining, playerInventorySlots, simulate = true)

						if (remaining.isEmpty) {
							remaining = tile.poweredView!!.insertStack(wrapper, false).stack
							moveItemStackToSlots(remaining, playerInventorySlots, simulate = false)
							craftingResult.remove(item.count)
							return item
						}

						return ItemStack.EMPTY
					}

					ItemMonitorPlayerSettings.ResultTarget.MIXED, ItemMonitorPlayerSettings.ResultTarget.ALL_INVENTORY -> {
						var remaining = moveItemStackToSlots(item, playerInventorySlots, simulate = true)

						if (remaining.isEmpty) {
							moveItemStackToSlots(item, playerInventorySlots, simulate = false)
							craftingResult.remove(item.count)
							return item
						}

						val wrapper = ItemStackWrapper(remaining)
						remaining = tile.poweredView?.insertStack(wrapper, true)?.stack ?: return ItemStack.EMPTY

						if (remaining.isEmpty) {
							moveItemStackToSlots(item, playerInventorySlots, simulate = false)
							tile.poweredView!!.insertStack(wrapper, false).stack
							craftingResult.remove(item.count)
							return item
						}

						return ItemStack.EMPTY
					}
				}
			} finally {
				tile.craftingResultContainer.craftingPlayer = null
			}
		}

		return super.quickMoveStack(ply, slotIndex)
	}
}
