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

import com.mojang.blaze3d.platform.InputConstants
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.ChatFormatting
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.Screen
import net.minecraft.network.chat.Component
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.client.render.AbstractMatterySprite
import ru.dbotthepony.mc.otm.client.render.IGUIRenderable
import ru.dbotthepony.mc.otm.client.render.UVWindingOrder
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
import ru.dbotthepony.mc.otm.core.*
import ru.dbotthepony.mc.otm.core.util.EnumValueCodec
import ru.dbotthepony.mc.otm.menu.input.IPlayerInputWithFeedback
import java.util.*
import java.util.function.Predicate
import kotlin.collections.ArrayList

abstract class EnumRectangleButtonPanel<out S : Screen, T : Enum<T>>(
	screen: S,
	parent: EditablePanel<*>?,
	x: Float = 0f,
	y: Float = 0f,
	width: Float,
	height: Float,
	enum: Class<T>,
	val prop: GetterSetter<T>,
	val defaultValue: T,
) : RectangleButtonPanel<S>(screen, parent, x, y, width, height, null) {
	val enum = EnumValueCodec.searchClass(enum)
	private val constants: Array<T> = enum.enumConstants
	private var isBuilding = true
	var predicate: Predicate<T> = Predicate { true }

	override var isDisabled: Boolean
		get() {
			if (prop is IPlayerInputWithFeedback<T>) {
				return !prop.test(minecraft.player)
			} else {
				return super.isDisabled
			}
		}
		set(value) { super.isDisabled = value }

	data class Entry<T : Enum<T>>(
		val key: T,
		val skinElement: IGUIRenderable?,
		val winding: UVWindingOrder = UVWindingOrder.NORMAL,
		val tooltip: Component? = null
	)

	protected val enumMapping = EnumMap<T, Entry<T>>(enum)

	fun add(key: T, skinElement: IGUIRenderable? = null, tooltip: Component? = null, winding: UVWindingOrder = UVWindingOrder.NORMAL): EnumRectangleButtonPanel<S, T> {
		return add(Entry(key = key, skinElement = skinElement, winding = winding, tooltip = tooltip))
	}

	fun add(entry: Entry<T>): EnumRectangleButtonPanel<S, T> {
		check(isBuilding) { "Not building" }
		check(enumMapping.put(entry.key, entry) == null) { "Already has mapping for ${entry.key}" }
		if (enumMapping.size == enum.enumConstants.size) finish()
		return this
	}

	fun finish(): EnumRectangleButtonPanel<S, T> {
		if (!isBuilding) return this
		check(enumMapping.isNotEmpty()) { "No enums were defined, like, at all." }
		check(enumMapping.containsKey(defaultValue)) { "Default value $defaultValue is missing from mapping" }
		isBuilding = false
		return this
	}

	override fun innerRender(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float) {
		check(!isBuilding) { "Still building this button!" }
		super.innerRender(graphics, mouseX, mouseY, partialTick)
		val entry = enumMapping[prop.get()] ?: return
		entry.skinElement?.render(graphics, 0f, 0f, width, height, entry.winding)
	}

	override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean {
		check(!isBuilding) { "Still building this button!" }
		return super.mouseClickedInner(x, y, button)
	}

	override fun mouseReleasedInner(x: Double, y: Double, button: Int): Boolean {
		check(!isBuilding) { "Still building this button!" }
		return super.mouseReleasedInner(x, y, button)
	}

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

	override fun onClick(mouseButton: Int) {
		check(!isBuilding) { "Still building this button!" }
		if (enumMapping.size == 1) return

		when (mouseButton) {
			InputConstants.MOUSE_BUTTON_LEFT -> {
				var i = constants.size
				var ordinal = prop.value.ordinal

				while (i >= 0) {
					i--
					ordinal++

					if (ordinal >= constants.size) {
						ordinal = 0
					}

					if (enumMapping.containsKey(constants[ordinal]) && predicate.test(constants[ordinal])) {
						prop.value = constants[ordinal]
						break
					}
				}
			}

			InputConstants.MOUSE_BUTTON_RIGHT -> {
				var i = constants.size
				var ordinal = prop.value.ordinal

				while (i >= 0) {
					i--
					ordinal--

					if (ordinal < 0) {
						ordinal = constants.size - 1
					}

					if (enumMapping.containsKey(constants[ordinal]) && predicate.test(constants[ordinal])) {
						prop.value = constants[ordinal]
						break
					}
				}
			}

			InputConstants.MOUSE_BUTTON_MIDDLE -> {
				if (prop.value != defaultValue) {
					prop.value = defaultValue
				}
			}
		}
	}

	override fun innerRenderTooltips(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean {
		if (tooltips.isEmpty() && enumMapping.entries.none { predicate.test(it.key) && it.value.tooltip != null }) {
			return super.innerRenderTooltips(graphics, mouseX, mouseY, partialTick)
		}

		val listing = ArrayList<Component>()

		if (tooltips.isNotEmpty()) {
			listing.addAll(tooltips)
			listing.add(SPACE)
		}

		for ((k, entry) in enumMapping) {
			if (entry.tooltip != null && predicate.test(k)) {
				val active = entry.key == prop.get()
				val tooltip = entry.tooltip.copy().withStyle(if (active) ChatFormatting.WHITE else ChatFormatting.GRAY)
				listing.add(if (active) TranslatableComponent("otm.gui.tooltip.enum.active", tooltip) else tooltip)
			}
		}

		graphics.renderComponentTooltip(font, listing, mouseX.toInt(), mouseY.toInt())
		return true
	}

	companion object {
		private val SPACE = TextComponent("")
	}
}
