package ru.dbotthepony.mc.otm.client.screen.panels.button

import com.mojang.blaze3d.platform.InputConstants
import net.minecraft.ChatFormatting
import net.minecraft.client.gui.screens.Screen
import net.minecraft.network.chat.Component
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.kommons.util.Delegate
import ru.dbotthepony.kommons.util.value
import ru.dbotthepony.mc.otm.block.entity.MatteryDeviceBlockEntity
import ru.dbotthepony.mc.otm.block.entity.RedstoneSetting
import ru.dbotthepony.mc.otm.capability.FlowDirection
import ru.dbotthepony.mc.otm.capability.addUpgradeTooltipLines
import ru.dbotthepony.mc.otm.client.CursorType
import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
import ru.dbotthepony.mc.otm.client.isCtrlDown
import ru.dbotthepony.mc.otm.client.isShiftDown
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.client.render.IGUIRenderable
import ru.dbotthepony.mc.otm.client.render.sprites.AbstractMatterySprite
import ru.dbotthepony.mc.otm.client.render.ItemStackIcon
import ru.dbotthepony.mc.otm.client.render.UVWindingOrder
import ru.dbotthepony.mc.otm.client.render.Widgets18
import ru.dbotthepony.mc.otm.client.screen.MatteryScreen
import ru.dbotthepony.mc.otm.client.screen.panels.Dock
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
import ru.dbotthepony.mc.otm.client.screen.panels.FramePanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.slot.SlotPanel
import ru.dbotthepony.mc.otm.client.screen.panels.util.GridPanel
import ru.dbotthepony.mc.otm.client.screen.tech.EssenceStorageScreen
import ru.dbotthepony.mc.otm.config.ClientConfig
import ru.dbotthepony.mc.otm.core.TextComponent
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.immutableList
import ru.dbotthepony.mc.otm.core.math.RelativeSide
import ru.dbotthepony.mc.otm.core.util.ItemStackSorter
import ru.dbotthepony.mc.otm.core.util.getLevelFromXp
import ru.dbotthepony.mc.otm.menu.MatteryMenu
import ru.dbotthepony.mc.otm.menu.MatterySlot
import ru.dbotthepony.mc.otm.menu.UpgradeSlots
import ru.dbotthepony.mc.otm.menu.input.BooleanInputWithFeedback
import ru.dbotthepony.mc.otm.menu.input.EnergyConfigPlayerInput
import ru.dbotthepony.mc.otm.menu.input.FluidConfigPlayerInput
import ru.dbotthepony.mc.otm.menu.input.IPlayerInputWithFeedback
import ru.dbotthepony.mc.otm.menu.input.ItemConfigPlayerInput
import ru.dbotthepony.mc.otm.menu.widget.TakeExperienceWidget
import java.util.*
import java.util.function.IntConsumer
import java.util.function.Predicate
import kotlin.NoSuchElementException
import kotlin.collections.ArrayList
import kotlin.collections.HashMap
import kotlin.math.ceil
import kotlin.math.max
import kotlin.math.pow

private val gunpowder = ItemStackIcon(ItemStack(Items.GUNPOWDER)).fixed()
private val barrier = ItemStackIcon(ItemStack(Items.BARRIER)).fixed()
private val redstone = ItemStackIcon(ItemStack(Items.REDSTONE)).fixed()
private val redstoneUnderBarrier = barrier.composeAfter(redstone).fixed()

private fun <S : MatteryScreen<*>> makeRedstoneSettingButton(
	screen: S,
	parent: EditablePanel<*>?,
	x: Float = 0f,
	y: Float = 0f,
	control: IPlayerInputWithFeedback<RedstoneSetting>
): EnumButtonPanel<S, RedstoneSetting> {
	return object : EnumButtonPanel<S, RedstoneSetting>(
		screen, parent, x = x, y = y,
		defaultValue = RedstoneSetting.LOW, enum = RedstoneSetting::class.java,
		prop = control, height = 18f, width = 18f,
	) {
		override var isDisabled: Boolean
			get() = !control.test(minecraft.player!!)
			set(value) {}

		init {
			add(RedstoneSetting.IGNORED, tooltip = RedstoneSetting.IGNORED.description, skinElement = if (ClientConfig.GUI.REDSTONE_CONTROLS_ITEM_ICONS) gunpowder else Widgets18.REDSTONE_IGNORED)
			add(RedstoneSetting.LOW, tooltip = RedstoneSetting.LOW.description, skinElement = if (ClientConfig.GUI.REDSTONE_CONTROLS_ITEM_ICONS) redstoneUnderBarrier else Widgets18.REDSTONE_LOW)
			add(RedstoneSetting.HIGH, tooltip = RedstoneSetting.HIGH.description, skinElement = if (ClientConfig.GUI.REDSTONE_CONTROLS_ITEM_ICONS) redstone else Widgets18.REDSTONE_HIGH)
		}
	}
}

private class PullPushButton<out S : MatteryScreen<*>, T : Enum<T>>(
	screen: S,
	parent: EditablePanel<*>?,
	x: Float = 0f,
	y: Float = 0f,
	enum: Class<T>,
	prop: Delegate<T>,
	defaultValue: T,
	val pullProp: BooleanInputWithFeedback,
	val pushProp: BooleanInputWithFeedback
) : EnumButtonPanel<S, T>(screen, parent, x = x, y = y, enum = enum, prop = prop, width = 18f, height = 18f, defaultValue = defaultValue) {
	init {
		if (pullProp.test(minecraft.player!!)) {
			tooltips.add(TranslatableComponent("otm.gui.sides.pull_help").withStyle(ChatFormatting.GRAY))
		}

		if (pushProp.test(minecraft.player!!)) {
			tooltips.add(TranslatableComponent("otm.gui.sides.push_help").withStyle(ChatFormatting.GRAY))
		}
	}

	data class State<T : Enum<T>>(val pull: Boolean, val push: Boolean, val value: T)
	private val sprites = HashMap<State<T>, AbstractMatterySprite>()

	fun addSprite(pull: Boolean, push: Boolean, value: T, sprite: AbstractMatterySprite) {
		sprites[State(pull, push, value)] = sprite
	}

	override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) {
		super.innerRender(graphics, mouseX, mouseY, partialTick)
		sprites[State(pullProp.value, pushProp.value, prop.get())]?.render(graphics, 0f, 0f, width, height)
	}

	override fun onClick(mouseButton: Int) {
		if (mouseButton != InputConstants.MOUSE_BUTTON_MIDDLE && minecraft.window.isShiftDown) {
			if (pullProp.test(minecraft.player!!)) {
				pullProp.switchValue()
			}
		} else if (mouseButton != InputConstants.MOUSE_BUTTON_MIDDLE && minecraft.window.isCtrlDown) {
			if (pushProp.test(minecraft.player!!)) {
				pushProp.switchValue()
			}
		} else {
			super.onClick(mouseButton)
		}
	}
}

private fun <S : MatteryScreen<*>> makeItemModeButton(screen: S, parent: FramePanel<S>, input: ItemConfigPlayerInput.Piece, side: RelativeSide): EnumButtonPanel<S, MatteryDeviceBlockEntity.ItemHandlerMode> {
	val button = PullPushButton(screen, parent, enum = MatteryDeviceBlockEntity.ItemHandlerMode::class.java, prop = input.input, defaultValue = input.default, pullProp = input.pull, pushProp = input.push)
	val widgets = Widgets18.CONTROLS[side]!!

	for (v in MatteryDeviceBlockEntity.ItemHandlerMode.entries) {
		button.add(v, tooltip = TranslatableComponent(v.translationKey))
		button.addSprite(false, false, v, widgets.items[v]!!)
		button.addSprite(true, false, v, widgets.itemsPull[v]!!)
		button.addSprite(false, true, v, widgets.itemsPush[v]!!)
		button.addSprite(true, true, v, widgets.itemsPullPush[v]!!)
	}

	button.finish()
	button.predicate = Predicate { input.isAllowed(it) }

	return button
}

private fun <S : MatteryScreen<*>> makeEnergyModeButton(screen: S, parent: FramePanel<S>, input: EnergyConfigPlayerInput.Piece, side: RelativeSide): EnumButtonPanel<S, FlowDirection> {
	val button = PullPushButton(screen, parent, enum = FlowDirection::class.java, prop = input.input, defaultValue = input.default, pullProp = input.pull, pushProp = input.push)
	val widgets = Widgets18.CONTROLS[side]!!

	for (v in FlowDirection.entries) {
		button.add(v, tooltip = TranslatableComponent(v.translationKey))
		button.addSprite(false, false, v, widgets.flow[v]!!)
		button.addSprite(true, false, v, widgets.flowPull[v]!!)
		button.addSprite(false, true, v, widgets.flowPush[v]!!)
		button.addSprite(true, true, v, widgets.flowPullPush[v]!!)
	}

	button.finish()

	if (input.possibleModes == FlowDirection.NONE) {
		button.visible = false
	}

	button.predicate = Predicate { input.possibleModes.isSupertype(it) }

	return button
}

private fun <S : MatteryScreen<*>> makeFluidModeButton(screen: S, parent: FramePanel<S>, input: FluidConfigPlayerInput.Piece, side: RelativeSide): EnumButtonPanel<S, FlowDirection> {
	val button = PullPushButton(screen, parent, enum = FlowDirection::class.java, prop = input.input, defaultValue = input.default, pullProp = input.pull, pushProp = input.push)
	val widgets = Widgets18.CONTROLS[side]!!

	for (v in FlowDirection.entries) {
		button.add(v, tooltip = TranslatableComponent(v.translationKey))
		button.addSprite(false, false, v, widgets.flow[v]!!)
		button.addSprite(true, false, v, widgets.flowPull[v]!!)
		button.addSprite(false, true, v, widgets.flowPush[v]!!)
		button.addSprite(true, true, v, widgets.flowPullPush[v]!!)
	}

	button.finish()

	return button
}

private fun moveButtons(
	front: EditablePanel<*>,
	back: EditablePanel<*>,
	left: EditablePanel<*>,
	right: EditablePanel<*>,
	top: EditablePanel<*>,
	bottom: EditablePanel<*>,
) {
	top.tooltips.add(0, TranslatableComponent("otm.gui.sides.top"))
	bottom.tooltips.add(0, TranslatableComponent("otm.gui.sides.bottom"))
	back.tooltips.add(0, TranslatableComponent("otm.gui.sides.back"))
	front.tooltips.add(0, TranslatableComponent("otm.gui.sides.front"))
	left.tooltips.add(0, TranslatableComponent("otm.gui.sides.left"))
	right.tooltips.add(0, TranslatableComponent("otm.gui.sides.right"))

	top.x = 30f
	top.y = 14f

	right.x = 30f - 20f
	right.y = 14f + 20f

	left.x = 30f + 20f
	left.y = 14f + 20f

	front.x = 30f
	front.y = 14f + 20f

	bottom.x = 30f
	bottom.y = 14f + 42f

	back.x = 30f - 20f
	back.y = 14f + 42f
}

private fun <S : MatteryScreen<*>> makeItemHandlerControlPanel(
	screen: S,
	inputs: ItemConfigPlayerInput
): FramePanel<S> {
	val frame = object : FramePanel<S>(screen, 78f, 80f, TranslatableComponent("otm.gui.sides.item_config")) {
		override fun tickInner() {
			super.tickInner()

			if (!isEverFocused()) {
				remove()
			}
		}
	}

	val front = makeItemModeButton(screen, frame, inputs.pieces[RelativeSide.FRONT]!!, RelativeSide.FRONT)
	val back = makeItemModeButton(screen, frame, inputs.pieces[RelativeSide.BACK]!!, RelativeSide.BACK)
	val left = makeItemModeButton(screen, frame, inputs.pieces[RelativeSide.LEFT]!!, RelativeSide.LEFT)
	val right = makeItemModeButton(screen, frame, inputs.pieces[RelativeSide.RIGHT]!!, RelativeSide.RIGHT)
	val top = makeItemModeButton(screen, frame, inputs.pieces[RelativeSide.TOP]!!, RelativeSide.TOP)
	val bottom = makeItemModeButton(screen, frame, inputs.pieces[RelativeSide.BOTTOM]!!, RelativeSide.BOTTOM)

	moveButtons(front, back, left, right, top, bottom)
	screen.addPanel(frame)
	frame.requestFocus()
	frame.closeOnEscape = true

	return frame
}

private fun <S : MatteryScreen<*>> makeEnergyConfigPanel(
	screen: S,
	inputs: EnergyConfigPlayerInput
): FramePanel<S> {
	val frame = object : FramePanel<S>(screen, 78f, 80f, TranslatableComponent("otm.gui.sides.energy_config")) {
		override fun tickInner() {
			super.tickInner()

			if (!isEverFocused()) {
				remove()
			}
		}
	}

	val front = makeEnergyModeButton(screen, frame, inputs.pieces[RelativeSide.FRONT]!!, RelativeSide.FRONT)
	val back = makeEnergyModeButton(screen, frame, inputs.pieces[RelativeSide.BACK]!!, RelativeSide.BACK)
	val left = makeEnergyModeButton(screen, frame, inputs.pieces[RelativeSide.LEFT]!!, RelativeSide.LEFT)
	val right = makeEnergyModeButton(screen, frame, inputs.pieces[RelativeSide.RIGHT]!!, RelativeSide.RIGHT)
	val top = makeEnergyModeButton(screen, frame, inputs.pieces[RelativeSide.TOP]!!, RelativeSide.TOP)
	val bottom = makeEnergyModeButton(screen, frame, inputs.pieces[RelativeSide.BOTTOM]!!, RelativeSide.BOTTOM)

	moveButtons(front, back, left, right, top, bottom)
	screen.addPanel(frame)
	frame.requestFocus()
	frame.closeOnEscape = true

	return frame
}

private fun <S : MatteryScreen<*>> makeFluidConfigPanel(
	screen: S,
	inputs: FluidConfigPlayerInput
): FramePanel<S> {
	val frame = object : FramePanel<S>(screen, 78f, 80f, TranslatableComponent("otm.gui.sides.fluid_config")) {
		override fun tickInner() {
			super.tickInner()

			if (!isEverFocused()) {
				remove()
			}
		}
	}

	val front = makeFluidModeButton(screen, frame, inputs.pieces[RelativeSide.FRONT]!!, RelativeSide.FRONT).also { it.predicate = Predicate { inputs.possibleModes.isSupertype(it) } }
	val back = makeFluidModeButton(screen, frame, inputs.pieces[RelativeSide.BACK]!!, RelativeSide.BACK).also { it.predicate = Predicate { inputs.possibleModes.isSupertype(it) } }
	val left = makeFluidModeButton(screen, frame, inputs.pieces[RelativeSide.LEFT]!!, RelativeSide.LEFT).also { it.predicate = Predicate { inputs.possibleModes.isSupertype(it) } }
	val right = makeFluidModeButton(screen, frame, inputs.pieces[RelativeSide.RIGHT]!!, RelativeSide.RIGHT).also { it.predicate = Predicate { inputs.possibleModes.isSupertype(it) } }
	val top = makeFluidModeButton(screen, frame, inputs.pieces[RelativeSide.TOP]!!, RelativeSide.TOP).also { it.predicate = Predicate { inputs.possibleModes.isSupertype(it) } }
	val bottom = makeFluidModeButton(screen, frame, inputs.pieces[RelativeSide.BOTTOM]!!, RelativeSide.BOTTOM).also { it.predicate = Predicate { inputs.possibleModes.isSupertype(it) } }

	moveButtons(front, back, left, right, top, bottom)
	screen.addPanel(frame)
	frame.requestFocus()
	frame.closeOnEscape = true

	return frame
}

class DeviceControls<out S : MatteryScreen<*>>(
	screen: S,
	parent: EditablePanel<*>,
	extra: Iterable<EditablePanel<S>> = listOf(),
	val redstoneConfig: IPlayerInputWithFeedback<RedstoneSetting>? = null,
	val itemConfig: ItemConfigPlayerInput? = null,
	val energyConfig: EnergyConfigPlayerInput? = null,
	val fluidConfig: FluidConfigPlayerInput? = null,
	val balanceInputs: BooleanInputWithFeedback? = null,
	val upgrades: UpgradeSlots? = null,
	val experience: TakeExperienceWidget? = null,
	var dockOnLeft: Boolean = false,
) : EditablePanel<S>(screen, parent, x = parent.width + 3f, height = 0f, width = 0f) {
	val itemConfigButton: ButtonPanel<S>?
	val energyConfigButton: ButtonPanel<S>?
	val fluidConfigButton: ButtonPanel<S>?
	val redstoneControlsButton: EnumButtonPanel<S, RedstoneSetting>?
	val balanceInputsButton: BooleanButtonPanel<S>?
	val upgradesButton: ButtonPanel<S>?
	val experienceButton: ButtonPanel<S>?

	init {
		childrenOrder = -1000
	}

	private var upgradeWindow: FramePanel<S>? = null

	override val ignoreWhenSizingToContents: Boolean
		get() = true

	private val buttons = ArrayList<EditablePanel<S>>()

	private fun alignButtons() {
		buttons.removeIf { it.isRemoved || it.parent != this }

		var y = 0f
		var x = 0f
		var currentWidth = 0f
		var totalWidth = 0f
		var totalHeight = 0f

		val parent = parent!!

		for (button in buttons) {
			if (y != 0f && button.height + y > parent.height && (parent.dock == Dock.NONE || !parent.layoutInvalidated)) {
				totalHeight = totalHeight.coerceAtLeast(y - 2f)
				y = 0f
				x += currentWidth + 2f
				totalWidth += currentWidth
				currentWidth = 0f
			}

			button.setPos(x + button.dockLeft, y + button.dockTop)
			y += button.height + button.dockMargin.vertical + 2f
			currentWidth = currentWidth.coerceAtLeast(button.width + button.dockMargin.horizontal)
		}

		if (currentWidth != 0f) {
			totalWidth += currentWidth
		}

		if (y != 0f) {
			totalHeight = totalHeight.coerceAtLeast(y - 2f)
		}

		width = max(totalWidth, width)
		height = max(totalHeight, height)
	}

	fun removeButton(button: EditablePanel<*>) {
		buttons.remove(button)
		alignButtons()
	}

	fun <P : EditablePanel<@UnsafeVariance S>> addButton(button: P): P {
		buttons.add(button)
		button.parent = this
		alignButtons()
		return button
	}

	fun <P : EditablePanel<@UnsafeVariance S>> addButton(button: P, after: EditablePanel<*>): P {
		val index = buttons.indexOf(after)
		if (index == -1) throw NoSuchElementException("Unknown panel to add button after: $after")
		buttons.add(index + 1, button)
		button.parent = this
		alignButtons()
		return button
	}

	fun <P : EditablePanel<@UnsafeVariance S>> prependButton(button: P): P {
		buttons.add(0, button)
		button.parent = this
		alignButtons()
		return button
	}

	override fun performLayout() {
		super.performLayout()
		alignButtons()
	}

	fun addStorageMode(prop: Delegate<FlowDirection>) {
		val mode = EnumButtonPanel.square18(screen, this, prop = prop, defaultValue = FlowDirection.BI_DIRECTIONAL, enum = FlowDirection::class.java)

		mode.add(FlowDirection.INPUT, Widgets18.ONLY_STORE, FlowDirection.INPUT.title)
		mode.add(FlowDirection.OUTPUT, Widgets18.ONLY_EXTRACT, FlowDirection.OUTPUT.title)
		mode.add(FlowDirection.BI_DIRECTIONAL, Widgets18.STORE_EXTRACT, FlowDirection.BI_DIRECTIONAL.title)
		mode.finish()

		addButton(mode)
	}

	inline fun <reified T : Enum<T>> sortingButtons(ascending: Delegate<Boolean>, sorting: Delegate<T>, default: T, configurator: EnumButtonPanel<S, T>.() -> Unit): List<EditablePanel<S>> {
		val result = ArrayList<EditablePanel<S>>()

		BooleanButtonPanel.square18(
			screen,
			this,
			prop = ascending,
			iconActive = Widgets18.SORT_ASCENDING,
			iconInactive = Widgets18.SORT_DESCENDING,
			tooltipActive = TranslatableComponent("otm.gui.sorting.ascending"),
			tooltipInactive = TranslatableComponent("otm.gui.sorting.descending"),
		).also {
			prependButton(it)
			result.add(it)
		}

		EnumButtonPanel.square18(screen, this, enum = T::class.java, prop = sorting, defaultValue = default).also {
			prependButton(it)
			configurator.invoke(it)
			it.finish()
			result.add(it)
		}

		return result
	}

	fun sortingButtons(input: MatteryMenu.SortInput, unfoldableSettings: Boolean = true) {
		object : ButtonPanel<S>(screen, this@DeviceControls, width = 18f, height = 18f) {
			var buttons: List<EditablePanel<*>>? = null

			init {
				tooltips.add(TranslatableComponent("otm.gui.sorting.sort_now"))

				if (unfoldableSettings) {
					tooltips.add(TextComponent(""))
					tooltips.add(TranslatableComponent("otm.gui.sorting.sort_settings").withStyle(ChatFormatting.GRAY))
				}
				addButton(this)

				if (!unfoldableSettings) {
					makeButtons()
				}
			}

			override val icon: IGUIRenderable
				get() = Widgets18.SORT_NOW

			override fun test(value: Int): Boolean {
				return value == InputConstants.MOUSE_BUTTON_LEFT || unfoldableSettings && value == InputConstants.MOUSE_BUTTON_RIGHT
			}

			override var isDisabled: Boolean
				get() { return !input.input.test(minecraft.player ?: return false) }
				set(value) {}

			private fun makeButtons() {
				buttons = sortingButtons(Delegate.Of(input.settings::isAscending), Delegate.Of(input.settings::sorting), ItemStackSorter.DEFAULT) {
					for (v in ItemStackSorter.entries) {
						add(v, v.icon, v.title)
					}

					finish()
				}

				buttons!!.forEach { removeButton(it) }
				buttons!!.forEach { addButton(it as EditablePanel<S>, this) }
			}

			override fun onClick(mouseButton: Int) {
				if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) {
					input.clientInput()
				} else {
					if (buttons == null) {
						makeButtons()
					} else {
						buttons!!.forEach { it.remove() }
						buttons = null
					}
				}
			}
		}
	}

	init {
		for (button in extra) {
			addButton(button)
		}

		if (redstoneConfig != null) {
			redstoneControlsButton = addButton(makeRedstoneSettingButton(screen, this, control = redstoneConfig))
		} else {
			redstoneControlsButton = null
		}

		if (upgrades != null) {
			upgradesButton = addButton(object : ButtonPanel<S>(screen, this@DeviceControls, width = 18f, height = 18f) {
				init {
					tooltips.add(TranslatableComponent("otm.gui.upgrades"))
				}

				override val icon: IGUIRenderable
					get() = Widgets18.UPGRADES

				override fun innerRenderTooltips(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean {
					graphics.renderComponentTooltip(font, ArrayList<Component>().also {
						it.add(TranslatableComponent("otm.gui.upgrades"))
						it.add(TextComponent(""))

						if (upgrades.allowedTypes.isEmpty()) {
							it.add(TranslatableComponent("otm.gui.upgrade_type.allowed_none").withStyle(ChatFormatting.DARK_GRAY))
						} else {
							it.add(TranslatableComponent("otm.gui.upgrade_type.allowed").withStyle(ChatFormatting.GRAY))

							for (upgrade in upgrades.allowedTypes) {
								it.add(upgrade.component.copy().withStyle(ChatFormatting.GRAY))
							}
						}

						val i = it.size
						upgrades.currentStats.addUpgradeTooltipLines(it::add)

						if (it.size != i) {
							it.add(i, TranslatableComponent("otm.gui.upgrades.current").withStyle(ChatFormatting.GRAY))
							it.add(i, TextComponent(""))
						}
					}, mouseX.toInt(), mouseY.toInt())

					return true
				}

				override fun onClick(mouseButton: Int) {
					if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) {
						if (upgradeWindow != null && !upgradeWindow!!.isRemoved) {
							upgradeWindow!!.toScreenCenter()
							upgradeWindow!!.popup()
						} else {
							val (columns, rows) = SPECIAL_UPGRADE_CASES.getOrElse(upgrades.slots.size - 1) {
								val square = ceil(it.toDouble().pow(0.5)).toInt()
								square to square
							}

							upgradeWindow = FramePanel(screen, (columns * AbstractSlotPanel.SIZE + 40f).coerceAtLeast(120f), 30f + rows * AbstractSlotPanel.SIZE, TranslatableComponent("otm.gui.upgrades")).also { frame ->
								val grid = GridPanel(screen, frame, columns = columns, rows = rows)

								for (slot in upgrades.slots) {
									object : SlotPanel<S, MatterySlot>(screen, grid, slot) {
										override val cursorType: CursorType
											get() = if (upgrades.areLocked.get()) CursorType.NOT_ALLOWED else super.cursorType
									}
								}

								grid.dock = Dock.FILL

								screen.addPanel(frame)

								val parentFrame = this@DeviceControls.parent!!

								frame.behaveAsWindow()
								frame.setPos(parentFrame.absoluteX + parentFrame.width / 2f - frame.width / 2f, parentFrame.absoluteY + parentFrame.height / 2f - frame.height / 2f)
								frame.popup()

								upgrades.openState.value = true
								frame.onClose { upgrades.openState.value = false }

								parentFrame.blockingWindow = frame
							}
						}
					}
				}
			})
		} else {
			upgradesButton = null
		}

		if (balanceInputs != null) {
			balanceInputsButton = addButton(BooleanButtonPanel.square18(
				screen, this,
				prop = balanceInputs,
				iconActive = Widgets18.BALANCING_ENABLED,
				iconInactive = Widgets18.BALANCING_DISABLED).also {
					it.tooltips.add(TranslatableComponent("otm.gui.balance_inputs"))
			})
		} else {
			balanceInputsButton = null
		}

		if (itemConfig != null) {
			itemConfigButton = addButton(ButtonPanel.square18(
				screen,
				this@DeviceControls,
				icon = Widgets18.ITEMS_CONFIGURATION,
				onPress = IntConsumer { mouseButton ->
					if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) {
						val frame = makeItemHandlerControlPanel(screen, itemConfig)

						frame.x = absoluteX + width / 2f - frame.width / 2f
						frame.y = absoluteY + height + 8f
					}
				}
			).also {
				it.tooltips.add(TranslatableComponent("otm.gui.sides.item_config"))
			})
		} else {
			itemConfigButton = null
		}

		if (energyConfig != null) {
			energyConfigButton = addButton(ButtonPanel.square18(
				screen,
				this@DeviceControls,
				icon = Widgets18.ENERGY_CONFIGURATION,
				onPress = IntConsumer { mouseButton ->
					if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) {
						val frame = makeEnergyConfigPanel(screen, energyConfig)

						frame.x = absoluteX + width / 2f - frame.width / 2f
						frame.y = absoluteY + height + 8f
					}
				}
			).also {
				it.tooltips.add(TranslatableComponent("otm.gui.sides.energy_config"))
			})
		} else {
			energyConfigButton = null
		}

		if (fluidConfig != null) {
			fluidConfigButton = addButton(ButtonPanel.square18(
				screen,
				this@DeviceControls,
				icon = Widgets18.FLUID_CONFIGURATION,
				onPress = IntConsumer { mouseButton ->
					if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) {
						val frame = makeFluidConfigPanel(screen, fluidConfig)

						frame.x = absoluteX + width / 2f - frame.width / 2f
						frame.y = absoluteY + height + 8f
					}
				}
			).also {
				it.tooltips.add(TranslatableComponent("otm.gui.sides.fluid_config"))
			})
		} else {
			fluidConfigButton = null
		}

		if (experience != null) {
			experienceButton = addButton(ExperienceButton(experience))
		} else {
			experienceButton = null
		}
	}

	private inner class ExperienceButton(val widget: TakeExperienceWidget) : ButtonPanel<S>(this@DeviceControls.screen, this@DeviceControls, 0f, 0f, 18f, 18f) {
		override fun onClick(mouseButton: Int) {
			widget.input.accept(null)
		}

		override val icon: IGUIRenderable
			get() = EssenceStorageScreen.DISPENSE_ALL

		override var isDisabled: Boolean
			get() = !widget.input.test(minecraft.player!!)
			set(value) {}

		override fun innerRenderTooltips(
			graphics: MGUIGraphics,
			mouseX: Float,
			mouseY: Float,
			partialTick: Float
		): Boolean {
			if (widget.maxAmount.get().isInfinite()) {
				if (minecraft.window.isShiftDown) {
					graphics.renderComponentTooltip(
						font,
						listOf(TranslatableComponent("otm.gui.experience_levels", getLevelFromXp(widget.amount.get().toLong()).toString())),
						mouseX.toInt(),
						mouseY.toInt()
					)
				} else {
					graphics.renderComponentTooltip(
						font,
						listOf(TranslatableComponent("otm.gui.experience", "%.1f".format(Locale.ROOT, widget.amount.get()))),
						mouseX.toInt(),
						mouseY.toInt()
					)
				}
			} else {
				if (minecraft.window.isShiftDown) {
					graphics.renderComponentTooltip(
						font,
						listOf(TranslatableComponent("otm.gui.experience_levels_with_limit", getLevelFromXp(widget.amount.get().toLong()).toString(), "~" + getLevelFromXp(widget.maxAmount.get().toLong()).toString())),
						mouseX.toInt(),
						mouseY.toInt()
					)
				} else {
					graphics.renderComponentTooltip(
						font,
						listOf(TranslatableComponent("otm.gui.experience_with_limit", "%.1f".format(Locale.ROOT, widget.amount.get()), widget.maxAmount.get().toString())),
						mouseX.toInt(),
						mouseY.toInt()
					)
				}
			}

			return true
		}
	}

	override fun preRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) {
		if (parent is FramePanel<*>) {
			if (dockOnLeft) {
				x = -width - 3f
			} else {
				x = parent!!.width + 3f
			}

			y = dockTop
		}
	}

	// не съедаем ввод мыши
	override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean {
		return false
	}

	override fun mouseReleasedInner(x: Double, y: Double, button: Int): Boolean {
		return false
	}

	override fun mouseDraggedInner(x: Double, y: Double, button: Int, xDelta: Double, yDelta: Double): Boolean {
		return false
	}

	companion object {
		val SPECIAL_UPGRADE_CASES = immutableList {
			accept(1 to 1)
			accept(2 to 1)
			accept(3 to 1)
			accept(2 to 2)
		}
	}
}

fun <S : MatteryScreen<*>> makeDeviceControls(
	screen: S,
	parent: FramePanel<S>,
	extra: Iterable<EditablePanel<S>> = listOf(),
	redstoneConfig: IPlayerInputWithFeedback<RedstoneSetting>? = null,
	itemConfig: ItemConfigPlayerInput? = null,
	energyConfig: EnergyConfigPlayerInput? = null,
	fluidConfig: FluidConfigPlayerInput? = null,
	balanceInputs: BooleanInputWithFeedback? = null,
	upgrades: UpgradeSlots? = null,
): DeviceControls<S> {
	return DeviceControls(screen, parent, extra = extra, redstoneConfig = redstoneConfig,
						  itemConfig = itemConfig, energyConfig = energyConfig, fluidConfig = fluidConfig,
						  balanceInputs = balanceInputs, upgrades = upgrades)
}
