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

import com.google.gson.JsonObject
import com.mojang.blaze3d.systems.RenderSystem
import com.mojang.blaze3d.vertex.DefaultVertexFormat
import com.mojang.blaze3d.vertex.PoseStack
import com.mojang.blaze3d.vertex.VertexConsumer
import com.mojang.blaze3d.vertex.VertexFormat
import net.minecraft.client.gui.GuiGraphics
import net.minecraft.client.renderer.GameRenderer
import net.minecraft.client.renderer.RenderStateShard
import net.minecraft.client.renderer.RenderType
import net.minecraft.network.FriendlyByteBuf
import net.minecraft.resources.ResourceLocation
import org.lwjgl.opengl.GL11.GL_ALWAYS
import ru.dbotthepony.mc.otm.core.math.RGBAColor
import ru.dbotthepony.mc.otm.core.math.linearInterpolation
import java.util.concurrent.ConcurrentHashMap

sealed class AbstractMatterySprite : IGUIRenderable, IUVCoords {
	/**
	 * Expected image width in pixels, used in calculations
	 * and as default width argument in render methods
	 */
	abstract override val width: Float

	/**
	 * Expected image height in pixels, used in calculations
	 * and as default height argument in render methods
	 */
	abstract override val height: Float

	fun partialU(offset: Float): Float {
		return u0 + (offset / width) * (u1 - u0)
	}

	fun partialV(offset: Float): Float {
		return v0 + (offset / height) * (v1 - v0)
	}

	abstract val type: SpriteType
	override val winding: UVWindingOrder get() = UVWindingOrder.NORMAL
	abstract val texture: ResourceLocation

	/**
	 * See [ru.dbotthepony.mc.otm.client.render.clearDepth]
	 */
	fun clearDepth(
		graphics: GuiGraphics,
		x: Float = 0f,
		y: Float = 0f,
		width: Float = this.width,
		height: Float = this.height,
	) = ru.dbotthepony.mc.otm.client.render.clearDepth(graphics, x, y, width, height)

	fun render(
		stack: PoseStack,
		x: Float = 0f,
		y: Float = 0f,
		width: Float = this.width,
		height: Float = this.height,
		winding: UVWindingOrder = this.winding
	) {
		renderTexturedRect(stack.last().pose(), x, y, width, height, uvWinding = winding, uv = this, texture = texture)
	}

	override fun render(
		guiGraphics: GuiGraphics,
		x: Float,
		y: Float,
		width: Float,
		height: Float,
		winding: UVWindingOrder,
		color: RGBAColor
	) {
		guiGraphics.renderTexturedRect(x, y, width, height, uvWinding = winding, color = color, texture = texture, uv = this)
	}

	fun renderPartial(
		stack: PoseStack,
		x: Float = 0f,
		y: Float = 0f,
		width: Float = this.width,
		height: Float = this.height,
		winding: UVWindingOrder = this.winding,
		color: RGBAColor = RGBAColor.WHITE
	) {
		val u1 = u0 + linearInterpolation(width / this.width, 0f, u1 - u0)
		val v1 = v0 + linearInterpolation(height / this.height, 0f, v1 - v0)

		val winded = winding.translate(u0, v0, u1.coerceIn(0f, 1f), v1.coerceIn(0f, 1f))

		renderTexturedRect(
			stack.last().pose(),
			x = x, y = y,
			width = width, height = height,
			uv = winded, texture = texture,
			color = color
		)
	}

	fun renderPartial(
		graphics: GuiGraphics,
		x: Float = 0f,
		y: Float = 0f,
		width: Float = this.width,
		height: Float = this.height,
		winding: UVWindingOrder = this.winding,
		color: RGBAColor = RGBAColor.WHITE
	) = renderPartial(graphics.pose(), x, y, width, height, winding, color)

	protected fun uploadOnto(
		pose: PoseStack,
		builder: VertexConsumer,
		x: Float,
		y: Float,
		width: Float,
		height: Float,
		z: Float,
		color: RGBAColor?,
		u0: Float,
		v0: Float,
		u1: Float,
		v1: Float,
	) {
		val matrix = pose.last().pose()
		builder.vertex(matrix, x, y + height, z).color(color).uv(u0, v1).endVertex()
		builder.vertex(matrix, x + width, y + height, z).color(color).uv(u1, v1).endVertex()
		builder.vertex(matrix, x + width, y, z).color(color).uv(u1, v0).endVertex()
		builder.vertex(matrix, x, y, z).color(color).uv(u0, v0).endVertex()
	}

	fun uploadOnto(
		pose: PoseStack,
		builder: VertexConsumer,
		x: Float = 0f,
		y: Float = 0f,
		width: Float = this.width,
		height: Float = this.height,
		z: Float = 0f,
		color: RGBAColor? = null,
		winding: UVWindingOrder = this.winding,
	) {
		val u0 = winding.u0(this)
		val v0 = winding.v0(this)
		val u1 = winding.u1(this)
		val v1 = winding.v1(this)

		uploadOnto(pose, builder, x, y, width, height, z, color, u0, v0, u1, v1)
	}

	fun uploadOntoPartial(
		pose: PoseStack,
		builder: VertexConsumer,
		x: Float = 0f,
		y: Float = 0f,
		width: Float = this.width,
		height: Float = this.height,
		z: Float = 0f,
		color: RGBAColor? = null,
		winding: UVWindingOrder = this.winding,
	) {
		val u1 = (u0 + linearInterpolation(width / this.width, 0f, u1 - u0)).coerceAtLeast(0f).coerceAtMost(1f)
		val v1 = (v0 + linearInterpolation(height / this.height, 0f, v1 - v0)).coerceAtLeast(0f).coerceAtMost(1f)
		val (u0_, v0_, u1_, v1_) = winding.translate(u0, v0, u1, v1)

		uploadOnto(pose, builder, x, y, width, height, z, color, u0_, v0_, u1_, v1_)
	}

	fun toJson(): JsonObject {
		return type.toActualJson(this)
	}

	fun toNetwork(buff: FriendlyByteBuf) {
		type.toActualNetwork(this, buff)
	}

	val renderTypeNoDepth by lazy {
		noDepthCache.computeIfAbsent(texture) {
			val builder = RenderType.CompositeState.builder()

			builder.setTextureState(RenderStateShard.TextureStateShard(it, false, false))
			builder.setShaderState(RenderStateShard.ShaderStateShard(GameRenderer::getPositionColorTexShader))
			builder.setDepthTestState(RenderStateShard.DepthTestStateShard("always", GL_ALWAYS))
			builder.setTransparencyState(RenderStateShard.TransparencyStateShard("normal_blend", {
				RenderSystem.enableBlend()
				RenderSystem.defaultBlendFunc()
			}, {
				RenderSystem.disableBlend()
			}))

			@Suppress("INACCESSIBLE_TYPE")
			RenderType.create("otm_gui_element_no_depth",
				DefaultVertexFormat.POSITION_COLOR_TEX,
				VertexFormat.Mode.QUADS,
				2048,
				false,
				false,
				builder.createCompositeState(false)) as RenderType
		}
	}

	val renderTypeDepth by lazy {
		depthCache.computeIfAbsent(texture) {
			val builder = RenderType.CompositeState.builder()

			builder.setTextureState(RenderStateShard.TextureStateShard(it, false, false))
			builder.setShaderState(RenderStateShard.ShaderStateShard(GameRenderer::getPositionColorTexShader))
			builder.setTransparencyState(RenderStateShard.TransparencyStateShard("normal_blend", {
				RenderSystem.enableBlend()
				RenderSystem.defaultBlendFunc()
			}, {
				RenderSystem.disableBlend()
			}))

			@Suppress("INACCESSIBLE_TYPE")
			RenderType.create("otm_gui_element_depth",
				DefaultVertexFormat.POSITION_COLOR_TEX,
				VertexFormat.Mode.QUADS,
				2048,
				false,
				false,
				builder.createCompositeState(false)) as RenderType
		}
	}

	val renderTypeWorld by lazy {
		worldCache.computeIfAbsent(texture) {
			val builder = RenderType.CompositeState.builder()

			builder.setTextureState(RenderStateShard.TextureStateShard(it, false, false))
			builder.setShaderState(RenderStateShard.ShaderStateShard(GameRenderer::getPositionColorTexShader))
			builder.setTransparencyState(RenderStateShard.TransparencyStateShard("normal_blend", {
				RenderSystem.enableBlend()
				RenderSystem.defaultBlendFunc()
			}, {
				RenderSystem.disableBlend()
			}))

			@Suppress("INACCESSIBLE_TYPE")
			RenderType.create("otm_gui_element_world",
				DefaultVertexFormat.POSITION_COLOR_TEX,
				VertexFormat.Mode.QUADS,
				8192,
				false,
				false,
				builder.createCompositeState(false)) as RenderType
		}
	}

	companion object {
		private val noDepthCache = ConcurrentHashMap<ResourceLocation, RenderType>()
		private val depthCache = ConcurrentHashMap<ResourceLocation, RenderType>()
		private val worldCache = ConcurrentHashMap<ResourceLocation, RenderType>()
	}
}
