package ru.dbotthepony.mc.otm.menu

import com.google.common.collect.ImmutableList
import com.mojang.datafixers.util.Pair
import net.minecraft.core.NonNullList
import net.minecraft.resources.ResourceLocation
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.iterator
import ru.dbotthepony.mc.otm.menu.input.InstantBooleanInput
import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
import ru.dbotthepony.mc.otm.menu.widget.ProgressGaugeWidget
import ru.dbotthepony.mc.otm.network.ExoSuitCarriedPacket
import ru.dbotthepony.mc.otm.network.ExoSuitMenuInitPacket
import ru.dbotthepony.mc.otm.network.ExoSuitSlotPacket
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 offhandSlot = object : InventorySlot(capability.ply.inventory, 40) {
		override fun getNoItemIcon(): Pair<ResourceLocation, ResourceLocation> {
			return Pair.of(InventoryMenu.BLOCK_ATLAS, InventoryMenu.EMPTY_ARMOR_SLOT_SHIELD)
		}
	}

	val armorSlots = makeArmorSlots()
	val allAccessibleSlots: List<Slot>

	init {
		mapQuickMove(offhandSlot, playerInventorySlots)
		addSlot(offhandSlot)

		val builder = ImmutableList.builder<Slot>()

		builder.addAll(playerInventorySlots)
		builder.addAll(craftingSlots)
		builder.add(offhandSlot)

		allAccessibleSlots = builder.build()
	}

	val curiosSlots: ImmutableList<PlayerSlot<Slot, Slot>> = ImmutableList.copyOf(ply.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 && !ply.level().isClientSide) {
			val whole = capability.exoPackSmelterExperience.toInt()
			capability.exoPackSmelterExperience -= whole
			ExperienceOrb.award(ply.level() as ServerLevel, ply.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<MachineOutputSlot> = capability.smelters.map {
		object : MachineOutputSlot(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 exoPackPowerSlot = BatterySlot(capability.exoPackEnergy.parent, 0)
	val exoPackPower = ProfiledLevelGaugeWidget(mSynchronizer, capability.exoPackEnergy)

	val furnaceMenuOpenState = InstantBooleanInput(this)

	val exoPackChargeSlots = makeSlots(capability.exoPackChargeSlots, ::ChargeSlot)

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

		addSlot(exoPackPowerSlot)
		mapQuickMoveToInventory(exoPackPowerSlot)

		exoPackChargeSlots.forEach { addSlot(it); mapQuickMoveToInventory(it) }
	}

	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) {
			val iterator = craftingGrid.iterator()

			for (itemStack in iterator) {
				val leftover = moveItemStackToSlots(itemStack, playerInventorySlots)

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

				iterator.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, playerInventorySlots2, simulate = true)

			if (leftover.isEmpty) {
				val copy = item.copy()
				moveItemStackToSlots(item, playerInventorySlots2, 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.ply as ServerPlayer, ExoSuitMenuInitPacket(itemStacks, carried, container.incrementStateId()))
		}

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

		fun sendCarriedChange(container: ExoPackInventoryMenu, itemStack: ItemStack) {
			MatteryPlayerNetworkChannel.send(container.ply as ServerPlayer, ExoSuitCarriedPacket(itemStack, container.stateId))
		}

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