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 ru.dbotthepony.mc.otm.client.screen.panels.slot.AbstractSlotPanel
import ru.dbotthepony.mc.otm.core.math.linearInterpolation
import ru.dbotthepony.mc.otm.milliTimeD
import kotlin.math.roundToInt

open class AnalogScrollBarPanel<out S : Screen>(
	screen: S,
	parent: EditablePanel<*>?,
	val maxScroll: (panel: AnalogScrollBarPanel<@UnsafeVariance S>) -> Float,
	val scrollCallback: (panel: AnalogScrollBarPanel<@UnsafeVariance S>, oldScroll: Float, newScroll: Float) -> Unit = { _, _, _ -> },
	val smoothScrollCallback: (panel: AnalogScrollBarPanel<*>, oldScroll: Float, newScroll: Float) -> Unit = { _, _, _ -> },
	x: Float = 0f,
	y: Float = 0f,
	height: Float = 20f,
	open val allowSmoothScroll: Boolean = true,
	open var scrollStep: Float = AbstractSlotPanel.SIZE,
	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@AnalogScrollBarPanel, 1f, 1f, this@AnalogScrollBarPanel.width - 2f, 15f) {
		var isScrolling = false
			private set

		override fun innerRender(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float) {
			if (this@AnalogScrollBarPanel.width == ScrollBarConstants.SLIM_WIDTH) {
				if (isScrolling) {
					ScrollBarConstants.SLIM_BUTTON_PRESS.render(graphics, width = width, height = height)
				} else if (maxScroll.invoke(this@AnalogScrollBarPanel) <= 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@AnalogScrollBarPanel) <= 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 = 0f
		private var rememberY = 0.0

		override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean {
			if (maxScroll.invoke(this@AnalogScrollBarPanel) <= 0f) {
				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@AnalogScrollBarPanel.mouseScrolledInner(x, y, scroll)
		}

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

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

			return false
		}
	}

	val scrollButton = Button()

	private var lastRender = milliTimeD

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

		val time = milliTimeD
		val diff = time - lastRender
		lastRender = time

		if (scroll != smoothScroll) {
			smoothScroll = linearInterpolation(diff / 50.0, smoothScroll.toDouble(), scroll.toDouble()).toFloat()
		}
	}

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

	var scroll = 0f
		set(value) {
			val newValue = value.coerceAtLeast(0f).coerceAtMost(maxScroll.invoke(this))

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

				if (!allowSmoothScroll) {
					smoothScroll = newValue
				}
			}
		}

	var smoothScroll = 0f
		set(value) {
			val newValue = value.coerceAtLeast(0f).coerceAtMost(maxScroll.invoke(this))

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

	fun hardSetScroll(scroll: Float) {
		this.scroll = scroll
		this.smoothScroll = scroll
	}

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

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

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

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

	open fun smoothScrollChanged(oldScroll: Float, newScroll: Float) {
		smoothScrollCallback.invoke(this, oldScroll, newScroll)
	}

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