package net.fabricmc.fabric.mixin.blockview.client;

import com.llamalad7.mixinextras.sugar.Local;
import com.llamalad7.mixinextras.sugar.Share;
import com.llamalad7.mixinextras.sugar.ref.LocalRef;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import java.util.ConcurrentModificationException;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import net.fabricmc.api.EnvType;
import net.fabricmc.api.Environment;
import net.fabricmc.fabric.impl.blockview.client.RenderDataMapConsumer;
import net.minecraft.class_1937;
import net.minecraft.class_2338;
import net.minecraft.class_2586;
import net.minecraft.class_2818;
import net.minecraft.class_4076;
import net.minecraft.class_6850;
import net.minecraft.class_853;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.Unique;
import org.spongepowered.asm.mixin.injection.At;
import org.spongepowered.asm.mixin.injection.Inject;
import org.spongepowered.asm.mixin.injection.callback.CallbackInfoReturnable;

@Environment(EnvType.CLIENT)
@Mixin({class_6850.class})
public abstract class ChunkRendererRegionBuilderMixin {
   @Unique
   private static final AtomicInteger ERROR_COUNTER = new AtomicInteger();
   @Unique
   private static final Logger LOGGER = LoggerFactory.getLogger(ChunkRendererRegionBuilderMixin.class);

   @Inject(
      method = {"method_39969(Lnet/minecraft/class_1937;J)Lnet/minecraft/class_853;"},
      at = {@At(
   value = "INVOKE",
   target = "Lnet/minecraft/class_6850;method_72042(Lnet/minecraft/class_1937;III)Lnet/minecraft/class_6849;"
)}
   )
   private void copyDataForChunk(class_1937 world, long packedChunkPos, CallbackInfoReturnable<class_853> cir, @Share("dataMap") LocalRef<Long2ObjectOpenHashMap<Object>> mapRef, @Local(ordinal = 11) int x, @Local(ordinal = 10) int y, @Local(ordinal = 9) int z) {
      while(true) {
         try {
            mapRef.set(mapChunk(world.method_8497(x, z), class_4076.method_18677(packedChunkPos), (Long2ObjectOpenHashMap)mapRef.get()));
            return;
         } catch (ConcurrentModificationException e) {
            int count = ERROR_COUNTER.incrementAndGet();
            if (count <= 5) {
               LOGGER.warn("[Block Entity Render Data] Encountered CME during render region build. A mod is accessing or changing chunk data outside the main thread. Retrying.", e);
               if (count == 5) {
                  LOGGER.info("[Block Entity Render Data] Subsequent exceptions will be suppressed.");
               }
            }
         }
      }
   }

   @Inject(
      method = {"method_39969(Lnet/minecraft/class_1937;J)Lnet/minecraft/class_853;"},
      at = {@At("RETURN")}
   )
   private void createDataMap(class_1937 world, long l, CallbackInfoReturnable<class_853> cir, @Share("dataMap") LocalRef<Long2ObjectOpenHashMap<Object>> mapRef) {
      class_853 rendererRegion = (class_853)cir.getReturnValue();
      Long2ObjectOpenHashMap<Object> map = (Long2ObjectOpenHashMap)mapRef.get();
      if (map != null) {
         ((RenderDataMapConsumer)rendererRegion).fabric_acceptRenderDataMap(map);
      }

   }

   @Unique
   private static Long2ObjectOpenHashMap<Object> mapChunk(class_2818 chunk, class_4076 chunkSectionPos, Long2ObjectOpenHashMap<Object> map) {
      if (chunk.method_12214().isEmpty()) {
         return map;
      } else {
         int xMin = class_4076.method_18688(chunkSectionPos.method_18674() - 1);
         int yMin = class_4076.method_18688(chunkSectionPos.method_18683() - 1);
         int zMin = class_4076.method_18688(chunkSectionPos.method_18687() - 1);
         int xMax = class_4076.method_18688(chunkSectionPos.method_18674() + 1);
         int yMax = class_4076.method_18688(chunkSectionPos.method_18683() + 1);
         int zMax = class_4076.method_18688(chunkSectionPos.method_18687() + 1);

         for(Map.Entry<class_2338, class_2586> entry : chunk.method_12214().entrySet()) {
            class_2338 pos = (class_2338)entry.getKey();
            if (pos.method_10263() >= xMin && pos.method_10263() <= xMax && pos.method_10264() >= yMin && pos.method_10264() <= yMax && pos.method_10260() >= zMin && pos.method_10260() <= zMax) {
               Object data = ((class_2586)entry.getValue()).getRenderData();
               if (data != null) {
                  if (map == null) {
                     map = new Long2ObjectOpenHashMap();
                  }

                  map.put(pos.method_10063(), data);
               }
            }
         }

         return map;
      }
   }
}
