/*
 * Derived from Epic Fight (https://github.com/Epic-Fight/epicfight)
 * by the Epic Fight Team, licensed under GPLv3.
 * Modifications © 2026 TiedUp! Remake Contributors, distributed under GPLv3.
 */

package com.tiedup.remake.rig.render;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;
import java.util.function.BiFunction;
import java.util.function.Function;

import com.google.common.collect.Maps;
import com.mojang.blaze3d.vertex.DefaultVertexFormat;
import com.mojang.blaze3d.vertex.VertexFormat;

import net.minecraft.Util;
import net.minecraft.client.renderer.RenderStateShard;
import net.minecraft.client.renderer.RenderType;
import net.minecraft.resources.ResourceLocation;
import com.tiedup.remake.rig.TiedUpRigConstants;

public final class TiedUpRenderTypes extends RenderType {
	private static final BiFunction<ResourceLocation, RenderStateShard.CullStateShard, RenderType> TRIANGULATED_OUTLINE =
		Util.memoize((texLocation, cullStateShard) -> {
			return RenderType.create(
				TiedUpRigConstants.prefix("outline").toString(),
				DefaultVertexFormat.POSITION_COLOR_TEX,
				VertexFormat.Mode.TRIANGLES,
				256,
				false,
				false,
				RenderType.CompositeState.builder()
					.setShaderState(RENDERTYPE_OUTLINE_SHADER)
					.setTextureState(new RenderStateShard.TextureStateShard(texLocation, false, false))
					.setCullState(cullStateShard)
					.setDepthTestState(NO_DEPTH_TEST)
					.setOutputState(OUTLINE_TARGET)
					.createCompositeState(RenderType.OutlineProperty.IS_OUTLINE)
			);
		});

	private static final Map<String, Map<ResourceLocation, RenderType>> TRIANGLED_RENDERTYPES_BY_NAME_TEXTURE = new HashMap<> ();

	private static final Function<RenderType, RenderType> TRIANGULATED_RENDER_TYPES = Util.memoize(renderType -> {
		if (renderType.mode() == VertexFormat.Mode.TRIANGLES) {
			return renderType;
		}

		if (renderType instanceof CompositeRenderType compositeRenderType) {
			Optional<ResourceLocation> cutoutTexture;

			if (compositeRenderType.state.textureState instanceof TextureStateShard texStateShard) {
				cutoutTexture = texStateShard.texture;
			} else {
				cutoutTexture = Optional.empty();
			}

			if (TRIANGLED_RENDERTYPES_BY_NAME_TEXTURE.containsKey(renderType.name)) {
				Map<ResourceLocation, RenderType> renderTypesByTexture = TRIANGLED_RENDERTYPES_BY_NAME_TEXTURE.get(renderType.name);

				if (compositeRenderType.state.textureState instanceof TextureStateShard) {
					ResourceLocation texLocation = cutoutTexture.orElse(null);

					if (renderTypesByTexture.containsKey(texLocation)) {
						return renderTypesByTexture.get(texLocation);
					}
				}
			}

			CompositeRenderType triangulatedRenderType = new CompositeRenderType(
				renderType.name,
				renderType.format,
				VertexFormat.Mode.TRIANGLES,
				renderType.bufferSize(),
				renderType.affectsCrumbling(),
				renderType.sortOnUpload,
				compositeRenderType.state
			);

			triangulatedRenderType.outline = triangulatedRenderType.outline.isEmpty() ? triangulatedRenderType.outline : cutoutTexture.map(texLocation -> {
				return TRIANGULATED_OUTLINE.apply(texLocation, compositeRenderType.state.cullState);
			});

			return triangulatedRenderType;
		} else {
			return renderType;
		}
	});

	public static RenderType getTriangulated(RenderType renderType) {
		return TRIANGULATED_RENDER_TYPES.apply(renderType);
	}

	private static RenderType replaceTextureShard(ResourceLocation texToReplace, RenderType renderType) {
		if (renderType instanceof CompositeRenderType compositeRenderType && compositeRenderType.state.textureState instanceof TextureStateShard texStateShard) {
			CompositeState textureReplacedState = new CompositeState(
				  new RenderStateShard.TextureStateShard(texToReplace, texStateShard.blur, texStateShard.mipmap)
				, compositeRenderType.state.shaderState
				, compositeRenderType.state.transparencyState
				, compositeRenderType.state.depthTestState
				, compositeRenderType.state.cullState
				, compositeRenderType.state.lightmapState
				, compositeRenderType.state.overlayState
				, compositeRenderType.state.layeringState
				, compositeRenderType.state.outputState
				, compositeRenderType.state.texturingState
				, compositeRenderType.state.writeMaskState
				, compositeRenderType.state.lineState
				, compositeRenderType.state.colorLogicState
				, compositeRenderType.state.outlineProperty
			);

			return new CompositeRenderType(renderType.name, renderType.format, compositeRenderType.mode(), renderType.bufferSize(), renderType.affectsCrumbling(), renderType.sortOnUpload, textureReplacedState);
		} else {
			return null;
		}
	}

	public static RenderType replaceTexture(ResourceLocation texLocation, RenderType renderType) {
		if (TRIANGLED_RENDERTYPES_BY_NAME_TEXTURE.containsKey(renderType.name)) {
			Map<ResourceLocation, RenderType> renderTypesByTexture = TRIANGLED_RENDERTYPES_BY_NAME_TEXTURE.get(renderType.name);

			if (renderTypesByTexture.containsKey(texLocation)) {
				return renderTypesByTexture.get(texLocation);
			}
		}

		RenderType textureReplacedRenderType = replaceTextureShard(texLocation, renderType);

		if (textureReplacedRenderType == null) {
			return renderType;
		}

		Map<ResourceLocation, RenderType> renderTypesByTexture = TRIANGLED_RENDERTYPES_BY_NAME_TEXTURE.computeIfAbsent(textureReplacedRenderType.name, k -> Maps.newHashMap());
		renderTypesByTexture.put(texLocation, textureReplacedRenderType);

		return textureReplacedRenderType;
	}

	//Util class
	private TiedUpRenderTypes() {
		super(null, null, null, -1, false, false, null, null);
	}
}
