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

import net.minecraft.client.gui.screens.Screen
import net.minecraft.network.chat.Component
import ru.dbotthepony.mc.otm.client.render.MGUIGraphics
import ru.dbotthepony.mc.otm.client.render.RenderGravity
import ru.dbotthepony.mc.otm.client.render.Widgets18
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.mc.otm.client.render.IGUIRenderable
import ru.dbotthepony.mc.otm.client.render.UVWindingOrder
import ru.dbotthepony.mc.otm.client.render.Widgets15
import ru.dbotthepony.mc.otm.client.render.Widgets8
import java.util.function.IntConsumer
import kotlin.math.max
import kotlin.math.min

/**
 * Base button panel, representing traditional buttons, extending [AbstractButtonPanel]
 */
abstract class ButtonPanel<out S : Screen>(
	screen: S,
	parent: EditablePanel<*>?,
	x: Float = 0f,
	y: Float = 0f,
	width: Float = 40f,
	height: Float = HEIGHT,
) : AbstractButtonPanel<S>(screen, parent, x, y, width, height) {
	open val labelColor: RGBAColor
		get() = RGBAColor.WHITE
	open val label: Component?
		get() = null
	open val icon: IGUIRenderable?
		get() = null
	open val iconWinding: UVWindingOrder
		get() = UVWindingOrder.NORMAL
	open val iconOnLeft: Boolean
		get() = true
	open val iconSpaceReservation: SpaceReservation
		get() = SpaceReservation.DEFAULT

	enum class SpaceReservation {
		/**
		 * Don't reserve space, icon pushes text only when present
		 */
		DEFAULT,

		/**
		 * Space for icon is always reserved
		 */
		ICON_AND_TEXT,

		/**
		 * Consider only icon will be present, and align it according to [RenderGravity.CENTER_CENTER].
		 *
		 * Having both icon and text will result in undefined behavior
		 */
		ICON_ONLY;
	}

	protected open val disabledSprite: IGUIRenderable
		get() = if (width >= 18f && height >= 18f)
			Widgets18.BUTTON_DISABLED_STRETCHABLE
		else if (width >= 15f && height >= 15f)
			Widgets15.BUTTON_DISABLED_STRETCHABLE
		else
			Widgets8.BUTTON_DISABLED_STRETCHABLE

	protected open val pressedSprite: IGUIRenderable
		get() = if (width >= 18f && height >= 18f)
			Widgets18.BUTTON_PRESSED_STRETCHABLE
		else if (width >= 15f && height >= 15f)
			Widgets15.BUTTON_PRESSED_STRETCHABLE
		else
			Widgets8.BUTTON_PRESSED_STRETCHABLE

	protected open val hoveredSprite: IGUIRenderable
		get() = if (width >= 18f && height >= 18f)
			Widgets18.BUTTON_HOVERED_STRETCHABLE
		else if (width >= 15f && height >= 15f)
			Widgets15.BUTTON_HOVERED_STRETCHABLE
		else
			Widgets8.BUTTON_HOVERED_STRETCHABLE

	protected open val idleSprite: IGUIRenderable
		get() = if (width >= 18f && height >= 18f)
			Widgets18.BUTTON_IDLE_STRETCHABLE
		else if (width >= 15f && height >= 15f)
			Widgets15.BUTTON_IDLE_STRETCHABLE
		else
			Widgets8.BUTTON_IDLE_STRETCHABLE

	protected open fun renderBackground(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) {
		if (isDisabled) {
			disabledSprite.render(graphics, canvasWidth = width, canvasHeight = height)
		} else if (isPressed) {
			pressedSprite.render(graphics, canvasWidth = width, canvasHeight = height)
		} else if (isHovered) {
			hoveredSprite.render(graphics, canvasWidth = width, canvasHeight = height)
		} else {
			idleSprite.render(graphics, canvasWidth = width, canvasHeight = height)
		}
	}

	override fun innerRender(graphics: MGUIGraphics, mouseX: Float, mouseY: Float, partialTick: Float) {
		renderBackground(graphics, mouseX, mouseY, partialTick)

		val label = label
		val icon = icon

		if (label != null) {
			if (icon == null && iconSpaceReservation == SpaceReservation.DEFAULT) {
				graphics.draw(label, width / 2f, height / 2f, color = labelColor, gravity = RenderGravity.CENTER_CENTER)
			} else {
				val lWidth = font.width(label).toFloat()
				val min = min(width, height)

				if (width / 2f - lWidth / 2f >= min + 1f) {
					// text to center
					graphics.draw(label, width / 2f, height / 2f, color = labelColor, gravity = RenderGravity.CENTER_CENTER)
				} else if (iconOnLeft) {
					graphics.draw(label, min + 1f, height / 2f, color = labelColor, gravity = RenderGravity.CENTER_LEFT)
				} else {
					graphics.draw(label, 1f, height / 2f, color = labelColor, gravity = RenderGravity.CENTER_LEFT)
				}
			}
		}

		if (icon != null) {
			if (iconSpaceReservation != SpaceReservation.ICON_ONLY) {
				val size = min(width, height)

				if (iconOnLeft) {
					icon.render(graphics, 0f, 0f, size, size, winding = iconWinding)
				} else {
					icon.render(graphics, width - size, 0f, size, size, winding = iconWinding)
				}
			} else {
				icon.render(graphics, 0f, 0f, width, height, winding = iconWinding)
			}
		}
	}

	override fun sizeToContents() {
		when (iconSpaceReservation) {
			SpaceReservation.DEFAULT -> {
				val icon = icon
				val label = label

				if (label == null && icon == null) {
					// i have no idea what you want me to do
					width = 18f
					height = 18f
				} else {
					var width = 2f
					var height = 2f

					if (icon != null) {
						width = icon.width
						height = icon.height
					}

					if (label != null) {
						if (icon != null) {
							width += 2f
							height = max(height, font.lineHeight.toFloat() + 2f)
						} else {
							height = 18f
						}

						width += font.width(label) + 2f
					}

					this.width = width
					this.height = height
				}
			}

			SpaceReservation.ICON_AND_TEXT -> {
				var height = icon?.height ?: 18f
				var width = icon?.width ?: 18f

				width += 2f

				val label = label

				if (label != null) {
					height = max(height, font.lineHeight.toFloat() + 2f)
					width += font.width(label) + 2f
				}

				this.width = width
				this.height = height
			}

			SpaceReservation.ICON_ONLY -> {
				val height = icon?.height ?: 18f
				val width = icon?.width ?: 18f
				val min = min(width, height)
				this.height = min
				this.width = min
			}
		}
	}

	open class Simple<out S : Screen>(
		screen: S,
		parent: EditablePanel<*>?,
		x: Float = 0f,
		y: Float = 0f,
		width: Float = 40f,
		height: Float = HEIGHT,
		override var label: Component?,
		var onPress: IntConsumer? = null
	) : ButtonPanel<S>(screen, parent, x, y, width, height) {
		constructor(screen: S, parent: EditablePanel<*>?, label: Component, onPress: IntConsumer? = null) : this(screen, parent, x = 0f, label = label, onPress = onPress)

		constructor(
			screen: S,
			parent: EditablePanel<*>?,
			x: Float = 0f,
			y: Float = 0f,
			width: Float = 40f,
			height: Float = HEIGHT,
			label: Component,
			onPress: Runnable
		) : this(screen, parent, x, y, width, height, label, onPress = IntConsumer { onPress.run() })

		override fun onClick(mouseButton: Int) {
			onPress?.accept(mouseButton)
		}

		override var labelColor: RGBAColor = RGBAColor.WHITE
		override var icon: IGUIRenderable? = null
		override var iconOnLeft: Boolean = true
		override var iconSpaceReservation = SpaceReservation.DEFAULT
		override var iconWinding: UVWindingOrder = UVWindingOrder.NORMAL
	}

	companion object {
		const val HEIGHT = 18f

		fun <S : Screen> square18(
			screen: S,
			parent: EditablePanel<*>?,
			x: Float = 0f,
			y: Float = 0f,
			icon: IGUIRenderable? = null,
			iconWinding: UVWindingOrder = UVWindingOrder.NORMAL,
			onPress: IntConsumer? = null
		): Simple<S> {
			return Simple(screen, parent, x, y, 18f, 18f, null, onPress).also {
				it.icon = icon
				it.iconWinding = iconWinding
			}
		}

		fun <S : Screen> square15(
			screen: S,
			parent: EditablePanel<*>?,
			x: Float = 0f,
			y: Float = 0f,
			icon: IGUIRenderable? = null,
			iconWinding: UVWindingOrder = UVWindingOrder.NORMAL,
			onPress: IntConsumer? = null
		): Simple<S> {
			return Simple(screen, parent, x, y, 15f, 15f, null, onPress).also {
				it.icon = icon
				it.iconWinding = iconWinding
			}
		}

		fun <S : Screen> square8(
			screen: S,
			parent: EditablePanel<*>?,
			x: Float = 0f,
			y: Float = 0f,
			icon: IGUIRenderable? = null,
			iconWinding: UVWindingOrder = UVWindingOrder.NORMAL,
			onPress: IntConsumer? = null
		): Simple<S> {
			return Simple(screen, parent, x, y, 8f, 8f, null, onPress).also {
				it.icon = icon
				it.iconWinding = iconWinding
			}
		}

		fun <S : Screen> square18(
			screen: S,
			parent: EditablePanel<*>?,
			icon: IGUIRenderable? = null,
			iconWinding: UVWindingOrder = UVWindingOrder.NORMAL,
			onPress: IntConsumer? = null
		): Simple<S> {
			return Simple(screen, parent, 0f, 0f, 18f, 18f, null, onPress).also {
				it.icon = icon
				it.iconWinding = iconWinding
			}
		}

		fun <S : Screen> square15(
			screen: S,
			parent: EditablePanel<*>?,
			icon: IGUIRenderable? = null,
			iconWinding: UVWindingOrder = UVWindingOrder.NORMAL,
			onPress: IntConsumer? = null
		): Simple<S> {
			return Simple(screen, parent, 0f, 0f, 15f, 15f, null, onPress).also {
				it.icon = icon
				it.iconWinding = iconWinding
			}
		}

		fun <S : Screen> square8(
			screen: S,
			parent: EditablePanel<*>?,
			icon: IGUIRenderable? = null,
			iconWinding: UVWindingOrder = UVWindingOrder.NORMAL,
			onPress: IntConsumer? = null
		): Simple<S> {
			return Simple(screen, parent, 0f, 0f, 8f, 8f, null, onPress).also {
				it.icon = icon
				it.iconWinding = iconWinding
			}
		}
	}
}
