package ru.dbotthepony.mc.otm.menu

import com.google.common.collect.ImmutableList
import net.minecraft.core.NonNullList
import net.minecraft.server.level.ServerLevel
import net.minecraft.server.level.ServerPlayer
import net.minecraft.world.Container
import net.minecraft.world.entity.ExperienceOrb
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.*
import net.minecraft.world.item.ItemStack
import ru.dbotthepony.mc.otm.capability.MatteryPlayerCapability
import ru.dbotthepony.mc.otm.compat.curios.curiosSlots
import ru.dbotthepony.mc.otm.container.util.slotIterator
import ru.dbotthepony.mc.otm.menu.input.InstantBooleanInput
import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget
import ru.dbotthepony.mc.otm.network.ExopackCarriedPacket
import ru.dbotthepony.mc.otm.network.ExopackMenuInitPacket
import ru.dbotthepony.mc.otm.network.ExopackSlotPacket
import ru.dbotthepony.mc.otm.network.MatteryPlayerNetworkChannel

class ExopackInventoryMenu(val capability: MatteryPlayerCapability) : MatteryMenu(null, CONTAINER_ID, capability.ply.inventory) {
	val craftingGrid: CraftingContainer
	val craftingSlots: List<MatterySlot>

	init {
		if (capability.isExopackCraftingUpgraded) {
			craftingGrid = TransientCraftingContainer(this, 3, 3)
		} else {
			craftingGrid = TransientCraftingContainer(this, 2, 2)
		}

		val builder = ImmutableList.builder<MatterySlot>()

		for (i in 0 until craftingGrid.containerSize) {
			builder.add(MatterySlot(craftingGrid, i))
		}

		craftingSlots = builder.build()
	}

	val craftingResultContainer = ResultContainer()
	val craftingResultSlot = ResultSlot(capability.ply, craftingGrid, craftingResultContainer, 0, 0, 0)

	init {
		addSlot(craftingResultSlot)

		if (capability.ply is ServerPlayer) {
			addSlotListener(capability.ply.containerListener)
		}
	}

	init {
		addInventorySlots(autoFrame = false)

		for (slot in playerCombinedInventorySlots) {
			mapQuickMove(slot, playerHotbarSlots)
		}

		for (slot in playerHotbarSlots) {
			mapQuickMove(slot, playerCombinedInventorySlots)
		}

		for (slot in craftingSlots) {
			mapQuickMove(slot, playerInventorySlots)
		}

		craftingSlots.forEach(this::addSlot)
	}

	val armorSlots = makeArmorSlots()
	val curiosSlots: ImmutableList<PlayerSlot<Slot, Slot>> = ImmutableList.copyOf(player.curiosSlots)

	init {
		for ((a, b) in curiosSlots) {
			addSlot(a)
			mapQuickMoveToInventory(a)

			if (b != null) {
				addSlot(b)
				mapQuickMoveToInventory(b)
			}
		}
	}

	private fun popFurnaceExp() {
		if (capability.isExopackSmeltingInstalled && capability.exopackSmelterExperience >= 1f && !player.level().isClientSide) {
			val whole = capability.exopackSmelterExperience.toInt()
			capability.exopackSmelterExperience -= whole
			ExperienceOrb.award(player.level() as ServerLevel, player.position(), whole)
		}
	}

	val furnaceInputs: List<MatterySlot> = capability.smelters.map {
		object : MatterySlot(it.input, 0) {
			override fun mayPlace(itemStack: ItemStack): Boolean {
				return super.mayPlace(itemStack) && capability.isExopackSmeltingInstalled
			}
		}
	}

	val furnaceOutputs: List<OutputSlot> = capability.smelters.map {
		object : OutputSlot(it.output, 0, onTake = { popFurnaceExp() }) {
			override fun mayPickup(player: Player): Boolean {
				return super.mayPickup(player) && capability.isExopackSmeltingInstalled
			}
		}
	}

	val furnaceProgress = capability.smelters.map { ProgressGaugeWidget(mSynchronizer, it) }
	val furnaceMenuOpenState = InstantBooleanInput(this)

	init {
		addStorageSlot(furnaceInputs, condition = furnaceMenuOpenState)
		addStorageSlot(furnaceOutputs, condition = furnaceMenuOpenState)
	}

	val enderChestSlots: List<MatterySlot>
	val enderChestOpenState = InstantBooleanInput(this)
	val playerEnderSortSettings = IItemStackSortingSettings.inputs(this, capability.enderSortingSettings)
	val sortEnderChest: SortInput?

	init {
		if (capability.isExopackEnderAccessInstalled) {
			enderChestSlots = makeSlots(player.enderChestInventory) { a, b ->
				MatterySlot(a, b).also {
					addStorageSlot(it, condition = enderChestOpenState)
				}
			}

			sortEnderChest = SortInput(player.enderChestInventory, playerEnderSortSettings)
		} else {
			enderChestSlots = listOf()
			sortEnderChest = null
		}
	}

	private var isRemoved = false

	override fun slotsChanged(container: Container) {
		if (isRemoved)
			return // кому это интересно

		super.slotsChanged(container)

		if (container == craftingGrid) {
			CraftingMenu.slotChangedCraftingGrid(this, capability.ply.level(), capability.ply, craftingGrid, craftingResultContainer)
		}
	}

	override fun setSynchronizer(synchronizer: ContainerSynchronizer) {
		if (synchronizer == Companion) {
			super.setSynchronizer(synchronizer)
		}
	}

	fun actuallyRemoved() {
		isRemoved = true
		removed(capability.ply)
	}

	override fun removed(player: Player) {
		if (player != capability.ply) {
			throw RuntimeException("what.")
		}

		super.removed(player)

		craftingResultContainer.clearContent()

		if (!player.level().isClientSide) {
			for (slot in craftingGrid.slotIterator()) {
				val leftover = moveItemStackToSlots(slot.item, playerInventorySlots)

				if (!leftover.isEmpty) {
					player.drop(leftover, true)
				}

				slot.remove()
			}
		}
	}

	init {
		if (capability.ply is ServerPlayer) {
			setSynchronizer(Companion)
		}
	}

	override fun stillValid(player: Player): Boolean {
		return true
	}

	override fun quickMoveStack(ply: Player, slotIndex: Int): ItemStack {
		if (!slots[slotIndex].hasItem() || !slots[slotIndex].mayPickup(ply)) {
			return ItemStack.EMPTY
		}

		if (slotIndex == craftingResultSlot.index) {
			val item = craftingResultSlot.item
			val leftover = moveItemStackToSlots(item, playerInventorySlots, simulate = true)

			if (leftover.isEmpty) {
				val copy = item.copy()
				moveItemStackToSlots(item, playerInventorySlots, simulate = false)
				item.count = 0
				craftingResultSlot.onTake(ply, copy)
				return copy
			}

			return ItemStack.EMPTY
		}

		return super.quickMoveStack(ply, slotIndex)
	}

	override fun canTakeItemForPickAll(itemStack: ItemStack, slot: Slot): Boolean {
		return slot.container != craftingResultContainer && super.canTakeItemForPickAll(itemStack, slot)
	}

	companion object : ContainerSynchronizer {
		// ServerPlayer#nextContainerCounter
		// this.containerCounter = this.containerCounter % 100 + 1;
		// значение ID никогда не будет больше 100
		const val CONTAINER_ID: Int = 101

		override fun sendInitialData(container: AbstractContainerMenu, itemStacks: NonNullList<ItemStack>, carried: ItemStack, remoteDataSlots: IntArray) {
			return sendInitialData(container as ExopackInventoryMenu, itemStacks, carried, remoteDataSlots)
		}

		override fun sendSlotChange(container: AbstractContainerMenu, slotId: Int, itemStack: ItemStack) {
			return sendSlotChange(container as ExopackInventoryMenu, slotId, itemStack)
		}

		override fun sendCarriedChange(container: AbstractContainerMenu, itemStack: ItemStack) {
			return sendCarriedChange(container as ExopackInventoryMenu, itemStack)
		}

		override fun sendDataChange(container: AbstractContainerMenu, dataSlotId: Int, shortData: Int) {
			return sendDataChange(container as ExopackInventoryMenu, dataSlotId, shortData)
		}

		fun sendInitialData(container: ExopackInventoryMenu, itemStacks: NonNullList<ItemStack>, carried: ItemStack, remoteDataSlots: IntArray) {
			MatteryPlayerNetworkChannel.send(container.player as ServerPlayer, ExopackMenuInitPacket(itemStacks, carried, container.incrementStateId()))
		}

		fun sendSlotChange(container: ExopackInventoryMenu, slotId: Int, itemStack: ItemStack) {
			if (container.slots[slotId].container != container.player.inventory || container.player.containerMenu is ExopackInventoryMenu)
				MatteryPlayerNetworkChannel.send(container.player as ServerPlayer, ExopackSlotPacket(slotId, itemStack, container.stateId))
		}

		fun sendCarriedChange(container: ExopackInventoryMenu, itemStack: ItemStack) {
			MatteryPlayerNetworkChannel.send(container.player as ServerPlayer, ExopackCarriedPacket(itemStack, container.stateId))
		}

		fun sendDataChange(container: ExopackInventoryMenu, dataSlotId: Int, shortData: Int) {
			throw UnsupportedOperationException("This should never be called. Dumbass")
		}
	}
}
