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

import com.mojang.blaze3d.vertex.PoseStack
import it.unimi.dsi.fastutil.floats.Float2ObjectArrayMap
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider
import net.minecraft.core.Direction
import net.minecraft.network.chat.Component
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.block.tech.EnergyCounterBlock
import ru.dbotthepony.mc.otm.block.entity.tech.EnergyCounterBlockEntity
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.client.render.*
import ru.dbotthepony.mc.otm.core.TextComponent
import ru.dbotthepony.kommons.math.RGBAColor
import ru.dbotthepony.mc.otm.core.RandomSource2Generator
import ru.dbotthepony.mc.otm.core.math.Decimal
import ru.dbotthepony.mc.otm.core.math.asAngle
import ru.dbotthepony.mc.otm.core.math.clusterize
import ru.dbotthepony.mc.otm.core.util.formatPower
import ru.dbotthepony.mc.otm.core.math.times
import java.util.Random
import kotlin.math.PI
import kotlin.math.absoluteValue

class EnergyCounterRenderer(private val context: BlockEntityRendererProvider.Context) : BlockEntityRenderer<EnergyCounterBlockEntity> {
	private val random = Random()

	override fun render(
		tile: EnergyCounterBlockEntity,
		p_112308_: Float,
		poseStack: PoseStack,
		p_112310_: MultiBufferSource,
		p_112311_: Int,
		p_112312_: Int
	) {
		val screenDir: Direction = tile.blockState.getValue(EnergyCounterBlock.IF_DIRECTION)
		val inputDir: Direction = tile.blockState.getValue(EnergyCounterBlock.INPUT_DIRECTION)

		poseStack.pushPose()
		poseStack.translate(0.5, 0.5, 0.5)
		poseStack.translate(screenDir.normal * 0.44)

		val font = minecraft.font

		poseStack.scale(0.01f, 0.01f, 0.01f)

		val sorse = DynamicBufferSource.WORLD

		if (screenDir === Direction.DOWN) {
			poseStack.rotateAroundPoint(poseStack.translation(), inputDir.asAngle().copy(pitch = PI, roll = PI / 2))
		} else if (screenDir === Direction.UP) {
			poseStack.rotateAroundPoint(poseStack.translation(), inputDir.asAngle().copy(pitch = 0.0, roll = PI / 2))
		} else {
			poseStack.rotateAroundPoint(poseStack.translation(), screenDir.asAngle().copy(pitch = PI))
		}

		poseStack.scale(1f, 1f, -1f)

		if (tile.displayChartOnBlock == 0) {
			var y = -16f

			val finalX = font.draw(poseStack, buffer = sorse, text = "00000000", gravity = RenderGravity.CENTER_CENTER, x = -4f, y = y, color = RGBAColor.GRAY)
			font.draw(poseStack, buffer = sorse, text = "00000000", gravity = RenderGravity.CENTER_CENTER, x = -4f, y = y + font.lineHeight, color = RGBAColor.GRAY)
			font.draw(poseStack, buffer = sorse, text = "/t", gravity = RenderGravity.CENTER_LEFT, x = finalX, y = y, color = RGBAColor.GRAY)
			font.draw(poseStack, buffer = sorse, text = "/s", gravity = RenderGravity.CENTER_LEFT, x = finalX, y = y + font.lineHeight, color = RGBAColor.GRAY)

			poseStack.pushPose()
			poseStack.translate(-0.1f, -0.1f, 0.1f)
			font.draw(poseStack, buffer = sorse, text = tile.lastTick.toString(0), gravity = RenderGravity.CENTER_RIGHT, x = finalX, y = y, color = RGBAColor.WHITE)
			font.draw(poseStack, buffer = sorse, text = tile.history5s.calculateSum(20).toString(0), gravity = RenderGravity.CENTER_RIGHT, x = finalX, y = y + font.lineHeight, color = RGBAColor.WHITE)
			poseStack.popPose()

			y += font.lineHeight * 3

			font.draw(poseStack, buffer = sorse, text = TOTAL, gravity = RenderGravity.CENTER_CENTER, x = 0f, y = y, color = RGBAColor.WHITE)
			font.draw(poseStack, buffer = sorse, text =  tile.passed.formatPower(), gravity = RenderGravity.CENTER_CENTER, x = 0f, y = y + font.lineHeight, color = RGBAColor.WHITE)
		} else {
			val chart = tile.charts.getOrNull(tile.displayChartOnBlock - 1)

			if (chart != null) {
				val maximum = chart.max()
				val normalized: FloatArray
				val levelLabels: ChartLevelLabels

				if (maximum.isZero || maximum.isInfinite) {
					normalized = FloatArray(chart.width) {
						if (chart[it].isInfinite) 1f else 0.0f
					}

					levelLabels = ChartLevelLabels(
						labels = mapOf(
							1f to TextComponent("∞"),
							0f to Decimal.ZERO.formatPower(),
						),
						font = font,
						textGravity = RenderGravity.TOP_LEFT
					)
				} else {
					normalized = FloatArray(chart.width) { (chart[it] / maximum).toFloat() }

					val map = Float2ObjectArrayMap<Component>()

					map[1f] = maximum.formatPower()

					for (cluster in chart.asIterable().clusterize(random, zeroBound = true)) {
						val perc = (cluster.center / maximum).toFloat()

						if (map.keys.none { (it - perc).absoluteValue < 0.08f }) {
							map[perc] = cluster.center.formatPower()
						}
					}

					/*
					if (map.keys.none { (it - 0.75f).absoluteValue < 0.1f })
						map[0.75f] = (maximum * 0.75).formatPower()

					if (map.keys.none { (it - 0.5f).absoluteValue < 0.1f })
						map[0.5f] = (maximum * 0.5).formatPower()

					if (map.keys.none { (it - 0.25f).absoluteValue < 0.1f })
						map[0.25f] = (maximum * 0.25).formatPower()
					*/

					levelLabels = ChartLevelLabels(
						labels = map,
						font = font,
						textGravity = RenderGravity.TOP_LEFT
					)
				}

				renderChart(
					poseStack,
					listOf(normalized to RGBAColor.WHITE),
					60f,
					50f,
					buffer = sorse,
					levelLabels = levelLabels,
					x = -30f,
					y = -20f,
					textScale = 0.5f,
					lineWidth = 0.5f
				)
			}
		}

		poseStack.popPose()
	}

	override fun getViewDistance() = 32

	companion object {
		private val TOTAL = TranslatableComponent("otm.gui.total_raw")
	}
}
