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

import com.mojang.blaze3d.platform.InputConstants
import net.minecraft.ChatFormatting
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.network.chat.Component
import net.minecraft.world.item.ItemStack
import net.minecraft.world.item.Items
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.isCtrlDown
import ru.dbotthepony.mc.otm.client.isShiftDown
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.client.render.AbstractMatterySprite
import ru.dbotthepony.mc.otm.client.render.ItemStackIcon
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.DockResizeMode
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.config.ClientConfig
import ru.dbotthepony.mc.otm.core.GetterSetter
import ru.dbotthepony.mc.otm.core.TextComponent
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.math.RelativeSide
import ru.dbotthepony.mc.otm.core.value
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 java.util.function.Predicate
import kotlin.math.ceil
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>
): LargeEnumRectangleButtonPanel<S, RedstoneSetting> {
	return object : LargeEnumRectangleButtonPanel<S, RedstoneSetting>(
		screen, parent, x = x, y = y,
		defaultValue = RedstoneSetting.LOW, enum = RedstoneSetting::class.java,
		prop = control,
	) {
		override var isDisabled: Boolean
			get() = !control.test(minecraft.player)
			set(value) {}

		init {
			add(RedstoneSetting.IGNORED, tooltip = RedstoneSetting.IGNORED.description, skinElement = if (ClientConfig.REDSTONE_CONTROLS_ITEM_ICONS) gunpowder else Widgets18.REDSTONE_IGNORED)
			add(RedstoneSetting.LOW, tooltip = RedstoneSetting.LOW.description, skinElement = if (ClientConfig.REDSTONE_CONTROLS_ITEM_ICONS) redstoneUnderBarrier else Widgets18.REDSTONE_LOW)
			add(RedstoneSetting.HIGH, tooltip = RedstoneSetting.HIGH.description, skinElement = if (ClientConfig.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: GetterSetter<T>,
	defaultValue: T,
	val pullProp: BooleanInputWithFeedback,
	val pushProp: BooleanInputWithFeedback
) : LargeEnumRectangleButtonPanel<S, T>(screen, parent, x = x, y = y, enum = enum, prop = prop, 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: GuiGraphics, 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): LargeEnumRectangleButtonPanel<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.values()) {
		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): LargeEnumRectangleButtonPanel<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.values()) {
		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): LargeEnumRectangleButtonPanel<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.values()) {
		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: FramePanel<S>,
	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,
) : EditablePanel<S>(screen, parent, x = parent.width + 3f, height = 0f, width = 0f) {
	val itemConfigButton: LargeRectangleButtonPanel<S>?
	val energyConfigButton: LargeRectangleButtonPanel<S>?
	val fluidConfigButton: LargeRectangleButtonPanel<S>?
	val redstoneControlsButton: LargeEnumRectangleButtonPanel<S, RedstoneSetting>?
	val balanceInputsButton: LargeBooleanRectangleButtonPanel<S>?
	val upgradesButton: LargeRectangleButtonPanel<S>?

	private var upgradeWindow: FramePanel<S>? = null

	private var nextY = 0f

	fun <P : EditablePanel<@UnsafeVariance S>> addButton(button: P): P {
		button.parent = this
		button.x = 0f
		button.y = nextY
		nextY += button.height + 2f
		height = nextY - 2f
		width = button.width.coerceAtLeast(width)
		return button
	}

	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 : LargeRectangleButtonPanel<S>(
				screen, this@DeviceControls,
				skinElement = Widgets18.UPGRADES
			) {
				init {
					tooltips.add(TranslatableComponent("otm.gui.upgrades"))
				}

				override fun innerRenderTooltips(graphics: GuiGraphics, 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)

						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 square = ceil(upgrades.slots.size.toDouble().pow(0.5)).toInt()
							val size = square * AbstractSlotPanel.SIZE

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

								for (slot in upgrades.slots) {
									SlotPanel(screen, grid, slot)
								}

								grid.dock = Dock.FILL
								grid.dockResize = DockResizeMode.NONE

								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(LargeBooleanRectangleButtonPanel(
				screen, this,
				prop = balanceInputs,
				skinElementActive = Widgets18.BALANCING_ENABLED,
				skinElementInactive = Widgets18.BALANCING_DISABLED).also {
					it.tooltips.add(TranslatableComponent("otm.gui.balance_inputs"))
			})
		} else {
			balanceInputsButton = null
		}

		if (itemConfig != null) {
			itemConfigButton = addButton(object : LargeRectangleButtonPanel<S>(screen, this@DeviceControls, skinElement = Widgets18.ITEMS_CONFIGURATION) {
				init {
					tooltips.add(TranslatableComponent("otm.gui.sides.item_config"))
				}

				override fun onClick(mouseButton: Int) {
					if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) {
						val frame = makeItemHandlerControlPanel(screen, itemConfig)

						frame.x = absoluteX + width / 2f - frame.width / 2f
						frame.y = absoluteY + height + 8f
					}
				}
			})
		} else {
			itemConfigButton = null
		}

		if (energyConfig != null) {
			energyConfigButton = addButton(object : LargeRectangleButtonPanel<S>(screen, this@DeviceControls, y = nextY, skinElement = Widgets18.ENERGY_CONFIGURATION) {
				init {
					tooltips.add(TranslatableComponent("otm.gui.sides.energy_config"))
				}

				override fun onClick(mouseButton: Int) {
					if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) {
						val frame = makeEnergyConfigPanel(screen, energyConfig)

						frame.x = absoluteX + width / 2f - frame.width / 2f
						frame.y = absoluteY + height + 8f
					}
				}
			})
		} else {
			energyConfigButton = null
		}

		if (fluidConfig != null) {
			fluidConfigButton = addButton(object : LargeRectangleButtonPanel<S>(screen, this@DeviceControls, y = nextY, skinElement = Widgets18.FLUID_CONFIGURATION) {
				init {
					tooltips.add(TranslatableComponent("otm.gui.sides.fluid_config"))
				}

				override fun onClick(mouseButton: Int) {
					if (mouseButton == InputConstants.MOUSE_BUTTON_LEFT) {
						val frame = makeFluidConfigPanel(screen, fluidConfig)

						frame.x = absoluteX + width / 2f - frame.width / 2f
						frame.y = absoluteY + height + 8f
					}
				}
			})
		} else {
			fluidConfigButton = null
		}
	}

	override fun tickInner() {
		super.tickInner()
		x = (parent?.width ?: 0f) + 3f
		y = 0f
	}
}

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)
}
