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

import com.mojang.blaze3d.platform.InputConstants
import com.mojang.blaze3d.vertex.PoseStack
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.Screen
import ru.dbotthepony.mc.otm.client.screen.panels.EditablePanel
import kotlin.math.roundToInt

open class DiscreteScrollBarPanel<out S : Screen>(
	screen: S,
	parent: EditablePanel<*>?,
	val maxScroll: (panel: DiscreteScrollBarPanel<@UnsafeVariance S>) -> Int,
	val scrollCallback: (panel: DiscreteScrollBarPanel<@UnsafeVariance S>, oldScroll: Int, newScroll: Int) -> Unit,
	x: Float = 0f,
	y: Float = 0f,
	height: Float = 20f,
	isSlim: Boolean = false
) : EditablePanel<S>(screen, parent, x, y, width = if (isSlim) ScrollBarConstants.SLIM_WIDTH else ScrollBarConstants.WIDTH, height = height) {
	inner class Button : EditablePanel<S>(screen, this@DiscreteScrollBarPanel, 1f, 1f, this@DiscreteScrollBarPanel.width - 2f, 15f) {
		var isScrolling = false
			private set

		override fun innerRender(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float) {
			if (this@DiscreteScrollBarPanel.width == ScrollBarConstants.SLIM_WIDTH) {
				if (isScrolling) {
					ScrollBarConstants.SLIM_BUTTON_PRESS.render(graphics, width = width, height = height)
				} else if (maxScroll.invoke(this@DiscreteScrollBarPanel) <= 0) {
					ScrollBarConstants.SLIM_BUTTON_DISABLED.render(graphics, width = width, height = height)
				} else if (isHovered) {
					ScrollBarConstants.SLIM_BUTTON_HOVER.render(graphics, width = width, height = height)
				} else {
					ScrollBarConstants.SLIM_BUTTON.render(graphics, width = width, height = height)
				}
			} else {
				if (isScrolling) {
					ScrollBarConstants.BUTTON_PRESS.render(graphics, width = width, height = height)
				} else if (maxScroll.invoke(this@DiscreteScrollBarPanel) <= 0) {
					ScrollBarConstants.BUTTON_DISABLED.render(graphics, width = width, height = height)
				} else if (isHovered) {
					ScrollBarConstants.BUTTON_HOVER.render(graphics, width = width, height = height)
				} else {
					ScrollBarConstants.BUTTON.render(graphics, width = width, height = height)
				}
			}
		}

		private var rememberScroll = 0
		private var rememberY = 0.0

		override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean {
			if (maxScroll.invoke(this@DiscreteScrollBarPanel) <= 0) {
				return true
			}

			if (button == InputConstants.MOUSE_BUTTON_LEFT && tryToGrabMouseInput()) {
				isScrolling = true
				rememberScroll = scroll
				rememberY = y
			}

			return true
		}

		override fun mouseReleasedInner(x: Double, y: Double, button: Int): Boolean {
			if (isScrolling && button == InputConstants.MOUSE_BUTTON_LEFT) {
				isScrolling = false
				grabMouseInput = false
				return true
			}

			return false
		}

		override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean {
			return this@DiscreteScrollBarPanel.mouseScrolledInner(x, y, scroll)
		}

		override fun mouseDraggedInner(x: Double, y: Double, button: Int, xDelta: Double, yDelta: Double): Boolean {
			if (isScrolling) {
				val pixelsPerRow = (this@DiscreteScrollBarPanel.height - height) / maxScroll.invoke(this@DiscreteScrollBarPanel)
				val diff = y - rememberY

				this@DiscreteScrollBarPanel.scroll = rememberScroll + (diff / pixelsPerRow).roundToInt()
				return true
			}

			return false
		}
	}

	val scrollButton = Button()

	override fun innerRender(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float) {
		if (width == ScrollBarConstants.SLIM_WIDTH) {
			ScrollBarConstants.SLIM_BODY.render(graphics, y = 2f, height = height - 4f)
			ScrollBarConstants.SLIM_TOP.render(graphics)
			ScrollBarConstants.SLIM_BOTTOM.render(graphics, y = height - 2f)
		} else {
			ScrollBarConstants.BODY.render(graphics, y = 2f, height = height - 4f)
			ScrollBarConstants.TOP.render(graphics)
			ScrollBarConstants.BOTTOM.render(graphics, y = height - 2f)
		}
	}

	public override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean {
		this.scroll -= scroll.toInt()
		return true
	}

	var scroll = 0
		set(value) {
			val newValue = value.coerceIn(0, maxScroll.invoke(this))

			if (newValue != field) {
				val old = field
				field = newValue
				scrollChanged(old, newValue)
			}
		}

	open fun updateScrollButtonPosition() {
		val maxScroll = this.maxScroll.invoke(this)

		if (maxScroll <= 0) {
			scrollButton.y = 1f
			return
		}

		val availableHeight = height - scrollButton.height - 2f
		scrollButton.y = 1f + availableHeight * (scroll.toFloat() / maxScroll)
	}

	open fun scrollChanged(oldScroll: Int, newScroll: Int) {
		scrollCallback.invoke(this, oldScroll, newScroll)
		updateScrollButtonPosition()
	}

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