/*
 * 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.mesh;

import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Executor;
import java.util.function.Function;
import java.util.stream.Collectors;

import javax.annotation.Nullable;

import com.google.common.collect.Maps;

import net.minecraft.resources.ResourceLocation;
import net.minecraft.server.packs.resources.PreparableReloadListener;
import net.minecraft.server.packs.resources.ResourceManager;
import net.minecraft.util.profiling.ProfilerFiller;
import com.tiedup.remake.rig.asset.AssetAccessor;
import com.tiedup.remake.rig.asset.JsonAssetLoader;
import com.tiedup.remake.rig.mesh.Mesh.RenderProperties;
import com.tiedup.remake.rig.cloth.ClothSimulatable;
import com.tiedup.remake.rig.cloth.ClothSimulator.ClothObject;
import com.tiedup.remake.rig.cloth.ClothSimulator.ClothObjectBuilder;
import com.tiedup.remake.rig.TiedUpRigConstants;

public class Meshes implements PreparableReloadListener {
	private static final Map<ResourceLocation, MeshAccessor<? extends Mesh>> ACCESSORS = Maps.newHashMap();
	private static final Map<MeshAccessor<? extends Mesh>, Mesh> MESHES = Maps.newHashMap();
	private static ResourceManager resourceManager = null;

	//For resource reloader
	public static final Meshes INSTANCE = new Meshes();

	// RIG : on garde uniquement les meshes humanoïdes (joueur + NPCs bondage).
	// Les mob meshes combat EF (Creeper/Dragon/Spider/etc.) et armor/particle/cape
	// ont été retirés du fork — hors scope bondage.
	public static final MeshAccessor<HumanoidMesh> ALEX = MeshAccessor.create(TiedUpRigConstants.MODID, "entity/biped_slim_arm", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(HumanoidMesh::new));
	public static final MeshAccessor<HumanoidMesh> BIPED = MeshAccessor.create(TiedUpRigConstants.MODID, "entity/biped", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(HumanoidMesh::new));
	public static final MeshAccessor<HumanoidMesh> BIPED_OLD_TEX = MeshAccessor.create(TiedUpRigConstants.MODID, "entity/biped_old_texture", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(HumanoidMesh::new));
	public static final MeshAccessor<HumanoidMesh> BIPED_OUTLAYER = MeshAccessor.create(TiedUpRigConstants.MODID, "entity/biped_outlayer", (jsonModelLoader) -> jsonModelLoader.loadSkinnedMesh(HumanoidMesh::new));

	public static void reload(ResourceManager resourceManager) {
		Meshes.resourceManager = resourceManager;

		ACCESSORS.entrySet().removeIf(entry -> !entry.getValue().inRegistry);

		MESHES.values().forEach((mesh) -> {
			if (mesh instanceof SkinnedMesh skinnedMesh) {
				skinnedMesh.destroy();
			}
		});

		MESHES.clear();
	}

	@SuppressWarnings("unchecked")
	@Nullable
	public static <M extends Mesh> AssetAccessor<M> get(ResourceLocation id) {
		return (AssetAccessor<M>) ACCESSORS.get(id);
	}

	@SuppressWarnings("unchecked")
	public static <M extends Mesh> AssetAccessor<M> getOrCreate(ResourceLocation id, Function<JsonAssetLoader, M> jsonLoader) {
		return ACCESSORS.containsKey(id) ? (AssetAccessor<M>)ACCESSORS.get(id) : MeshAccessor.create(id, jsonLoader, false);
	}

	@SuppressWarnings("unchecked")
	public static <M extends Mesh> Set<AssetAccessor<M>> entry(Class<? extends Mesh> filter) {
		return ACCESSORS.values().stream().filter((accessor) -> filter.isAssignableFrom(accessor.get().getClass())).map((accessor) -> (AssetAccessor<M>)accessor).collect(Collectors.toSet());
	}

	public static ResourceLocation wrapLocation(ResourceLocation rl) {
		return rl.getPath().matches("animmodels/.*\\.json") ? rl : ResourceLocation.fromNamespaceAndPath(rl.getNamespace(), "animmodels/" + rl.getPath() + ".json");
	}

	@Override
	public CompletableFuture<Void> reload(PreparableReloadListener.PreparationBarrier stage, ResourceManager resourceManager, ProfilerFiller preparationsProfiler, ProfilerFiller reloadProfiler, Executor backgroundExecutor, Executor gameExecutor) {
		return CompletableFuture.runAsync(() -> {
			Meshes.reload(resourceManager);
		}, gameExecutor).thenCompose(stage::wait);
	}

	@FunctionalInterface
	public interface MeshContructor<P extends MeshPart, V extends VertexBuilder, M extends StaticMesh<P>> {
		M invoke(Map<String, Number[]> arrayMap, Map<MeshPartDefinition, List<V>> parts, M parent, RenderProperties properties);
	}

	public static record MeshAccessor<M extends Mesh> (ResourceLocation registryName, Function<JsonAssetLoader, M> jsonLoader, boolean inRegistry) implements AssetAccessor<M>, SoftBodyTranslatable {
		public static <M extends Mesh> MeshAccessor<M> create(String namespaceId, String path, Function<JsonAssetLoader, M> jsonLoader) {
			return create(ResourceLocation.fromNamespaceAndPath(namespaceId, path), jsonLoader, true);
		}

		private static <M extends Mesh> MeshAccessor<M> create(ResourceLocation id, Function<JsonAssetLoader, M> jsonLoader, boolean inRegistry) {
			MeshAccessor<M> accessor = new MeshAccessor<M> (id, jsonLoader, inRegistry);
			ACCESSORS.put(id, accessor);
			return accessor;
		}

		@SuppressWarnings("unchecked")
		@Override
		public M get() {
			if (!MESHES.containsKey(this)) {
				JsonAssetLoader jsonModelLoader = new JsonAssetLoader(resourceManager, wrapLocation(this.registryName));
				MESHES.put(this, this.jsonLoader.apply(jsonModelLoader));
			}

			return (M)MESHES.get(this);
		}

		public String toString() {
			return this.registryName.toString();
		}

		public int hashCode() {
			return this.registryName.hashCode();
		}

		public boolean equals(Object obj) {
			if (this == obj) {
				return true;
			} else if (obj instanceof MeshAccessor armatureAccessor) {
				return this.registryName.equals(armatureAccessor.registryName());
			} else if (obj instanceof ResourceLocation rl) {
				return this.registryName.equals(rl);
			} else if (obj instanceof String name) {
				return this.registryName.toString().equals(name);
			} else {
				return false;
			}
		}

		@Override
		public boolean canStartSoftBodySimulation() {
			Mesh mesh = this.get();

			if (mesh instanceof StaticMesh<?> staticMesh) {
				return staticMesh.canStartSoftBodySimulation();
			} else if (mesh instanceof CompositeMesh compositeMesh) {
				return compositeMesh.canStartSoftBodySimulation();
			}

			return false;
		}

		@Override
		public ClothObject createSimulationData(SoftBodyTranslatable provider, ClothSimulatable simOwner, ClothObjectBuilder simBuilder) {
			Mesh mesh = this.get();

			if (mesh instanceof StaticMesh<?> staticMesh) {
				return staticMesh.createSimulationData(provider, simOwner, simBuilder);
			} else if (mesh instanceof CompositeMesh compositeMesh) {
				return compositeMesh.createSimulationData(provider, simOwner, simBuilder);
			}

			return null;
		}

		@Override
		public void putSoftBodySimulationInfo(Map<String, ClothSimulationInfo> sofyBodySimulationInfo) {
			Mesh mesh = this.get();

			if (mesh instanceof SoftBodyTranslatable softBodyTranslatable) {
				softBodyTranslatable.putSoftBodySimulationInfo(sofyBodySimulationInfo);
			}
		}

		@Override
		public Map<String, ClothSimulationInfo> getSoftBodySimulationInfo() {
			Mesh mesh = this.get();

			if (mesh instanceof SoftBodyTranslatable softBodyTranslatable) {
				return softBodyTranslatable.getSoftBodySimulationInfo();
			} else {
				return null;
			}
		}
	}
}
