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

import com.mojang.blaze3d.vertex.*
import net.minecraft.client.renderer.MultiBufferSource
import net.minecraft.client.renderer.RenderType
import net.minecraft.client.renderer.blockentity.BeaconRenderer
import net.minecraft.client.renderer.blockentity.BlockEntityRenderer
import net.minecraft.client.renderer.blockentity.BlockEntityRendererProvider
import net.minecraft.client.renderer.texture.OverlayTexture
import net.minecraft.core.Direction
import org.joml.Matrix4f
import ru.dbotthepony.mc.otm.core.TranslatableComponent
import ru.dbotthepony.mc.otm.block.BlackHoleBlock
import ru.dbotthepony.mc.otm.block.entity.tech.GravitationStabilizerBlockEntity
import ru.dbotthepony.mc.otm.block.entity.blackhole.BlackHoleBlockEntity
import ru.dbotthepony.mc.otm.block.entity.WorkerState
import ru.dbotthepony.mc.otm.client.ShiftPressedCond
import ru.dbotthepony.mc.otm.client.minecraft
import ru.dbotthepony.mc.otm.client.render.*
import ru.dbotthepony.mc.otm.core.*
import ru.dbotthepony.mc.otm.core.math.RGBAColor
import ru.dbotthepony.mc.otm.core.math.VECTOR_DOWN
import ru.dbotthepony.mc.otm.core.math.VECTOR_FORWARD
import ru.dbotthepony.mc.otm.core.math.VECTOR_RIGHT
import ru.dbotthepony.mc.otm.core.math.VECTOR_UP
import ru.dbotthepony.mc.otm.core.math.Vector
import ru.dbotthepony.mc.otm.core.math.asAngle
import ru.dbotthepony.mc.otm.core.math.facingTwo
import ru.dbotthepony.mc.otm.core.math.plus
import ru.dbotthepony.mc.otm.core.math.rotateAroundAxis
import ru.dbotthepony.mc.otm.core.math.times
import ru.dbotthepony.mc.otm.core.util.formatMatter
import kotlin.math.PI

@Suppress("PrivatePropertyName")
class GravitationStabilizerRenderer(private val context: BlockEntityRendererProvider.Context) : BlockEntityRenderer<GravitationStabilizerBlockEntity> {
	override fun render(
		tile: GravitationStabilizerBlockEntity,
		partialTick: Float,
		poseStack: PoseStack,
		bufferSource: MultiBufferSource,
		packedLight: Int,
		overlayLight: Int
	) {
		if (tile.blockState.getValue(WorkerState.SEMI_WORKER_STATE) != WorkerState.WORKING)
			return

		var len = 64.0
		var lenI = 64
		val normal = tile.blockState.facingTwo.normal
		val level = tile.level
		var bhTile: BlackHoleBlockEntity? = null

		if (level != null) {
			for (i in 1 .. GravitationStabilizerBlockEntity.RANGE) {
				val pos = tile.blockPos + normal * i
				val getTile = level.getBlockEntity(pos) as? BlackHoleBlockEntity

				if (getTile != null || level.getBlockState(pos).block is BlackHoleBlock) {
					len = i.toDouble()
					lenI = i
					bhTile = getTile
					break
				}
			}
		}

		poseStack.pushPose()
		poseStack.translate(0.5, 0.5, 0.5)

		val facing = tile.blockState.facingTwo

		val rotateZ: Double
		val rotateY: Double

		when (facing) {
			Direction.DOWN -> {
				rotateZ = -PI / 2
				rotateY = 0.0
			}

			Direction.UP -> {
				rotateZ = PI / 2
				rotateY = 0.0
			}

			Direction.NORTH -> {
				rotateY = PI / 2
				rotateZ = 0.0
			}

			Direction.SOUTH -> {
				rotateY = -PI / 2
				rotateZ = 0.0
			}

			Direction.WEST -> {
				rotateY = PI
				rotateZ = 0.0
			}

			Direction.EAST -> {
				rotateY = 0.0
				rotateZ = 0.0
			}
		}

		val rotationInner = (System.currentTimeMillis().toDouble() / 1000.0) % (PI * 2)
		val rotationOuter = (-System.currentTimeMillis().toDouble() / 600.0) % (PI * 2)

		val halfStep = normal * 0.3

		run {
			val consumer = bufferSource.getBuffer(BEAM_RENDER_TYPE_INNER)

			poseStack.pushPose()

			ray(poseStack.last().pose(), consumer, 0.3, rotationInner, rotateZ, rotateY, RAY_RADIUS_INNER, INNER_COLOR_A)
			poseStack.translate(halfStep)
			ray(poseStack.last().pose(), consumer, 0.3, rotationInner, rotateZ, rotateY, RAY_RADIUS_INNER, INNER_COLOR_B)
			poseStack.translate(halfStep)
			ray(poseStack.last().pose(), consumer, 0.3, rotationInner, rotateZ, rotateY, RAY_RADIUS_INNER, INNER_COLOR_C)
			poseStack.translate(halfStep)
			ray(poseStack.last().pose(), consumer, len - 0.9, rotationInner, rotateZ, rotateY, RAY_RADIUS_INNER, INNER_COLOR_D)

			poseStack.popPose()
		}

		run {
			val consumer = bufferSource.getBuffer(BEAM_RENDER_TYPE_OUTER)

			poseStack.pushPose()

			ray(poseStack.last().pose(), consumer, 0.3, rotationOuter, rotateZ, rotateY, RAY_RADIUS_OUTER, OUTER_COLOR_A)
			poseStack.translate(halfStep)
			ray(poseStack.last().pose(), consumer, 0.3, rotationOuter, rotateZ, rotateY, RAY_RADIUS_OUTER, OUTER_COLOR_B)
			poseStack.translate(halfStep)
			ray(poseStack.last().pose(), consumer, 0.3, rotationOuter, rotateZ, rotateY, RAY_RADIUS_OUTER, OUTER_COLOR_C)
			poseStack.translate(halfStep)
			ray(poseStack.last().pose(), consumer, len - 0.9, rotationOuter, rotateZ, rotateY, RAY_RADIUS_OUTER, OUTER_COLOR_D)

			poseStack.popPose()
		}

		poseStack.popPose()

		if (bhTile != null) {
			poseStack.pushPose()
			poseStack.translate(0.5, 0.5, 0.5)

			poseStack.translate(facing.opposite.normal * 0.5)
			poseStack.rotateAroundPoint(poseStack.translation(), facing.opposite.asAngle().copy(pitch = PI))

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

			val sorse = DynamicBufferSource.WORLD
			val font = minecraft.font
			font.drawAligned(poseStack, sorse, TranslatableComponent("otm.3d2d.gravitation_stabilizer.mass", bhTile.mass.formatMatter(formatAsReadable = ShiftPressedCond)), RenderGravity.TOP_CENTER, 1f, -font.lineHeight.toFloat() / 2f + 1f, 0)
			font.drawAligned(poseStack, sorse, TranslatableComponent("otm.3d2d.gravitation_stabilizer.strength", "%.2f".format(bhTile.gravitationStrength)), RenderGravity.TOP_CENTER, 1f, font.lineHeight.toFloat() / 2f + 1f, 0)

			poseStack.translate(0.2f, 0f, -0.5f)
			font.drawAligned(poseStack, sorse, TranslatableComponent("otm.3d2d.gravitation_stabilizer.mass", bhTile.mass.formatMatter(formatAsReadable = ShiftPressedCond)), RenderGravity.TOP_CENTER, 0f, -font.lineHeight.toFloat() / 2f, 0xFFFFFF)
			font.drawAligned(poseStack, sorse, TranslatableComponent("otm.3d2d.gravitation_stabilizer.strength", "%.2f".format(bhTile.gravitationStrength)), RenderGravity.TOP_CENTER, 0f, font.lineHeight.toFloat() / 2f, 0xFFFFFF)

			poseStack.popPose()
		}
	}

	override fun shouldRenderOffScreen(p_112306_: GravitationStabilizerBlockEntity) = true
	override fun getViewDistance() = 96

	companion object {
		private val BEAM_RENDER_TYPE_INNER = RenderType.beaconBeam(BeaconRenderer.BEAM_LOCATION, false)
		private val BEAM_RENDER_TYPE_OUTER = RenderType.beaconBeam(BeaconRenderer.BEAM_LOCATION, true)

		private val OUTER_COLOR_A = RGBAColor(37, 255, 129, 110)
		private val OUTER_COLOR_B = RGBAColor(37, 255, 129, 110)
		private val OUTER_COLOR_C = RGBAColor(37, 255, 129, 110)
		private val OUTER_COLOR_D = RGBAColor(37, 255, 129, 110)

		private val INNER_COLOR_A = RGBAColor(69, 240, 255)
		private val INNER_COLOR_B = RGBAColor(69, 240, 255)
		private val INNER_COLOR_C = RGBAColor(69, 240, 255)
		private val INNER_COLOR_D = RGBAColor.WHITE

		//(0, 36, 255, 110)
		//(32, 179, 255, 110)

		//(174, 115, 0)
		//(69, 240, 255)
		//(69, 240, 255)

		private const val RAY_RADIUS_INNER = 0.18
		private const val RAY_RADIUS_OUTER = 0.22
		private const val k = 2.414213562373095 // 1.0 + sqrt(2.0)
		private const val kSquared = 1.3065629648763766 // sqrt(k / (k - 1))
		private const val PI_STEP = PI / 4

		private fun quad(
			matrix4f: Matrix4f,
			builder: VertexConsumer,
			len: Double,
			fwdRotation: Double,
			rightRotation: Double,
			topRotation: Double,
			anim: Float,
			rayRadius: Double,
			color: RGBAColor
		) {
			val rayWidth = rayRadius / kSquared

			var a = Vector(0.0, -rayRadius, -rayWidth / 1.84)
			var b = Vector(len, -rayRadius, -rayWidth / 1.84)
			var c = Vector(len, -rayRadius, rayWidth / 1.84)
			var d = Vector(0.0, -rayRadius, rayWidth / 1.84)
			var normal = VECTOR_DOWN

			if (fwdRotation != 0.0) {
				a = a.rotateAroundAxis(VECTOR_FORWARD, fwdRotation)
				b = b.rotateAroundAxis(VECTOR_FORWARD, fwdRotation)
				c = c.rotateAroundAxis(VECTOR_FORWARD, fwdRotation)
				d = d.rotateAroundAxis(VECTOR_FORWARD, fwdRotation)
				normal = normal.rotateAroundAxis(VECTOR_FORWARD, fwdRotation)
			}

			if (rightRotation != 0.0) {
				a = a.rotateAroundAxis(VECTOR_RIGHT, rightRotation)
				b = b.rotateAroundAxis(VECTOR_RIGHT, rightRotation)
				c = c.rotateAroundAxis(VECTOR_RIGHT, rightRotation)
				d = d.rotateAroundAxis(VECTOR_RIGHT, rightRotation)
				normal = normal.rotateAroundAxis(VECTOR_RIGHT, rightRotation)
			} else if (topRotation != 0.0) {
				a = a.rotateAroundAxis(VECTOR_UP, topRotation)
				b = b.rotateAroundAxis(VECTOR_UP, topRotation)
				c = c.rotateAroundAxis(VECTOR_UP, topRotation)
				d = d.rotateAroundAxis(VECTOR_UP, topRotation)
				normal = normal.rotateAroundAxis(VECTOR_UP, topRotation)
			}

			builder.vertex(matrix4f, b).color(color).uv(0f, anim).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(15728880).normal(normal).endVertex()
			builder.vertex(matrix4f, c).color(color).uv(1f, anim).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(15728880).normal(normal).endVertex()
			builder.vertex(matrix4f, d).color(color).uv(1f, anim + len.toFloat()).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(15728880).normal(normal).endVertex()
			builder.vertex(matrix4f, a).color(color).uv(0f, anim + len.toFloat()).overlayCoords(OverlayTexture.NO_OVERLAY).uv2(15728880).normal(normal).endVertex()
		}

		private fun ray(
			matrix4f: Matrix4f,
			builder: VertexConsumer,
			len: Double,
			fwdRotation: Double,
			rightRotation: Double,
			topRotation: Double,
			rayRadius: Double,
			color: RGBAColor
		) {
			val anim = (((System.currentTimeMillis().toDouble() / 100.0) % 100.0).toFloat() / 100f) * len.toFloat()

			for (i in 0 until 8) {
				quad(matrix4f, builder, len, i * PI_STEP + fwdRotation, rightRotation, topRotation, anim, rayRadius, color)
			}
		}
	}
}
