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

import com.mojang.blaze3d.systems.RenderSystem
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap
import it.unimi.dsi.fastutil.objects.Object2ObjectFunction
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.gui.screens.Screen
import net.minecraft.client.gui.screens.inventory.AbstractContainerScreen
import net.minecraft.world.effect.MobEffect
import net.minecraft.world.effect.MobEffectInstance
import net.minecraft.world.entity.LivingEntity
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.client.render.RenderGravity
import ru.dbotthepony.mc.otm.client.render.determineTooltipPosition
import ru.dbotthepony.mc.otm.client.render.drawAligned
import ru.dbotthepony.mc.otm.client.render.drawScaledAligned
import ru.dbotthepony.mc.otm.client.render.sprite
import ru.dbotthepony.mc.otm.client.render.render
import ru.dbotthepony.mc.otm.client.screen.panels.util.DiscreteScrollBarPanel
import ru.dbotthepony.mc.otm.core.math.RGBAColor
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.core.util.formatTickDuration
import ru.dbotthepony.mc.otm.core.math.integerDivisionDown
import java.util.stream.Collectors

open class EffectListPanel<out S : Screen> @JvmOverloads constructor(
	screen: S,
	parent: EditablePanel<*>?,
	val entity: LivingEntity,
	x: Float = 0f,
	y: Float = 0f,
	gridWidth: Int = 2,
	gridHeight: Int = 2,
) : EditablePanel<S>(screen, parent, x, y, gridWidth * (SQUARE_THIN.width + 2f), gridHeight * (SQUARE_THIN.height + 2f)) {
	val scroll: DiscreteScrollBarPanel<S> = DiscreteScrollBarPanel(screen, this, this::calculateMaxScroll, this::onScrolled, height = height)

	init {
		isObstructing = false
	}

	var alignOnRight = true

	var gridWidth: Int = gridWidth
		set(value) {
			if (field == value)
				return

			field = value
			width = value * (SQUARE_THIN.width + 2f)
			canvas.invalidateLayout()
		}

	var gridHeight: Int = gridHeight
		set(value) {
			if (field == value)
				return

			field = value
			height = value * (SQUARE_THIN.height + 2f)
			canvas.invalidateLayout()
		}

	var autoAlignOnScrollbarShow = true

	init {
		scroll.visible = false
		//scissor = true
	}

	open inner class EffectSquare(
		effect: MobEffectInstance,
		x: Float = 0f,
		y: Float = 0f,
		width: Float = SQUARE_THIN.width,
		height: Float = SQUARE_THIN.height,
	) : EditablePanel<S>(screen, this@EffectListPanel.canvas, x, y, width, height) {
		protected var lastInstance: MobEffectInstance = effect

		val effectType: MobEffect = effect.effect
		val effect: MobEffectInstance get() {
			val get = entity.activeEffectsMap[effectType]

			if (get != null) {
				lastInstance = get
				return get
			}

			return lastInstance
		}

		val isStillValid: Boolean get() {
			return entity.activeEffectsMap[effectType]?.let { it.duration > 0 } ?: false
		}

		override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean {
			if (this@EffectListPanel.scroll.visible) {
				this@EffectListPanel.scroll.mouseScrolledInner(x, y, scroll)
			}

			return true
		}

		override fun tickInner() {
			super.tickInner()

			if (!isStillValid) {
				effectButtons.remove(effectType)
				remove()
			}
		}

		override fun innerRender(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float) {
			clearDepth(graphics)
			SQUARE_THIN.render(graphics, width = width, height = height)

			RenderSystem.setShaderColor(1f, 1f, 1f, 0.5f)
			minecraft.mobEffectTextures.get(effect.effect).render(graphics, x = 3f, y = 3f, width = width - 6f, height = height - 6f)

			RenderSystem.setShaderColor(1f, 1f, 1f, 1f)
			graphics.drawScaledAligned(font, formatTickDuration(effect.duration), 0.75f, RenderGravity.CENTER_CENTER, width / 2f - 1f, height / 2f, RGBAColor.WHITE)
		}

		override fun innerRenderTooltips(graphics: GuiGraphics, mouseX: Float, mouseY: Float, partialTick: Float): Boolean {
			if (isHovered) {
				val (x, y) = determineTooltipPosition(mouseX + 4f, mouseY - 12f, BAR.width, BAR.height)

				BAR.clearDepth(graphics, x, y)
				BAR.render(graphics, x, y)

				val renderWidth = 18f
				val renderHeight = 18f

				minecraft.mobEffectTextures.get(effect.effect).render(
					graphics.pose(),
					x = x + 8f,
					y = y + BAR.height / 2f - renderHeight / 2f,
					width = renderWidth,
					height = renderHeight)

				val name = effect.effect.displayName.copy()

				if (effect.amplifier in 1 .. 9) {
					name.append(" ").append(TranslatableComponent("enchantment.level.${effect.amplifier + 1}"))
				} else if (effect.amplifier > 9) {
					name.append(" ${effect.amplifier + 1}")
				}

				graphics.drawAligned(font, name, RenderGravity.TOP_LEFT, x + renderWidth + 12f, y + 7f, RGBAColor.WHITE)
				graphics.drawAligned(font, formatTickDuration(effect.duration, true), RenderGravity.TOP_LEFT, x + renderWidth + 12f, y + 7f + font.lineHeight + 2f, 8355711)
			}

			return isHovered
		}
	}

	protected val effectButtons: MutableMap<MobEffect, EffectSquare> = Object2ObjectArrayMap()

	fun calculateMaxScroll(scrollBarPanel: DiscreteScrollBarPanel<*>): Int {
		return integerDivisionDown(entity.activeEffects.size, gridWidth)
	}

	fun onScrolled(scrollBarPanel: DiscreteScrollBarPanel<*>, oldScroll: Int, newScroll: Int) {
		canvas.yOffset = newScroll * -26f
	}

	override fun tickInner() {
		for (effect in entity.activeEffects) {
			effectButtons.computeIfAbsent(effect.effect, Object2ObjectFunction {
				EffectSquare(effect)
			})
		}

		super.tickInner()
	}

	val canvas = object : EditablePanel<S>(screen, this@EffectListPanel, 0f, 0f, width, height) {
		init {
			isObstructing = false
		}

		override fun performLayout() {
			super.performLayout()

			val sorted = children.stream().filter { it is EffectSquare }.collect(Collectors.toList()) as MutableList<EffectListPanel<S>.EffectSquare>

			sorted.sortWith { a, b ->
				a.effect.duration.compareTo(b.effect.duration)
			}

			@Suppress("name_shadowing")
			var y = 0
			@Suppress("name_shadowing")
			var x = 0

			for (panel in sorted) {
				if (alignOnRight) {
					panel.setPos((gridWidth - x - 1) * 26f, y * 26f)
				} else {
					panel.setPos(x * 26f, y * 26f)
				}

				x++

				if (x >= gridWidth) {
					x = 0
					y++
				}
			}

			onSortedEffectPanels(sorted)
		}

		override fun mouseScrolledInner(x: Double, y: Double, scroll: Double): Boolean {
			if (this@EffectListPanel.scroll.visible) {
				this@EffectListPanel.scroll.mouseScrolledInner(x, y, scroll)
			}

			return true
		}

		override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean {
			return false
		}

		override fun mouseReleasedInner(x: Double, y: Double, button: Int): Boolean {
			return false
		}

		override fun mouseDraggedInner(x: Double, y: Double, button: Int, xDelta: Double, yDelta: Double): Boolean {
			return false
		}
	}

	override fun mouseClickedInner(x: Double, y: Double, button: Int): Boolean {
		return false
	}

	override fun mouseReleasedInner(x: Double, y: Double, button: Int): Boolean {
		return false
	}

	override fun mouseDraggedInner(x: Double, y: Double, button: Int, xDelta: Double, yDelta: Double): Boolean {
		return false
	}

	fun onSortedEffectPanels(sorted: List<EffectSquare>) {
		if (sorted.size > (gridHeight * gridWidth) && !scroll.visible) {
			scroll.visible = true

			if (autoAlignOnScrollbarShow) {
				this.x -= scroll.width + 2f
				canvas.x = scroll.width + 2f
				this.width += scroll.width + 2f
			}

			scroll.scroll = scroll.scroll
		} else if (sorted.size <= (gridHeight * gridWidth) && scroll.visible) {
			scroll.visible = false
			scroll.scroll = 0

			if (autoAlignOnScrollbarShow) {
				this.x += scroll.width + 2f
				canvas.x = 0f
				this.width -= scroll.width + 2f
			}
		}
	}

	companion object {
		val BAR = AbstractContainerScreen.INVENTORY_LOCATION.sprite(0f, 166f, 120f, 32f)
		val SQUARE = AbstractContainerScreen.INVENTORY_LOCATION.sprite(0f, 198f, 32f, 32f)
		val SQUARE_THIN = AbstractContainerScreen.INVENTORY_LOCATION.sprite(141f, 166f, 24f, 24f)
		val SQUARE_THIN_HIGHLIGHT = AbstractContainerScreen.INVENTORY_LOCATION.sprite(165f, 166f, 24f, 24f)
	}
}
