package ru.dbotthepony.mc.otm.block.entity

import com.google.common.collect.ImmutableSet
import net.minecraft.core.BlockPos
import net.minecraft.core.HolderLookup
import net.minecraft.nbt.CompoundTag
import net.minecraft.nbt.NbtOps
import net.minecraft.network.chat.Component
import net.minecraft.network.chat.ComponentSerialization
import net.minecraft.server.level.ServerLevel
import net.minecraft.world.MenuProvider
import net.minecraft.world.entity.player.Inventory
import net.minecraft.world.entity.player.Player
import net.minecraft.world.inventory.AbstractContainerMenu
import net.minecraft.world.item.ItemStack
import net.minecraft.world.level.Level
import net.minecraft.world.level.block.entity.BlockEntityType
import net.minecraft.world.level.block.state.BlockState
import net.neoforged.neoforge.capabilities.Capabilities
import net.neoforged.neoforge.fluids.FluidStack
import net.neoforged.neoforge.fluids.capability.IFluidHandler
import net.neoforged.neoforge.items.IItemHandler
import org.apache.logging.log4j.LogManager
import ru.dbotthepony.kommons.util.getValue
import ru.dbotthepony.kommons.util.setValue
import ru.dbotthepony.mc.otm.block.IBlockWithCustomName
import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.capability.energy.IMatteryEnergyStorage
import ru.dbotthepony.mc.otm.capability.item.CombinedItemHandler
import ru.dbotthepony.mc.otm.capability.item.EmptyItemHandler
import ru.dbotthepony.mc.otm.capability.item.UnmodifiableItemHandler
import ru.dbotthepony.mc.otm.capability.moveBetweenSlots
import ru.dbotthepony.mc.otm.capability.moveEnergy
import ru.dbotthepony.mc.otm.capability.moveFluid
import ru.dbotthepony.mc.otm.core.TextComponent
import ru.dbotthepony.mc.otm.core.getValue
import ru.dbotthepony.mc.otm.core.immutableMap
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.RelativeSide
import ru.dbotthepony.mc.otm.core.nbt.set
import ru.dbotthepony.mc.otm.core.util.ITickable
import ru.dbotthepony.mc.otm.once
import kotlin.jvm.optionals.getOrNull

/**
 * Device block entity base, implementing [MenuProvider] and [IRedstoneControlled], and also tracks custom display name
 */
@Suppress("LiftReturnOrAssignment")
abstract class MatteryDeviceBlockEntity(blockEntityType: BlockEntityType<*>, blockPos: BlockPos, blockState: BlockState) : MatteryBlockEntity(blockEntityType, blockPos, blockState), MenuProvider, IRedstoneControlled, IBlockWithCustomName {
	final override var customDisplayName: Component? = null

	override val redstoneControl: AbstractRedstoneControl = RedstoneControl { new, old ->
		markDirtyFast()

		if (new != old)
			redstoneStatusUpdated(new, old)
	}

	init {
		savetablesConfig.stateful(::redstoneControl, REDSTONE_CONTROL_KEY)
	}

	protected open val defaultDisplayName: Component
		get() = level?.getBlockState(blockPos)?.block?.name ?: TextComponent("null at $blockPos")

	abstract override fun createMenu(containerID: Int, inventory: Inventory, ply: Player): AbstractContainerMenu?

	override fun getDisplayName(): Component {
		return customDisplayName ?: defaultDisplayName
	}

	protected open fun redstoneStatusUpdated(newBlocked: Boolean, oldBlocked: Boolean) {}

	override fun saveShared(nbt: CompoundTag, registry: HolderLookup.Provider) {
		super.saveShared(nbt, registry)
		saveCustomDisplayName(nbt, registry)
	}

	override fun loadAdditional(nbt: CompoundTag, registry: HolderLookup.Provider) {
		super.loadAdditional(nbt, registry)
		loadCustomDisplayName(nbt, registry)
	}

	override fun setLevel(level: Level) {
		super.setLevel(level)

		if (level is ServerLevel) {
			level.once(4) {
				if (!isRemoved && this.level == level) {
					redstoneControl.redstoneSignal = level.getBestNeighborSignal(blockPos)
				}
			}
		}
	}

	inner class ConfigurableFluidHandler<T : IFluidHandler>(
		val capability: T,

		val possibleModes: FlowDirection = FlowDirection.BI_DIRECTIONAL,

		val frontDefault: FlowDirection = possibleModes,
		val backDefault: FlowDirection = possibleModes,
		val leftDefault: FlowDirection = possibleModes,
		val rightDefault: FlowDirection = possibleModes,
		val topDefault: FlowDirection = possibleModes,
		val bottomDefault: FlowDirection = possibleModes,
	) {
		init {
			exposeSideless(Capabilities.FluidHandler.BLOCK, capability)
		}

		val front = Piece(RelativeSide.FRONT).also { it.flow = frontDefault }
		val back = Piece(RelativeSide.BACK).also { it.flow = backDefault }
		val left = Piece(RelativeSide.LEFT).also { it.flow = leftDefault }
		val right = Piece(RelativeSide.RIGHT).also { it.flow = rightDefault }
		val top = Piece(RelativeSide.TOP).also { it.flow = topDefault }
		val bottom = Piece(RelativeSide.BOTTOM).also { it.flow = bottomDefault }

		val pieces = immutableMap {
			put(RelativeSide.FRONT, front)
			put(RelativeSide.BACK, back)
			put(RelativeSide.LEFT, left)
			put(RelativeSide.RIGHT, right)
			put(RelativeSide.TOP, top)
			put(RelativeSide.BOTTOM, bottom)
		}

		val defaults = immutableMap {
			put(RelativeSide.FRONT, frontDefault)
			put(RelativeSide.BACK, backDefault)
			put(RelativeSide.LEFT, leftDefault)
			put(RelativeSide.RIGHT, rightDefault)
			put(RelativeSide.TOP, topDefault)
			put(RelativeSide.BOTTOM, bottomDefault)
		}

		inner class Piece(val side: RelativeSide) : IFluidHandler, ITickable {
			private val ticker = tickList.Ticker(this)
			private val neighbour = CapabilityCache(side, Capabilities.FluidHandler.BLOCK)
			private val control = exposeSided(side, Capabilities.FluidHandler.BLOCK, this)

			private fun updateTickerState() {
				ticker.isEnabled =
					(automatePull || automatePush) &&
					flow != FlowDirection.NONE &&
					!redstoneControl.isBlockedByRedstone &&
					neighbour.isPresent
			}

			init {
				// https://tenor.com/view/simp-metal-gear-liquid-snake-running-gif-16717852
				savetablesConfig.enum(::flow, "fluid_${side}_flow", FlowDirection::valueOf)
				savetablesConfig.bool(::automatePull, "fluid_${side}_pull")
				savetablesConfig.bool(::automatePush, "fluid_${side}_push")
			}

			var flow by syncher.enum(possibleModes, setter = { access, value ->
				require(possibleModes.isSupertype(value)) { "Energy mode $value is not allowed (allowed modes: ${possibleModes.family})" }

				if (access.get() != value) {
					access.accept(value)
					markDirtyFast()
					updateTickerState()
					control.isEnabled = value != FlowDirection.NONE
				}
			})

			// var automatePull by synchronizer.bool().property
			var automatePull = false
				set(value) {
					field = value
					markDirtyFast()
					updateTickerState()
				}

			// var automatePush by synchronizer.bool().property
			var automatePush = false
				set(value) {
					field = value
					markDirtyFast()
					updateTickerState()
				}

			init {
				waitForServerLevel {
					redstoneControl.addListener(::updateTickerState)
					neighbour.addListener(::updateTickerState)

					updateTickerState()
				}
			}

			override fun tick() {
				if (flow == FlowDirection.NONE || !automatePull && !automatePush || redstoneControl.isBlockedByRedstone)
					return

				val it = neighbour.get() ?: return

				if (flow.input && automatePull) {
					moveFluid(source = it, destination = capability)
				}

				if (flow.output && automatePush) {
					moveFluid(source = capability, destination = it)
				}
			}

			override fun getTanks(): Int {
				if (flow == FlowDirection.NONE) {
					return 0
				} else {
					return capability.getTanks()
				}
			}

			override fun getFluidInTank(tank: Int): FluidStack {
				if (flow == FlowDirection.NONE) {
					return FluidStack.EMPTY
				} else {
					return capability.getFluidInTank(tank)
				}
			}

			override fun getTankCapacity(tank: Int): Int {
				if (flow == FlowDirection.NONE) {
					return 0
				} else {
					return capability.getTankCapacity(tank)
				}
			}

			override fun isFluidValid(tank: Int, stack: FluidStack): Boolean {
				if (flow.input) {
					return capability.isFluidValid(tank, stack)
				} else {
					return false
				}
			}

			override fun fill(resource: FluidStack, action: IFluidHandler.FluidAction): Int {
				if (flow.input) {
					return capability.fill(resource, action)
				} else {
					return 0
				}
			}

			override fun drain(resource: FluidStack, action: IFluidHandler.FluidAction): FluidStack {
				if (flow.output) {
					return capability.drain(resource, action)
				} else {
					return FluidStack.EMPTY
				}
			}

			override fun drain(maxDrain: Int, action: IFluidHandler.FluidAction): FluidStack {
				if (flow.output) {
					return capability.drain(maxDrain, action)
				} else {
					return FluidStack.EMPTY
				}
			}

			init {
				if (flow == FlowDirection.NONE) {
					control.isEnabled = false
				}
			}
		}
	}

	inner class ConfigurableEnergy<T : IMatteryEnergyStorage>(
		val energy: T,

		val modesFront: FlowDirection = energy.energyFlow,
		val modesBack: FlowDirection = energy.energyFlow,
		val modesLeft: FlowDirection = energy.energyFlow,
		val modesRight: FlowDirection = energy.energyFlow,
		val modesTop: FlowDirection = energy.energyFlow,
		val modesBottom: FlowDirection = energy.energyFlow,

		val frontDefault: FlowDirection = modesFront,
		val backDefault: FlowDirection = modesBack,
		val leftDefault: FlowDirection = modesLeft,
		val rightDefault: FlowDirection = modesRight,
		val topDefault: FlowDirection = modesTop,
		val bottomDefault: FlowDirection = modesBottom,

		/**
		 * If battery level can update without calling [markDirtyFast]/[setChanged]
		 */
		val volatileEnergyValues: Boolean = false
	) {
		constructor(
			energy: T,
			possibleModes: FlowDirection,
			frontDefault: FlowDirection = possibleModes,
			backDefault: FlowDirection = possibleModes,
			leftDefault: FlowDirection = possibleModes,
			rightDefault: FlowDirection = possibleModes,
			topDefault: FlowDirection = possibleModes,
			bottomDefault: FlowDirection = possibleModes,
			volatileEnergyValues: Boolean = false
		) : this(
			energy,
			modesFront = possibleModes, frontDefault = frontDefault,
			modesBack = possibleModes, backDefault = backDefault,
			modesLeft = possibleModes, leftDefault = leftDefault,
			modesRight = possibleModes, rightDefault = rightDefault,
			modesTop = possibleModes, topDefault = topDefault,
			modesBottom = possibleModes, bottomDefault = bottomDefault,
			volatileEnergyValues = volatileEnergyValues,
		)

		init {
			exposeEnergySideless(energy)
		}

		val front = Piece(RelativeSide.FRONT, modesFront).also { it.energyFlow = frontDefault }
		val back = Piece(RelativeSide.BACK, modesBack).also { it.energyFlow = backDefault }
		val left = Piece(RelativeSide.LEFT, modesLeft).also { it.energyFlow = leftDefault }
		val right = Piece(RelativeSide.RIGHT, modesRight).also { it.energyFlow = rightDefault }
		val top = Piece(RelativeSide.TOP, modesTop).also { it.energyFlow = topDefault }
		val bottom = Piece(RelativeSide.BOTTOM, modesBottom).also { it.energyFlow = bottomDefault }

		val pieces = immutableMap {
			put(RelativeSide.FRONT, front)
			put(RelativeSide.BACK, back)
			put(RelativeSide.LEFT, left)
			put(RelativeSide.RIGHT, right)
			put(RelativeSide.TOP, top)
			put(RelativeSide.BOTTOM, bottom)
		}

		val defaults = immutableMap {
			put(RelativeSide.FRONT, frontDefault)
			put(RelativeSide.BACK, backDefault)
			put(RelativeSide.LEFT, leftDefault)
			put(RelativeSide.RIGHT, rightDefault)
			put(RelativeSide.TOP, topDefault)
			put(RelativeSide.BOTTOM, bottomDefault)
		}

		inner class Piece(val side: RelativeSide, val possibleModes: FlowDirection) : IMatteryEnergyStorage, ITickable {
			private val neighbour = CapabilityCache(side, Capabilities.EnergyStorage.BLOCK)
			private val control = exposeEnergySided(side, this)

			override var batteryLevel: Decimal by energy::batteryLevel
			override val maxBatteryLevel: Decimal by energy::maxBatteryLevel
			override val missingPower: Decimal by energy::missingPower

			override val canSetBatteryLevel: Boolean by energy::canSetBatteryLevel

			private val ticker = tickList.Ticker(this)

			private fun updateTickerState() {
				ticker.isEnabled = (automatePull || automatePush) &&
					energyFlow != FlowDirection.NONE &&
					!redstoneControl.isBlockedByRedstone &&
					neighbour.isPresent &&
					(volatileEnergyValues || energy.batteryLevel.isPositive)
			}

			// var automatePull by synchronizer.bool().property
			var automatePull = false
				set(value) {
					field = value
					markDirtyFast()
					updateTickerState()
				}

			// var automatePush by synchronizer.bool().property
			var automatePush = false
				set(value) {
					field = value
					markDirtyFast()
					updateTickerState()
				}

			init {
				savetablesConfig.enum(::energyFlow, "energy_${side}_flow", FlowDirection::valueOf)
				savetablesConfig.bool(::automatePull, "energy_${side}_pull")
				savetablesConfig.bool(::automatePush, "energy_${side}_push")

				dirtyListeners.addListener(Runnable {
					updateTickerState()
				})

				waitForServerLevel {
					redstoneControl.addListener(::updateTickerState)
					neighbour.addListener(::updateTickerState)

					updateTickerState()
				}
			}

			override fun extractEnergy(howMuch: Decimal, simulate: Boolean): Decimal {
				return energy.extractEnergy(howMuch, simulate)
			}

			override fun receiveEnergy(howMuch: Decimal, simulate: Boolean): Decimal {
				return energy.receiveEnergy(howMuch, simulate)
			}

			override fun extractEnergyChecked(howMuch: Decimal, simulate: Boolean): Decimal {
				if (energyFlow.output)
					return energy.extractEnergyChecked(howMuch, simulate)

				return Decimal.ZERO
			}

			override fun receiveEnergyChecked(howMuch: Decimal, simulate: Boolean): Decimal {
				if (energyFlow.input)
					return energy.receiveEnergyChecked(howMuch, simulate)

				return Decimal.ZERO
			}

			override fun drainBattery(): Boolean {
				return energy.drainBattery()
			}

			override fun fillBattery(): Boolean {
				return energy.fillBattery()
			}

			override fun tick() {
				val it = neighbour.get() ?: return

				if (energyFlow.input && automatePull) {
					moveEnergy(source = it, destination = energy, simulate = false)
				}

				if (energyFlow.output && automatePush) {
					moveEnergy(source = energy, destination = it, simulate = false)
				}
			}

			override var energyFlow by syncher.enum(possibleModes, setter = { access, value ->
				require(possibleModes.isSupertype(value)) { "Energy mode $value is not allowed (allowed modes: ${possibleModes.family})" }

				if (access.get() != value) {
					access.accept(value)
					markDirtyFast()
					updateTickerState()
					control.isEnabled = value != FlowDirection.NONE
				}
			}).delegate

			init {
				if (energyFlow == FlowDirection.NONE) {
					control.isEnabled = false
				}
			}
		}
	}

	enum class ItemHandlerMode(val translationKey: String) {
		DISABLED("otm.gui.side_mode.disabled"),
		INPUT("otm.gui.side_mode.input"),
		OUTPUT("otm.gui.side_mode.output"),
		INPUT_OUTPUT("otm.gui.side_mode.input_output"),
		BATTERY("otm.gui.side_mode.battery");
	}

	inner class ConfigurableItemHandler(
		input: IItemHandler? = null,
		output: IItemHandler? = null,
		inputOutput: IItemHandler? = null,
		val battery: IItemHandler? = null,
		val frontDefault: ItemHandlerMode = determineDefaultMode(input, output, inputOutput, battery, RelativeSide.FRONT),
		val backDefault: ItemHandlerMode = determineDefaultMode(input, output, inputOutput, battery, RelativeSide.BACK),
		val leftDefault: ItemHandlerMode = determineDefaultMode(input, output, inputOutput, battery, RelativeSide.LEFT),
		val rightDefault: ItemHandlerMode = determineDefaultMode(input, output, inputOutput, battery, RelativeSide.RIGHT),
		val topDefault: ItemHandlerMode = determineDefaultMode(input, output, inputOutput, battery, RelativeSide.TOP),
		val bottomDefault: ItemHandlerMode = determineDefaultMode(input, output, inputOutput, battery, RelativeSide.BOTTOM),
	) {
		val sideless: IItemHandler
		val possibleViews: ImmutableSet<ItemHandlerMode>
		val inputOutput: IItemHandler?
		val input: IItemHandler?
		val output: IItemHandler?

		init {
			if ((input != null || output != null) && inputOutput != null)
				throw IllegalArgumentException("Either specify input or/and output separately, or specify inputOutput")

			if (inputOutput != null) {
				this.input = inputOutput
				this.output = inputOutput
			} else {
				this.input = input
				this.output = output
			}

			val builder = ImmutableSet.Builder<ItemHandlerMode>()

			builder.add(ItemHandlerMode.DISABLED)

			if (input != null) builder.add(ItemHandlerMode.INPUT)
			if (output != null) builder.add(ItemHandlerMode.OUTPUT)
			if (input != null && output != null) builder.add(ItemHandlerMode.INPUT_OUTPUT)

			if (inputOutput != null) {
				builder.add(ItemHandlerMode.INPUT)
				builder.add(ItemHandlerMode.OUTPUT)
				builder.add(ItemHandlerMode.INPUT_OUTPUT)
			}

			if (battery != null) builder.add(ItemHandlerMode.BATTERY)

			possibleViews = builder.build()

			if (input != null && output != null) {
				this.inputOutput = CombinedItemHandler(input, output)
			} else {
				this.inputOutput = inputOutput
			}

			val caps = ArrayList<IItemHandler>()

			if (input != null) caps.add(input)
			if (output != null) caps.add(output)
			if (inputOutput != null) caps.add(inputOutput)
			if (battery != null) caps.add(battery)

			sideless = UnmodifiableItemHandler(CombinedItemHandler(caps))
			exposeSideless(Capabilities.ItemHandler.BLOCK, sideless)
		}

		val front = Piece(RelativeSide.FRONT).also { it.mode = frontDefault }
		val back = Piece(RelativeSide.BACK).also { it.mode = backDefault }
		val left = Piece(RelativeSide.LEFT).also { it.mode = leftDefault }
		val right = Piece(RelativeSide.RIGHT).also { it.mode = rightDefault }
		val top = Piece(RelativeSide.TOP).also { it.mode = topDefault }
		val bottom = Piece(RelativeSide.BOTTOM).also { it.mode = bottomDefault }

		val pieces = immutableMap {
			put(RelativeSide.FRONT, front)
			put(RelativeSide.BACK, back)
			put(RelativeSide.LEFT, left)
			put(RelativeSide.RIGHT, right)
			put(RelativeSide.TOP, top)
			put(RelativeSide.BOTTOM, bottom)
		}

		val defaults = immutableMap {
			put(RelativeSide.FRONT, frontDefault)
			put(RelativeSide.BACK, backDefault)
			put(RelativeSide.LEFT, leftDefault)
			put(RelativeSide.RIGHT, rightDefault)
			put(RelativeSide.TOP, topDefault)
			put(RelativeSide.BOTTOM, bottomDefault)
		}

		inner class Piece(val side: RelativeSide) : IItemHandler, ITickable {
			private var currentHandler: IItemHandler = EmptyItemHandler
				set(value) {
					field = value
					updateTickerState()
				}

			private val neighbour = CapabilityCache(side, Capabilities.ItemHandler.BLOCK)
			private val ticker = tickList.Ticker(this)
			private val control = exposeSided(side, Capabilities.ItemHandler.BLOCK, this)

			private var innerSlotPull = 0
			private var outerSlotPull = 0

			private var innerSlotPush = 0
			private var outerSlotPush = 0

			private fun updateTickerState() {
				ticker.isEnabled =
					(automatePull || automatePush) &&
					mode != ItemHandlerMode.DISABLED &&
					!redstoneControl.isBlockedByRedstone &&
					currentHandler.slots != 0 &&
					neighbour.isPresent
			}

			var mode by syncher.enum(ItemHandlerMode.DISABLED, setter = { access, value ->
				require(value in possibleViews) { "View type $value is not allowed (allowed views: $possibleViews)" }

				if (access.get() != value) {
					access.accept(value)
					markDirtyFast()

					currentHandler = when (value) {
						ItemHandlerMode.DISABLED -> EmptyItemHandler
						ItemHandlerMode.INPUT -> input!!
						ItemHandlerMode.OUTPUT -> output!!
						ItemHandlerMode.INPUT_OUTPUT -> inputOutput!!
						ItemHandlerMode.BATTERY -> battery!!
					}

					control.isEnabled = value != ItemHandlerMode.DISABLED
				}
			}).delegate

			var automatePull = false
				set(value) {
					if (field != value) {
						field = value
						markDirtyFast()

						if (value) {
							innerSlotPush = 0
							outerSlotPush = 0
						}

						updateTickerState()
					}
				}

			var automatePush = false
				set(value) {
					if (field != value) {
						field = value
						markDirtyFast()

						if (value) {
							innerSlotPush = 0
							outerSlotPush = 0
						}

						updateTickerState()
					}
				}

			init {
				savetablesConfig.bool(::automatePull, "itemhandler_${side}_automatePull")
				savetablesConfig.bool(::automatePush, "itemhandler_${side}_automatePush")
				savetablesConfig.enum(::mode, "itemhandler_${side}_mode", ItemHandlerMode::valueOf)

				waitForServerLevel {
					redstoneControl.addListener(::updateTickerState)
					neighbour.addListener(::updateTickerState)

					updateTickerState()
				}
			}

			override fun tick() {
				if (mode == ItemHandlerMode.DISABLED || !automatePull && !automatePush || redstoneControl.isBlockedByRedstone || currentHandler.slots == 0)
					return

				val it = neighbour.get() ?: return

				if (it.slots == 0)
					return

				if (automatePull) {
					if (innerSlotPull !in 0 until currentHandler.slots)
						innerSlotPull = 0

					if (outerSlotPull !in 0 until it.slots)
						outerSlotPull = 0

					val (outerSlotPull, innerSlotPull) = moveBetweenSlots(it, outerSlotPull, currentHandler, innerSlotPull)
					this.innerSlotPull = innerSlotPull
					this.outerSlotPull = outerSlotPull
				}

				if (automatePush) {
					if (innerSlotPush !in 0 until currentHandler.slots)
						innerSlotPush = 0

					if (outerSlotPush !in 0 until it.slots)
						outerSlotPush = 0

					val (innerSlotPush, outerSlotPush) = moveBetweenSlots(currentHandler, innerSlotPush, it, outerSlotPush)
					this.innerSlotPush = innerSlotPush
					this.outerSlotPush = outerSlotPush
				}
			}

			override fun getSlots(): Int {
				return currentHandler.slots
			}

			override fun getStackInSlot(slot: Int): ItemStack {
				return currentHandler.getStackInSlot(slot)
			}

			override fun insertItem(slot: Int, stack: ItemStack, simulate: Boolean): ItemStack {
				return currentHandler.insertItem(slot, stack, simulate)
			}

			override fun extractItem(slot: Int, amount: Int, simulate: Boolean): ItemStack {
				return currentHandler.extractItem(slot, amount, simulate)
			}

			override fun getSlotLimit(slot: Int): Int {
				return currentHandler.getSlotLimit(slot)
			}

			override fun isItemValid(slot: Int, stack: ItemStack): Boolean {
				return currentHandler.isItemValid(slot, stack)
			}

			init {
				if (mode == ItemHandlerMode.DISABLED) {
					control.isEnabled = false
				}
			}
		}
	}

	companion object {
		private val LOGGER = LogManager.getLogger()

		private fun determineDefaultMode(input: IItemHandler?, output: IItemHandler?, inputOutput: IItemHandler?, battery: IItemHandler?, side: RelativeSide): ItemHandlerMode {
			if (side == RelativeSide.BACK && battery != null) {
				return ItemHandlerMode.BATTERY
			}

			if (input == null && output == null && inputOutput == null) {
				return ItemHandlerMode.DISABLED
			} else if (inputOutput != null) {
				return ItemHandlerMode.INPUT_OUTPUT
			} else if (input != null && output != null) {
				return when (side) {
					RelativeSide.FRONT, RelativeSide.BACK -> ItemHandlerMode.DISABLED
					RelativeSide.RIGHT, RelativeSide.TOP -> ItemHandlerMode.INPUT
					RelativeSide.LEFT, RelativeSide.BOTTOM -> ItemHandlerMode.OUTPUT
				}
			} else if (output != null) {
				if (side == RelativeSide.TOP) {
					return ItemHandlerMode.DISABLED
				} else {
					return ItemHandlerMode.OUTPUT
				}
			} else {
				if (side == RelativeSide.BOTTOM) {
					return ItemHandlerMode.DISABLED
				} else {
					return ItemHandlerMode.INPUT
				}
			}
		}
	}
}
