package net.minecraft.client.render.entity;

import java.util.ArrayList;
import java.util.List;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.minecraft.client.MinecraftClient;
import net.minecraft.client.font.TextRenderer;
import net.minecraft.client.render.Camera;
import net.minecraft.client.render.OverlayTexture;
import net.minecraft.client.render.RenderLayer;
import net.minecraft.client.render.VertexConsumer;
import net.minecraft.client.render.VertexConsumerProvider;
import net.minecraft.client.render.block.BlockRenderManager;
import net.minecraft.client.render.item.ItemRenderer;
import net.minecraft.client.texture.SpriteAtlasTexture;
import net.minecraft.client.util.math.MatrixStack;
import net.minecraft.entity.decoration.DisplayEntity;
import net.minecraft.entity.decoration.DisplayEntity.BlockDisplayEntity;
import net.minecraft.entity.decoration.DisplayEntity.ItemDisplayEntity;
import net.minecraft.entity.decoration.DisplayEntity.RenderState;
import net.minecraft.entity.decoration.DisplayEntity.TextDisplayEntity;
import net.minecraft.entity.decoration.DisplayEntity.BlockDisplayEntity.Data;
import net.minecraft.entity.decoration.DisplayEntity.TextDisplayEntity.TextAlignment;
import net.minecraft.entity.decoration.DisplayEntity.TextDisplayEntity.TextLine;
import net.minecraft.entity.decoration.DisplayEntity.TextDisplayEntity.TextLines;
import net.minecraft.text.OrderedText;
import net.minecraft.text.Text;
import net.minecraft.util.Identifier;
import net.minecraft.util.math.AffineTransformation;
import net.minecraft.util.math.RotationAxis;
import org.jetbrains.annotations.Nullable;
import org.joml.Matrix4f;
import org.joml.Quaternionf;

@Environment(EnvType.CLIENT)
public abstract class DisplayEntityRenderer<T extends DisplayEntity, S> extends EntityRenderer<T> {
	private final EntityRenderDispatcher renderDispatcher;

	protected DisplayEntityRenderer(EntityRendererFactory.Context context) {
		super(context);
		this.renderDispatcher = context.getRenderDispatcher();
	}

	public Identifier getTexture(T displayEntity) {
		return SpriteAtlasTexture.BLOCK_ATLAS_TEXTURE;
	}

	public void render(T displayEntity, float f, float g, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i) {
		RenderState renderState = displayEntity.getRenderState();
		if (renderState != null) {
			S object = this.getData(displayEntity);
			if (object != null) {
				float h = displayEntity.getLerpProgress(g);
				this.shadowRadius = renderState.shadowRadius().lerp(h);
				this.shadowOpacity = renderState.shadowStrength().lerp(h);
				int j = renderState.brightnessOverride();
				int k = j != -1 ? j : i;
				super.render(displayEntity, f, g, matrixStack, vertexConsumerProvider, k);
				matrixStack.push();
				matrixStack.multiply(this.getBillboardRotation(renderState, displayEntity));
				AffineTransformation affineTransformation = (AffineTransformation)renderState.transformation().interpolate(h);
				matrixStack.multiplyPositionMatrix(affineTransformation.getMatrix());
				matrixStack.peek().getNormalMatrix().rotate(affineTransformation.getLeftRotation()).rotate(affineTransformation.getRightRotation());
				this.render(displayEntity, object, matrixStack, vertexConsumerProvider, k, h);
				matrixStack.pop();
			}
		}
	}

	private Quaternionf getBillboardRotation(RenderState renderState, T entity) {
		Camera camera = this.renderDispatcher.camera;

		return switch (renderState.billboardConstraints()) {
			case FIXED -> entity.getFixedRotation();
			case HORIZONTAL -> new Quaternionf().rotationYXZ((float) (-Math.PI / 180.0) * entity.getYaw(), (float) (-Math.PI / 180.0) * camera.getPitch(), 0.0F);
			case VERTICAL -> new Quaternionf()
				.rotationYXZ((float) Math.PI - (float) (Math.PI / 180.0) * camera.getYaw(), (float) (Math.PI / 180.0) * entity.getPitch(), 0.0F);
			case CENTER -> new Quaternionf()
				.rotationYXZ((float) Math.PI - (float) (Math.PI / 180.0) * camera.getYaw(), (float) (-Math.PI / 180.0) * camera.getPitch(), 0.0F);
			default -> throw new IncompatibleClassChangeError();
		};
	}

	@Nullable
	protected abstract S getData(T entity);

	protected abstract void render(T entity, S data, MatrixStack matrices, VertexConsumerProvider vertexConsumers, int brightness, float lerpProgress);

	@Environment(EnvType.CLIENT)
	public static class BlockDisplayEntityRenderer extends DisplayEntityRenderer<BlockDisplayEntity, Data> {
		private final BlockRenderManager blockRenderManager;

		protected BlockDisplayEntityRenderer(EntityRendererFactory.Context context) {
			super(context);
			this.blockRenderManager = context.getBlockRenderManager();
		}

		@Nullable
		protected Data getData(BlockDisplayEntity blockDisplayEntity) {
			return blockDisplayEntity.getData();
		}

		public void render(BlockDisplayEntity blockDisplayEntity, Data data, MatrixStack matrixStack, VertexConsumerProvider vertexConsumerProvider, int i, float f) {
			this.blockRenderManager.renderBlockAsEntity(data.blockState(), matrixStack, vertexConsumerProvider, i, OverlayTexture.DEFAULT_UV);
		}
	}

	@Environment(EnvType.CLIENT)
	public static class ItemDisplayEntityRenderer
		extends DisplayEntityRenderer<ItemDisplayEntity, net.minecraft.entity.decoration.DisplayEntity.ItemDisplayEntity.Data> {
		private final ItemRenderer itemRenderer;

		protected ItemDisplayEntityRenderer(EntityRendererFactory.Context context) {
			super(context);
			this.itemRenderer = context.getItemRenderer();
		}

		@Nullable
		protected net.minecraft.entity.decoration.DisplayEntity.ItemDisplayEntity.Data getData(ItemDisplayEntity itemDisplayEntity) {
			return itemDisplayEntity.getData();
		}

		public void render(
			ItemDisplayEntity itemDisplayEntity,
			net.minecraft.entity.decoration.DisplayEntity.ItemDisplayEntity.Data data,
			MatrixStack matrixStack,
			VertexConsumerProvider vertexConsumerProvider,
			int i,
			float f
		) {
			matrixStack.multiply(RotationAxis.POSITIVE_Y.rotation((float) Math.PI));
			this.itemRenderer
				.renderItem(
					data.itemStack(),
					data.itemTransform(),
					i,
					OverlayTexture.DEFAULT_UV,
					matrixStack,
					vertexConsumerProvider,
					itemDisplayEntity.getWorld(),
					itemDisplayEntity.getId()
				);
		}
	}

	@Environment(EnvType.CLIENT)
	public static class TextDisplayEntityRenderer
		extends DisplayEntityRenderer<TextDisplayEntity, net.minecraft.entity.decoration.DisplayEntity.TextDisplayEntity.Data> {
		private final TextRenderer displayTextRenderer;

		protected TextDisplayEntityRenderer(EntityRendererFactory.Context context) {
			super(context);
			this.displayTextRenderer = context.getTextRenderer();
		}

		private TextLines getLines(Text text, int width) {
			List<OrderedText> list = this.displayTextRenderer.wrapLines(text, width);
			List<TextLine> list2 = new ArrayList(list.size());
			int i = 0;

			for (OrderedText orderedText : list) {
				int j = this.displayTextRenderer.getWidth(orderedText);
				i = Math.max(i, j);
				list2.add(new TextLine(orderedText, j));
			}

			return new TextLines(list2, i);
		}

		@Nullable
		protected net.minecraft.entity.decoration.DisplayEntity.TextDisplayEntity.Data getData(TextDisplayEntity textDisplayEntity) {
			return textDisplayEntity.getData();
		}

		public void render(
			TextDisplayEntity textDisplayEntity,
			net.minecraft.entity.decoration.DisplayEntity.TextDisplayEntity.Data data,
			MatrixStack matrixStack,
			VertexConsumerProvider vertexConsumerProvider,
			int i,
			float f
		) {
			byte b = data.flags();
			boolean bl = (b & 2) != 0;
			boolean bl2 = (b & 4) != 0;
			boolean bl3 = (b & 1) != 0;
			TextAlignment textAlignment = TextDisplayEntity.getAlignment(b);
			byte c = (byte)data.textOpacity().lerp(f);
			int j;
			if (bl2) {
				float g = MinecraftClient.getInstance().options.getTextBackgroundOpacity(0.25F);
				j = (int)(g * 255.0F) << 24;
			} else {
				j = data.backgroundColor().lerp(f);
			}

			float g = 0.0F;
			Matrix4f matrix4f = matrixStack.peek().getPositionMatrix();
			matrix4f.rotate((float) Math.PI, 0.0F, 1.0F, 0.0F);
			matrix4f.scale(-0.025F, -0.025F, -0.025F);
			TextLines textLines = textDisplayEntity.splitLines(this::getLines);
			int k = 9 + 1;
			int l = textLines.width();
			int m = textLines.lines().size() * k;
			matrix4f.translate(1.0F - l / 2.0F, -m, 0.0F);
			if (j != 0) {
				VertexConsumer vertexConsumer = vertexConsumerProvider.getBuffer(bl ? RenderLayer.getTextBackgroundSeeThrough() : RenderLayer.getTextBackground());
				vertexConsumer.vertex(matrix4f, -1.0F, -1.0F, 0.0F).color(j).light(i).next();
				vertexConsumer.vertex(matrix4f, -1.0F, m, 0.0F).color(j).light(i).next();
				vertexConsumer.vertex(matrix4f, l, m, 0.0F).color(j).light(i).next();
				vertexConsumer.vertex(matrix4f, l, -1.0F, 0.0F).color(j).light(i).next();
			}

			for (TextLine textLine : textLines.lines()) {
				float h = switch (textAlignment) {
					case LEFT -> 0.0F;
					case RIGHT -> l - textLine.width();
					case CENTER -> l / 2.0F - textLine.width() / 2.0F;
					default -> throw new IncompatibleClassChangeError();
				};
				this.displayTextRenderer
					.draw(
						textLine.contents(),
						h,
						g,
						c << 24 | 16777215,
						bl3,
						matrix4f,
						vertexConsumerProvider,
						bl ? TextRenderer.TextLayerType.SEE_THROUGH : TextRenderer.TextLayerType.POLYGON_OFFSET,
						0,
						i
					);
				g += k;
			}
		}
	}
}
