package net.minecraft.world.level.chunk;

import com.google.common.base.Stopwatch;
import com.mojang.datafixers.util.Pair;
import com.mojang.logging.LogUtils;
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import javax.annotation.Nullable;
import net.minecraft.Util;
import net.minecraft.core.BlockPos;
import net.minecraft.core.Holder;
import net.minecraft.core.HolderLookup;
import net.minecraft.core.HolderSet;
import net.minecraft.core.SectionPos;
import net.minecraft.util.RandomSource;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.biome.BiomeSource;
import net.minecraft.world.level.levelgen.RandomState;
import net.minecraft.world.level.levelgen.structure.Structure;
import net.minecraft.world.level.levelgen.structure.StructureSet;
import net.minecraft.world.level.levelgen.structure.placement.ConcentricRingsStructurePlacement;
import net.minecraft.world.level.levelgen.structure.placement.StructurePlacement;
import org.slf4j.Logger;

public class ChunkGeneratorStructureState {
   private static final Logger LOGGER = LogUtils.getLogger();
   private final RandomState randomState;
   private final BiomeSource biomeSource;
   private final long levelSeed;
   private final long concentricRingsSeed;
   private final Map<Structure, List<StructurePlacement>> placementsForStructure = new Object2ObjectOpenHashMap<>();
   private final Map<ConcentricRingsStructurePlacement, CompletableFuture<List<ChunkPos>>> ringPositions = new Object2ObjectArrayMap<>();
   private boolean hasGeneratedPositions;
   private final List<Holder<StructureSet>> possibleStructureSets;

   public static ChunkGeneratorStructureState createForFlat(RandomState p_256240_, long p_256404_, BiomeSource p_256274_, Stream<Holder<StructureSet>> p_256348_) {
      List<Holder<StructureSet>> list = p_256348_.filter((p_255616_) -> {
         return hasBiomesForStructureSet(p_255616_.value(), p_256274_);
      }).toList();
      return new ChunkGeneratorStructureState(p_256240_, p_256274_, p_256404_, 0L, list);
   }

   public static ChunkGeneratorStructureState createForNormal(RandomState p_256197_, long p_255806_, BiomeSource p_256653_, HolderLookup<StructureSet> p_256659_) {
      List<Holder<StructureSet>> list = p_256659_.listElements().filter((p_256144_) -> {
         return hasBiomesForStructureSet(p_256144_.value(), p_256653_);
      }).collect(Collectors.toUnmodifiableList());
      return new ChunkGeneratorStructureState(p_256197_, p_256653_, p_255806_, p_255806_, list);
   }

   private static boolean hasBiomesForStructureSet(StructureSet p_255766_, BiomeSource p_256424_) {
      Stream<Holder<Biome>> stream = p_255766_.structures().stream().flatMap((p_255738_) -> {
         Structure structure = p_255738_.structure().value();
         return structure.biomes().stream();
      });
      return stream.anyMatch(p_256424_.possibleBiomes()::contains);
   }

   private ChunkGeneratorStructureState(RandomState p_256401_, BiomeSource p_255742_, long p_256615_, long p_255979_, List<Holder<StructureSet>> p_256237_) {
      this.randomState = p_256401_;
      this.levelSeed = p_256615_;
      this.biomeSource = p_255742_;
      this.concentricRingsSeed = p_255979_;
      this.possibleStructureSets = p_256237_;
   }

   public List<Holder<StructureSet>> possibleStructureSets() {
      return this.possibleStructureSets;
   }

   private void generatePositions() {
      Set<Holder<Biome>> set = this.biomeSource.possibleBiomes();
      this.possibleStructureSets().forEach((p_255638_) -> {
         StructureSet structureset = p_255638_.value();
         boolean flag = false;

         for(StructureSet.StructureSelectionEntry structureset$structureselectionentry : structureset.structures()) {
            Structure structure = structureset$structureselectionentry.structure().value();
            if (structure.biomes().stream().anyMatch(set::contains)) {
               this.placementsForStructure.computeIfAbsent(structure, (p_256235_) -> {
                  return new ArrayList();
               }).add(structureset.placement());
               flag = true;
            }
         }

         if (flag) {
            StructurePlacement structureplacement = structureset.placement();
            if (structureplacement instanceof ConcentricRingsStructurePlacement) {
               ConcentricRingsStructurePlacement concentricringsstructureplacement = (ConcentricRingsStructurePlacement)structureplacement;
               this.ringPositions.put(concentricringsstructureplacement, this.generateRingPositions(p_255638_, concentricringsstructureplacement));
            }
         }

      });
   }

   private CompletableFuture<List<ChunkPos>> generateRingPositions(Holder<StructureSet> p_255966_, ConcentricRingsStructurePlacement p_255744_) {
      if (p_255744_.count() == 0) {
         return CompletableFuture.completedFuture(List.of());
      } else {
         Stopwatch stopwatch = Stopwatch.createStarted(Util.TICKER);
         int i = p_255744_.distance();
         int j = p_255744_.count();
         List<CompletableFuture<ChunkPos>> list = new ArrayList<>(j);
         int k = p_255744_.spread();
         HolderSet<Biome> holderset = p_255744_.preferredBiomes();
         RandomSource randomsource = RandomSource.create();
         randomsource.setSeed(this.concentricRingsSeed);
         double d0 = randomsource.nextDouble() * Math.PI * 2.0D;
         int l = 0;
         int i1 = 0;

         for(int j1 = 0; j1 < j; ++j1) {
            double d1 = (double)(4 * i + i * i1 * 6) + (randomsource.nextDouble() - 0.5D) * (double)i * 2.5D;
            int k1 = (int)Math.round(Math.cos(d0) * d1);
            int l1 = (int)Math.round(Math.sin(d0) * d1);
            RandomSource randomsource1 = randomsource.fork();
            list.add(CompletableFuture.supplyAsync(() -> {
               Pair<BlockPos, Holder<Biome>> pair = this.biomeSource.findBiomeHorizontal(SectionPos.sectionToBlockCoord(k1, 8), 0, SectionPos.sectionToBlockCoord(l1, 8), 112, holderset::contains, randomsource1, this.randomState.sampler());
               if (pair != null) {
                  BlockPos blockpos = pair.getFirst();
                  return new ChunkPos(SectionPos.blockToSectionCoord(blockpos.getX()), SectionPos.blockToSectionCoord(blockpos.getZ()));
               } else {
                  return new ChunkPos(k1, l1);
               }
            }, Util.backgroundExecutor()));
            d0 += (Math.PI * 2D) / (double)k;
            ++l;
            if (l == k) {
               ++i1;
               l = 0;
               k += 2 * k / (i1 + 1);
               k = Math.min(k, j - j1);
               d0 += randomsource.nextDouble() * Math.PI * 2.0D;
            }
         }

         return Util.sequence(list).thenApply((p_256372_) -> {
            double d2 = (double)stopwatch.stop().elapsed(TimeUnit.MILLISECONDS) / 1000.0D;
            LOGGER.debug("Calculation for {} took {}s", p_255966_, d2);
            return p_256372_;
         });
      }
   }

   public void ensureStructuresGenerated() {
      if (!this.hasGeneratedPositions) {
         this.generatePositions();
         this.hasGeneratedPositions = true;
      }

   }

   @Nullable
   public List<ChunkPos> getRingPositionsFor(ConcentricRingsStructurePlacement p_256667_) {
      this.ensureStructuresGenerated();
      CompletableFuture<List<ChunkPos>> completablefuture = this.ringPositions.get(p_256667_);
      return completablefuture != null ? completablefuture.join() : null;
   }

   public List<StructurePlacement> getPlacementsForStructure(Holder<Structure> p_256494_) {
      this.ensureStructuresGenerated();
      return this.placementsForStructure.getOrDefault(p_256494_.value(), List.of());
   }

   public RandomState randomState() {
      return this.randomState;
   }

   public boolean hasStructureChunkInRange(Holder<StructureSet> p_256489_, int p_256593_, int p_256115_, int p_256619_) {
      StructurePlacement structureplacement = p_256489_.value().placement();

      for(int i = p_256593_ - p_256619_; i <= p_256593_ + p_256619_; ++i) {
         for(int j = p_256115_ - p_256619_; j <= p_256115_ + p_256619_; ++j) {
            if (structureplacement.isStructureChunk(this, i, j)) {
               return true;
            }
         }
      }

      return false;
   }

   public long getLevelSeed() {
      return this.levelSeed;
   }
}