package ru.dbotthepony.mc.otm.client.render

import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.Tesselator
import com.mojang.blaze3d.vertex.VertexConsumer
import com.mojang.blaze3d.vertex.VertexSorting
import net.minecraft.client.gui.Font
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.core.Vec3i
import net.minecraft.network.chat.Component
import net.minecraft.util.FormattedCharSequence
import org.joml.Matrix4f
import org.joml.Vector3f
import ru.dbotthepony.mc.otm.core.math.IAngle
import ru.dbotthepony.mc.otm.core.math.RGBAColor
import ru.dbotthepony.mc.otm.core.math.Vector
import ru.dbotthepony.mc.otm.core.math.rotateAroundPoint
import ru.dbotthepony.mc.otm.core.math.rotateAroundThis
import ru.dbotthepony.mc.otm.core.math.times
import ru.dbotthepony.mc.otm.core.math.translation
import ru.dbotthepony.mc.otm.core.math.unaryMinus

val tesselator: Tesselator get() = Tesselator.getInstance()

fun VertexConsumer.normal(vector: Vector): VertexConsumer = normal(vector.x.toFloat(), vector.y.toFloat(), vector.z.toFloat())
fun VertexConsumer.vertex(matrix4f: Matrix4f, vector: Vector): VertexConsumer = vertex(matrix4f, vector.x.toFloat(), vector.y.toFloat(), vector.z.toFloat())
fun VertexConsumer.color(color: RGBAColor?): VertexConsumer {
	if (color != null)
		color(color.redInt, color.greenInt, color.blueInt, color.alphaInt)

	return this
}

fun PoseStack.translate(vector: Vector) = translate(vector.x, vector.y, vector.z)
fun PoseStack.translate(vector: Vec3i) = translate(vector.x.toDouble(), vector.y.toDouble(), vector.z.toDouble())
fun PoseStack.translate(vector: Vector3f) = translate(vector.x(), vector.y(), vector.z()) // TODO: 1.19.3

fun PoseStack.rotateAroundPoint(point: Vector, axis: Vector, rotation: Float, isDegrees: Boolean = false) {
	val last = last()
	last.pose().rotateAroundPoint(point, axis, rotation, isDegrees)
	last.normal().rotate(axis.rotateAroundThis(rotation, isDegrees))
}

fun PoseStack.rotateAroundPoint(point: Vector, rotation: IAngle) {
	val last = last()
	last.pose().rotateAroundPoint(point, rotation)
	// last.normal().mul(rotation.forward().rotateAroundThis(rotation))
}

fun PoseStack.rotateAroundPoint(point: Vector3f, axis: Vector, rotation: Float, isDegrees: Boolean = false) {
	val last = last()
	last.pose().rotateAroundPoint(point, axis, rotation, isDegrees)
	last.normal().rotate(axis.rotateAroundThis(rotation, isDegrees))
}

fun PoseStack.rotateAroundPoint(point: Vector3f, rotation: IAngle) {
	val last = last()
	last.pose().rotateAroundPoint(point, rotation)
	// last.normal().mul(rotation.forward().rotateAroundThis(rotation))
}

fun PoseStack.translation(): Vector3f {
	return last().pose().translation
}

inline val PoseStack.last: PoseStack.Pose get() = last()
inline val PoseStack.Pose.pose: Matrix4f get() = pose()

private fun Font.drawScaledDuckTyped(poseStack: PoseStack, text: Any, scale: Float, x: Float, y: Float, color: Int): Int {
	val translation = poseStack.translation()

	poseStack.pushPose()
	poseStack.translate(-translation)
	poseStack.scale(scale, scale, scale)
	val inv = 1f / scale
	poseStack.translate(translation * inv)
	val size = drawDuckTyped(poseStack, text, x * inv, y * inv, color)
	poseStack.popPose()

	return size
}

private fun Font.drawScaledDuckTyped(poseStack: PoseStack, buffer: MultiBufferSource, text: Any, scale: Float, x: Float, y: Float, color: Int, drawShadow: Boolean = false, displayMode: Font.DisplayMode = Font.DisplayMode.NORMAL, packedLightCoords: Int = 15728880, effectColor: Int = 0): Int {
	val translation = poseStack.translation()

	poseStack.pushPose()
	poseStack.translate(-translation)
	poseStack.scale(scale, scale, scale)
	val inv = 1f / scale
	val size = drawDuckTyped(poseStack, buffer, text, x * inv, y * inv, color, drawShadow, displayMode, packedLightCoords, effectColor)
	poseStack.popPose()

	return size
}

private val buffer = DynamicBufferSource(vertexSorting = VertexSorting.ORTHOGRAPHIC_Z)

private fun Font.drawDuckTyped(poseStack: PoseStack, text: Any, x: Float, y: Float, color: Int): Int {
	val width = drawDuckTyped(poseStack, buffer, text, x, y, color)
	buffer.endBatch()
	return width
}

private fun Font.drawDuckTyped(
	poseStack: PoseStack,
	buffer: MultiBufferSource,
	text: Any,
	x: Float,
	y: Float,
	color: Int,
	drawShadow: Boolean = false,
	displayMode: Font.DisplayMode = Font.DisplayMode.NORMAL,
	packedLightCoords: Int = 15728880,
	effectColor: Int = 0
): Int {
	if (drawShadow) {
		poseStack.pushPose()
	}

	val result = when (text) {
		is Component -> drawInBatch(text, x, y, color, drawShadow, poseStack.last().pose(), buffer, displayMode, effectColor, packedLightCoords)
		is String -> drawInBatch(text, x, y, color, drawShadow, poseStack.last().pose(), buffer, displayMode, effectColor, packedLightCoords)
		is FormattedCharSequence -> drawInBatch(text, x, y, color, drawShadow, poseStack.last().pose(), buffer, displayMode, effectColor, packedLightCoords)
		else -> throw ClassCastException(text::class.qualifiedName)
	}

	if (drawShadow) {
		poseStack.popPose()
	}

	return result
}

private fun Font.widthDuckTyped(text: Any): Int {
	return when (text) {
		is Component -> width(text)
		is String -> width(text)
		is FormattedCharSequence -> width(text)
		else -> throw ClassCastException(text::class.qualifiedName)
	}
}

private fun Font.drawAlignedDuckTyped(poseStack: PoseStack, text: Any, align: RenderGravity, x: Float, y: Float, color: Int): Int {
	return drawDuckTyped(poseStack, text, align.x(x, widthDuckTyped(text)), align.y(y, lineHeight), color)
}

private fun Font.drawAlignedDuckTyped(
	poseStack: PoseStack,
	buffer: MultiBufferSource,
	text: Any,
	align: RenderGravity,
	x: Float,
	y: Float,
	color: Int,
	drawShadow: Boolean = false,
	displayMode: Font.DisplayMode = Font.DisplayMode.NORMAL,
	packedLightCoords: Int = 15728880,
	effectColor: Int = 0
): Int {
	return drawDuckTyped(poseStack, buffer, text, align.x(x, widthDuckTyped(text)), align.y(y, lineHeight), color, drawShadow, displayMode, packedLightCoords, effectColor)
}

private fun GuiGraphics.drawAlignedDuckTyped(
	font: Font,
	text: Any,
	align: RenderGravity,
	x: Float,
	y: Float,
	color: Int,
	drawShadow: Boolean = false,
	displayMode: Font.DisplayMode = Font.DisplayMode.NORMAL,
	packedLightCoords: Int = 15728880,
	effectColor: Int = 0
): Int {
	val width = font.drawDuckTyped(pose(), bufferSource(), text, align.x(x, font.widthDuckTyped(text)), align.y(y, font.lineHeight), color, drawShadow, displayMode, packedLightCoords, effectColor)
	flush()
	return width
}

private fun Font.drawScaledAlignedDuckTyped(poseStack: PoseStack, text: Any, scale: Float, align: RenderGravity, x: Float, y: Float, color: Int): Int {
	return drawScaledDuckTyped(poseStack, text, scale, align.x(x, widthDuckTyped(text)), align.y(y, lineHeight * scale), color)
}

private fun Font.drawScaledAlignedDuckTyped(
	poseStack: PoseStack,
	buffer: MultiBufferSource,
	text: Any,
	scale: Float,
	align: RenderGravity,
	x: Float,
	y: Float,
	color: Int,
	drawShadow: Boolean = false,
	displayMode: Font.DisplayMode = Font.DisplayMode.NORMAL,
	packedLightCoords: Int = 15728880,
	effectColor: Int = 0
): Int {
	return drawScaledDuckTyped(poseStack, buffer, text, scale, align.x(x, widthDuckTyped(text) * scale), align.y(y, lineHeight * scale), color, drawShadow, displayMode, packedLightCoords, effectColor)
}

private fun GuiGraphics.drawScaledAlignedDuckTyped(
	font: Font,
	text: Any,
	scale: Float,
	align: RenderGravity,
	x: Float,
	y: Float,
	color: Int,
	drawShadow: Boolean = false,
	displayMode: Font.DisplayMode = Font.DisplayMode.NORMAL,
	packedLightCoords: Int = 15728880,
	effectColor: Int = 0
): Int {
	val width = font.drawScaledDuckTyped(pose(), bufferSource(), text, scale, align.x(x, font.widthDuckTyped(text) * scale), align.y(y, font.lineHeight * scale), color, drawShadow, displayMode, packedLightCoords, effectColor)
	flush()
	return width
}

fun Font.drawAligned(poseStack: PoseStack, text: String, align: RenderGravity, x: Float, y: Float, color: Int) = drawAlignedDuckTyped(poseStack, text, align, x, y, color)
fun Font.drawAligned(poseStack: PoseStack, text: Component, align: RenderGravity, x: Float, y: Float, color: Int) = drawAlignedDuckTyped(poseStack, text, align, x, y, color)
fun Font.drawAligned(poseStack: PoseStack, text: FormattedCharSequence, align: RenderGravity, x: Float, y: Float, color: Int) = drawAlignedDuckTyped(poseStack, text, align, x, y, color)

fun Font.drawAligned(poseStack: PoseStack, text: String, align: RenderGravity, x: Float, y: Float, color: RGBAColor) = drawAligned(poseStack, text, align, x, y, color.toRGB())
fun Font.drawAligned(poseStack: PoseStack, text: Component, align: RenderGravity, x: Float, y: Float, color: RGBAColor) = drawAligned(poseStack, text, align, x, y, color.toRGB())
fun Font.drawAligned(poseStack: PoseStack, text: FormattedCharSequence, align: RenderGravity, x: Float, y: Float, color: RGBAColor) = drawAligned(poseStack, text, align, x, y, color.toRGB())

fun Font.drawScaledAligned(poseStack: PoseStack, text: String, scale: Float, align: RenderGravity, x: Float, y: Float, color: Int) = drawScaledAlignedDuckTyped(poseStack, text, scale, align, x, y, color)
fun Font.drawScaledAligned(poseStack: PoseStack, text: Component, scale: Float, align: RenderGravity, x: Float, y: Float, color: Int) = drawScaledAlignedDuckTyped(poseStack, text, scale, align, x, y, color)
fun Font.drawScaledAligned(poseStack: PoseStack, text: FormattedCharSequence, scale: Float, align: RenderGravity, x: Float, y: Float, color: Int) = drawScaledAlignedDuckTyped(poseStack, text, scale, align, x, y, color)

fun Font.drawScaledAligned(poseStack: PoseStack, text: String, scale: Float, align: RenderGravity, x: Float, y: Float, color: RGBAColor) = drawScaledAligned(poseStack, text, scale, align, x, y, color.toRGB())
fun Font.drawScaledAligned(poseStack: PoseStack, text: Component, scale: Float, align: RenderGravity, x: Float, y: Float, color: RGBAColor) = drawScaledAligned(poseStack, text, scale, align, x, y, color.toRGB())
fun Font.drawScaledAligned(poseStack: PoseStack, text: FormattedCharSequence, scale: Float, align: RenderGravity, x: Float, y: Float, color: RGBAColor) = drawScaledAligned(poseStack, text, scale, align, x, y, color.toRGB())

fun Font.drawAligned(poseStack: PoseStack, buffer: MultiBufferSource, text: String, align: RenderGravity, x: Float, y: Float, color: Int, drawShadow: Boolean = false, displayMode: Font.DisplayMode = Font.DisplayMode.NORMAL, packedLightCoords: Int = 15728880, effectColor: Int = 0) = drawAlignedDuckTyped(poseStack, buffer, text, align, x, y, color, drawShadow, displayMode, packedLightCoords, effectColor)
fun Font.drawAligned(poseStack: PoseStack, buffer: MultiBufferSource, text: Component, align: RenderGravity, x: Float, y: Float, color: Int, drawShadow: Boolean = false, displayMode: Font.DisplayMode = Font.DisplayMode.NORMAL, packedLightCoords: Int = 15728880, effectColor: Int = 0) = drawAlignedDuckTyped(poseStack, buffer, text, align, x, y, color, drawShadow, displayMode, packedLightCoords, effectColor)
fun Font.drawAligned(poseStack: PoseStack, buffer: MultiBufferSource, text: FormattedCharSequence, align: RenderGravity, x: Float, y: Float, color: Int, drawShadow: Boolean = false, displayMode: Font.DisplayMode = Font.DisplayMode.NORMAL, packedLightCoords: Int = 15728880, effectColor: Int = 0) = drawAlignedDuckTyped(poseStack, buffer, text, align, x, y, color, drawShadow, displayMode, packedLightCoords, effectColor)

fun Font.drawAligned(poseStack: PoseStack, buffer: MultiBufferSource, text: String, align: RenderGravity, x: Float, y: Float, color: RGBAColor, drawShadow: Boolean = false, displayMode: Font.DisplayMode = Font.DisplayMode.NORMAL, packedLightCoords: Int = 15728880, effectColor: Int = 0) = drawAligned(poseStack, buffer, text, align, x, y, color.toRGB(), drawShadow, displayMode, packedLightCoords, effectColor)
fun Font.drawAligned(poseStack: PoseStack, buffer: MultiBufferSource, text: Component, align: RenderGravity, x: Float, y: Float, color: RGBAColor, drawShadow: Boolean = false, displayMode: Font.DisplayMode = Font.DisplayMode.NORMAL, packedLightCoords: Int = 15728880, effectColor: Int = 0) = drawAligned(poseStack, buffer, text, align, x, y, color.toRGB(), drawShadow, displayMode, packedLightCoords, effectColor)
fun Font.drawAligned(poseStack: PoseStack, buffer: MultiBufferSource, text: FormattedCharSequence, align: RenderGravity, x: Float, y: Float, color: RGBAColor, drawShadow: Boolean = false, displayMode: Font.DisplayMode = Font.DisplayMode.NORMAL, packedLightCoords: Int = 15728880, effectColor: Int = 0) = drawAligned(poseStack, buffer, text, align, x, y, color.toRGB(), drawShadow, displayMode, packedLightCoords, effectColor)

fun Font.drawScaledAligned(poseStack: PoseStack, buffer: MultiBufferSource, text: String, scale: Float, align: RenderGravity, x: Float, y: Float, color: Int, drawShadow: Boolean = false, displayMode: Font.DisplayMode = Font.DisplayMode.NORMAL, packedLightCoords: Int = 15728880, effectColor: Int = 0,) = drawScaledAlignedDuckTyped(poseStack, buffer, text, scale, align, x, y, color, drawShadow, displayMode, packedLightCoords, effectColor)
fun Font.drawScaledAligned(poseStack: PoseStack, buffer: MultiBufferSource, text: Component, scale: Float, align: RenderGravity, x: Float, y: Float, color: Int, drawShadow: Boolean = false, displayMode: Font.DisplayMode = Font.DisplayMode.NORMAL, packedLightCoords: Int = 15728880, effectColor: Int = 0,) = drawScaledAlignedDuckTyped(poseStack, buffer, text, scale, align, x, y, color, drawShadow, displayMode, packedLightCoords, effectColor)
fun Font.drawScaledAligned(poseStack: PoseStack, buffer: MultiBufferSource, text: FormattedCharSequence, scale: Float, align: RenderGravity, x: Float, y: Float, color: Int, drawShadow: Boolean = false, displayMode: Font.DisplayMode = Font.DisplayMode.NORMAL, packedLightCoords: Int = 15728880, effectColor: Int = 0,) = drawScaledAlignedDuckTyped(poseStack, buffer, text, scale, align, x, y, color, drawShadow, displayMode, packedLightCoords, effectColor)

fun Font.drawScaledAligned(poseStack: PoseStack, buffer: MultiBufferSource, text: String, scale: Float, align: RenderGravity, x: Float, y: Float, drawShadow: Boolean = false, displayMode: Font.DisplayMode = Font.DisplayMode.NORMAL, packedLightCoords: Int = 15728880, effectColor: Int = 0, color: RGBAColor) = drawScaledAligned(poseStack, buffer, text, scale, align, x, y, color.toRGB(), drawShadow, displayMode, packedLightCoords, effectColor)
fun Font.drawScaledAligned(poseStack: PoseStack, buffer: MultiBufferSource, text: Component, scale: Float, align: RenderGravity, x: Float, y: Float, drawShadow: Boolean = false, displayMode: Font.DisplayMode = Font.DisplayMode.NORMAL, packedLightCoords: Int = 15728880, effectColor: Int = 0, color: RGBAColor) = drawScaledAligned(poseStack, buffer, text, scale, align, x, y, color.toRGB(), drawShadow, displayMode, packedLightCoords, effectColor)
fun Font.drawScaledAligned(poseStack: PoseStack, buffer: MultiBufferSource, text: FormattedCharSequence, scale: Float, align: RenderGravity, x: Float, y: Float, drawShadow: Boolean = false, displayMode: Font.DisplayMode = Font.DisplayMode.NORMAL, packedLightCoords: Int = 15728880, effectColor: Int = 0, color: RGBAColor) = drawScaledAligned(poseStack, buffer, text, scale, align, x, y, color.toRGB(), drawShadow, displayMode, packedLightCoords, effectColor)

fun GuiGraphics.drawAligned(font: Font, text: String, align: RenderGravity, x: Float, y: Float, color: Int) 							= drawAlignedDuckTyped(font, text, align, x, y, color)
fun GuiGraphics.drawAligned(font: Font, text: Component, align: RenderGravity, x: Float, y: Float, color: Int) 							= drawAlignedDuckTyped(font, text, align, x, y, color)
fun GuiGraphics.drawAligned(font: Font, text: FormattedCharSequence, align: RenderGravity, x: Float, y: Float, color: Int) 				= drawAlignedDuckTyped(font, text, align, x, y, color)

fun GuiGraphics.drawAligned(font: Font, text: String, align: RenderGravity, x: Float, y: Float, color: RGBAColor) 						= drawAligned(font, text, align, x, y, color.toRGB())
fun GuiGraphics.drawAligned(font: Font, text: Component, align: RenderGravity, x: Float, y: Float, color: RGBAColor) 					= drawAligned(font, text, align, x, y, color.toRGB())
fun GuiGraphics.drawAligned(font: Font, text: FormattedCharSequence, align: RenderGravity, x: Float, y: Float, color: RGBAColor) 		= drawAligned(font, text, align, x, y, color.toRGB())

fun GuiGraphics.drawScaledAligned(font: Font, text: String, scale: Float, align: RenderGravity, x: Float, y: Float, color: Int) 		= drawScaledAlignedDuckTyped(font, text, scale, align, x, y, color)
fun GuiGraphics.drawScaledAligned(font: Font, text: Component, scale: Float, align: RenderGravity, x: Float, y: Float, color: Int) 		= drawScaledAlignedDuckTyped(font, text, scale, align, x, y, color)
fun GuiGraphics.drawScaledAligned(font: Font, text: FormattedCharSequence, scale: Float, align: RenderGravity, x: Float, y: Float, color: Int) = drawScaledAlignedDuckTyped(font, text, scale, align, x, y, color)

fun GuiGraphics.drawScaledAligned(font: Font, text: String, scale: Float, align: RenderGravity, x: Float, y: Float, color: RGBAColor) 	= drawScaledAligned(font, text, scale, align, x, y, color.toRGB())
fun GuiGraphics.drawScaledAligned(font: Font, text: Component, scale: Float, align: RenderGravity, x: Float, y: Float, color: RGBAColor) = drawScaledAligned(font, text, scale, align, x, y, color.toRGB())
fun GuiGraphics.drawScaledAligned(font: Font, text: FormattedCharSequence, scale: Float, align: RenderGravity, x: Float, y: Float, color: RGBAColor) = drawScaledAligned(font, text, scale, align, x, y, color.toRGB())
