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

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 net.minecraftforge.common.capabilities.ForgeCapabilities
import ru.dbotthepony.mc.otm.block.entity.storage.DriveViewerBlockEntity
import ru.dbotthepony.mc.otm.capability.MatteryCapability
import ru.dbotthepony.mc.otm.capability.drive.IMatteryDrive
import ru.dbotthepony.mc.otm.capability.energy.ProfiledEnergyStorage
import ru.dbotthepony.mc.otm.container.ItemFilter
import ru.dbotthepony.mc.otm.core.GetterSetter
import ru.dbotthepony.mc.otm.core.asGetterSetter
import ru.dbotthepony.mc.otm.core.ifPresentK
import ru.dbotthepony.mc.otm.core.immutableList
import ru.dbotthepony.mc.otm.core.util.ItemStorageStackSorter
import ru.dbotthepony.mc.otm.item.PortableCondensationDriveItem
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.EnergyConfigPlayerInput
import ru.dbotthepony.mc.otm.menu.input.EnumInputWithFeedback
import ru.dbotthepony.mc.otm.menu.widget.ProfiledLevelGaugeWidget
import ru.dbotthepony.mc.otm.registry.MMenus
import ru.dbotthepony.mc.otm.storage.ItemStorageStack
import ru.dbotthepony.mc.otm.storage.StorageStack
import ru.dbotthepony.mc.otm.storage.powered.PoweredVirtualComponent

class DriveViewerMenu(
	containerID: Int,
	inventory: Inventory,
	tile: DriveViewerBlockEntity? = null
) : MatteryPoweredMenu(MMenus.DRIVE_VIEWER, containerID, inventory, tile), INetworkedItemViewProvider {
	override val networkedItemView = NetworkedItemView(inventory.player, this, tile == null)

	val driveSlot = object : MatterySlot(tile?.container ?: SimpleContainer(1), 0) {
		override fun mayPlace(itemStack: ItemStack): Boolean {
			return itemStack.getCapability(MatteryCapability.DRIVE).isPresent
		}
	}

	private val powered: PoweredVirtualComponent<ItemStorageStack>?
	private var lastDrive: IMatteryDrive<ItemStorageStack>? = null
	val profiledEnergy = ProfiledLevelGaugeWidget<ProfiledEnergyStorage<*>>(this, energyWidget)
	val energyConfig = EnergyConfigPlayerInput(this, tile?.energyConfig)

	val settings = object : DriveViewerBlockEntity.ISettings {
		override var sorting: ItemStorageStackSorter by EnumInputWithFeedback(this@DriveViewerMenu, true, tile?.let { it.getSettingsFor(player)::sorting }).also { it.addListener { changes() } }
		override var isAscending: Boolean by BooleanInputWithFeedback(this@DriveViewerMenu, true, tile?.let { it.getSettingsFor(player)::isAscending }).also { it.addListener { changes() } }

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

	init {
		if (tile != null) {
			profiledEnergy.with(tile.energy)
			powered = PoweredVirtualComponent(StorageStack.ITEMS, tile::energy)
			this.networkedItemView.component = powered
		} else {
			powered = null
		}

		addStorageSlot(driveSlot)
		addInventorySlots()
	}

	var drivePresent by mSynchronizer.bool().property

	private fun getFilter(): ItemFilter? {
		val stack = (tile as? DriveViewerBlockEntity)?.container?.getItem(0)
		return (stack?.item as? PortableCondensationDriveItem)?.getFilterSettings(stack)
	}

	val driveFilterSlots = immutableList(PortableCondensationDriveItem.MAX_FILTERS) { i ->
		GetterSetter.of(
			mSynchronizer.computedItem { getFilter()?.get(i) ?: ItemStack.EMPTY },
			itemStackInput { getFilter()?.set(i, it) }
		)
	}

	private fun make(mapper: (ItemFilter) -> GetterSetter<Boolean>): GetterSetter<Boolean> {
		return GetterSetter.of(
			{ getFilter()?.let(mapper)?.get() ?: false },
			{ getFilter()?.let(mapper)?.accept(it) }
		)
	}

	val isWhitelist = BooleanInputWithFeedback(this, make { it::isWhitelist.asGetterSetter() }).also { it.filter { drivePresent } }
	val matchTag = BooleanInputWithFeedback(this, make { it::matchTag.asGetterSetter() }).also { it.filter { drivePresent } }
	val matchNBT = BooleanInputWithFeedback(this, make { it::matchNBT.asGetterSetter() }).also { it.filter { drivePresent } }

	override fun broadcastChanges() {
		super.broadcastChanges()

		if (tile != null) {
			val itemStack = (tile as DriveViewerBlockEntity).container.getItem(0)

			val prev = lastDrive
			lastDrive = null

			if (!itemStack.isEmpty) {
				itemStack.getCapability(MatteryCapability.DRIVE).ifPresentK {
					if (it.storageType == StorageStack.ITEMS) {
						lastDrive = it as IMatteryDrive<ItemStorageStack>
					}
				}
			}

			drivePresent = lastDrive != null

			if (prev != lastDrive) {
				prev?.let(powered!!::remove)
				this.networkedItemView.clear()
				lastDrive?.let(powered!!::add)
			}

			this.networkedItemView.network()
		}
	}

	override fun removed(player: Player) {
		super.removed(player)
		lastDrive?.let { powered?.remove(it) }
		this.networkedItemView.removed()
	}

	override fun quickMoveStack(ply: Player, slotIndex: Int): ItemStack {
		val slot = slots[slotIndex]
		val item = slot.item

		if (item.isEmpty || item.getCapability(MatteryCapability.DRIVE).isPresent || item.getCapability(ForgeCapabilities.ENERGY).isPresent)
			return super.quickMoveStack(ply, slotIndex)

		val powered = powered
		if (lastDrive == null || powered == null)
			return ItemStack.EMPTY

		val remaining = powered.insertStack(ItemStorageStack(item), false)

		if (remaining.count.toInt() == item.count)
			return ItemStack.EMPTY

		if (remaining.isEmpty) {
			val copy = item.copy()
			slot.set(ItemStack.EMPTY)
			return copy
		}

		val copy = item.copy()
		item.shrink(remaining.count.toInt())
		slot.setChanged()
		return copy
	}
}
