package net.minecraft.world.level.chunk;

import java.util.function.Function;
import net.minecraft.world.level.levelgen.DebugLevelSource;
import net.minecraft.world.level.levelgen.FlatLevelSource;
import net.minecraft.world.level.levelgen.NoiseBasedChunkGenerator;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.CrashReportCategory;
import net.minecraft.network.protocol.game.DebugPackets;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.core.SectionPos;
import net.minecraft.world.level.levelgen.feature.ConfiguredStructureFeature;
import net.minecraft.world.level.biome.BiomeDefaultFeatures;
import net.minecraft.world.level.levelgen.structure.templatesystem.StructureManager;
import net.minecraft.world.entity.MobCategory;
import net.minecraft.ReportedException;
import net.minecraft.core.Registry;
import net.minecraft.CrashReport;
import net.minecraft.world.level.WorldGenLevel;
import net.minecraft.world.level.StructureFeatureManager;
import net.minecraft.server.level.WorldGenRegion;
import javax.annotation.Nullable;
import net.minecraft.world.level.LevelReader;
import net.minecraft.core.Vec3i;
import net.minecraft.server.level.ServerLevel;
import java.util.ListIterator;
import java.util.BitSet;
import net.minecraft.world.level.levelgen.carver.ConfiguredWorldCarver;
import net.minecraft.world.level.levelgen.WorldgenRandom;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.biome.BiomeManager;
import net.minecraft.core.BlockPos;
import java.util.Iterator;
import net.minecraft.world.level.levelgen.feature.configurations.StrongholdConfiguration;
import java.util.Random;
import net.minecraft.world.level.levelgen.feature.StructureFeature;
import net.minecraft.world.level.biome.Biome;
import com.google.common.collect.Lists;
import net.minecraft.world.level.ChunkPos;
import java.util.List;
import net.minecraft.world.level.levelgen.StructureSettings;
import net.minecraft.world.level.biome.BiomeSource;
import com.mojang.serialization.Codec;

public abstract class ChunkGenerator {
    public static final Codec<ChunkGenerator> CODEC;
    protected final BiomeSource biomeSource;
    protected final BiomeSource runtimeBiomeSource;
    private final StructureSettings settings;
    private final long strongholdSeed;
    private final List<ChunkPos> strongholdPositions;
    
    public ChunkGenerator(final BiomeSource brh, final StructureSettings ciw) {
        this(brh, brh, ciw, 0L);
    }
    
    public ChunkGenerator(final BiomeSource brh1, final BiomeSource brh2, final StructureSettings ciw, final long long4) {
        this.strongholdPositions = Lists.newArrayList();
        this.biomeSource = brh1;
        this.runtimeBiomeSource = brh2;
        this.settings = ciw;
        this.strongholdSeed = long4;
    }
    
    private void generateStrongholds() {
        if (!this.strongholdPositions.isEmpty()) {
            return;
        }
        final StrongholdConfiguration cos2 = this.settings.stronghold();
        if (cos2 == null || cos2.count() == 0) {
            return;
        }
        final List<Biome> list3 = Lists.newArrayList();
        for (final Biome bre5 : this.biomeSource.possibleBiomes()) {
            if (bre5.isValidStart(StructureFeature.STRONGHOLD)) {
                list3.add(bre5);
            }
        }
        final int integer4 = cos2.distance();
        final int integer5 = cos2.count();
        int integer6 = cos2.spread();
        final Random random7 = new Random();
        random7.setSeed(this.strongholdSeed);
        double double8 = random7.nextDouble() * 3.141592653589793 * 2.0;
        int integer7 = 0;
        int integer8 = 0;
        for (int integer9 = 0; integer9 < integer5; ++integer9) {
            final double double9 = 4 * integer4 + integer4 * integer8 * 6 + (random7.nextDouble() - 0.5) * (integer4 * 2.5);
            int integer10 = (int)Math.round(Math.cos(double8) * double9);
            int integer11 = (int)Math.round(Math.sin(double8) * double9);
            final BlockPos fu17 = this.biomeSource.findBiomeHorizontal((integer10 << 4) + 8, 0, (integer11 << 4) + 8, 112, list3, random7);
            if (fu17 != null) {
                integer10 = fu17.getX() >> 4;
                integer11 = fu17.getZ() >> 4;
            }
            this.strongholdPositions.add(new ChunkPos(integer10, integer11));
            double8 += 6.283185307179586 / integer6;
            if (++integer7 == integer6) {
                ++integer8;
                integer7 = 0;
                integer6 += 2 * integer6 / (integer8 + 1);
                integer6 = Math.min(integer6, integer5 - integer9);
                double8 += random7.nextDouble() * 3.141592653589793 * 2.0;
            }
        }
    }
    
    protected abstract Codec<? extends ChunkGenerator> codec();
    
    public abstract ChunkGenerator withSeed(final long long1);
    
    public void createBiomes(final ChunkAccess cgy) {
        final ChunkPos bph3 = cgy.getPos();
        ((ProtoChunk)cgy).setBiomes(new ChunkBiomeContainer(bph3, this.runtimeBiomeSource));
    }
    
    public void applyCarvers(final long long1, final BiomeManager brg, final ChunkAccess cgy, final GenerationStep.Carving a) {
        final BiomeManager brg2 = brg.withDifferentSource(this.biomeSource);
        final WorldgenRandom ciy8 = new WorldgenRandom();
        final int integer9 = 8;
        final ChunkPos bph10 = cgy.getPos();
        final int integer10 = bph10.x;
        final int integer11 = bph10.z;
        final Biome bre13 = this.biomeSource.getNoiseBiome(bph10.x << 2, 0, bph10.z << 2);
        final BitSet bitSet14 = ((ProtoChunk)cgy).getOrCreateCarvingMask(a);
        for (int integer12 = integer10 - 8; integer12 <= integer10 + 8; ++integer12) {
            for (int integer13 = integer11 - 8; integer13 <= integer11 + 8; ++integer13) {
                final List<ConfiguredWorldCarver<?>> list17 = bre13.getCarvers(a);
                final ListIterator<ConfiguredWorldCarver<?>> listIterator18 = list17.listIterator();
                while (listIterator18.hasNext()) {
                    final int integer14 = listIterator18.nextIndex();
                    final ConfiguredWorldCarver<?> cjc20 = listIterator18.next();
                    ciy8.setLargeFeatureSeed(long1 + integer14, integer12, integer13);
                    if (cjc20.isStartChunk(ciy8, integer12, integer13)) {
                        cjc20.carve(cgy, brg2::getBiome, ciy8, this.getSeaLevel(), integer12, integer13, integer10, integer11, bitSet14);
                    }
                }
            }
        }
    }
    
    @Nullable
    public BlockPos findNearestMapFeature(final ServerLevel zd, final StructureFeature<?> cml, final BlockPos fu, final int integer, final boolean boolean5) {
        if (!this.biomeSource.canGenerateStructure(cml)) {
            return null;
        }
        if (cml == StructureFeature.STRONGHOLD) {
            this.generateStrongholds();
            BlockPos fu2 = null;
            double double8 = Double.MAX_VALUE;
            final BlockPos.MutableBlockPos a10 = new BlockPos.MutableBlockPos();
            for (final ChunkPos bph12 : this.strongholdPositions) {
                a10.set((bph12.x << 4) + 8, 32, (bph12.z << 4) + 8);
                final double double9 = a10.distSqr(fu);
                if (fu2 == null) {
                    fu2 = new BlockPos(a10);
                    double8 = double9;
                }
                else {
                    if (double9 >= double8) {
                        continue;
                    }
                    fu2 = new BlockPos(a10);
                    double8 = double9;
                }
            }
            return fu2;
        }
        return cml.getNearestGeneratedFeature(zd, zd.structureFeatureManager(), fu, integer, boolean5, zd.getSeed(), this.settings.getConfig(cml));
    }
    
    public void applyBiomeDecoration(final WorldGenRegion zj, final StructureFeatureManager bqq) {
        final int integer4 = zj.getCenterX();
        final int integer5 = zj.getCenterZ();
        final int integer6 = integer4 * 16;
        final int integer7 = integer5 * 16;
        final BlockPos fu8 = new BlockPos(integer6, 0, integer7);
        final Biome bre9 = this.biomeSource.getNoiseBiome((integer4 << 2) + 2, 2, (integer5 << 2) + 2);
        final WorldgenRandom ciy10 = new WorldgenRandom();
        final long long11 = ciy10.setDecorationSeed(zj.getSeed(), integer6, integer7);
        for (final GenerationStep.Decoration b16 : GenerationStep.Decoration.values()) {
            try {
                bre9.generate(b16, bqq, this, zj, long11, ciy10, fu8);
            }
            catch (Exception exception17) {
                final CrashReport j18 = CrashReport.forThrowable(exception17, "Biome decoration");
                j18.addCategory("Generation").setDetail("CenterX", integer4).setDetail("CenterZ", integer5).setDetail("Step", b16).setDetail("Seed", long11).setDetail("Biome", Registry.BIOME.getKey(bre9));
                throw new ReportedException(j18);
            }
        }
    }
    
    public abstract void buildSurfaceAndBedrock(final WorldGenRegion zj, final ChunkAccess cgy);
    
    public void spawnOriginalMobs(final WorldGenRegion zj) {
    }
    
    public StructureSettings getSettings() {
        return this.settings;
    }
    
    public int getSpawnHeight() {
        return 64;
    }
    
    public BiomeSource getBiomeSource() {
        return this.runtimeBiomeSource;
    }
    
    public int getGenDepth() {
        return 256;
    }
    
    public List<Biome.SpawnerData> getMobsAt(final Biome bre, final StructureFeatureManager bqq, final MobCategory apa, final BlockPos fu) {
        return bre.getMobs(apa);
    }
    
    public void createStructures(final StructureFeatureManager bqq, final ChunkAccess cgy, final StructureManager cva, final long long4) {
        final ChunkPos bph7 = cgy.getPos();
        final Biome bre8 = this.biomeSource.getNoiseBiome((bph7.x << 2) + 2, 0, (bph7.z << 2) + 2);
        this.createStructure(BiomeDefaultFeatures.STRONGHOLD, bqq, cgy, cva, long4, bph7, bre8);
        for (final ConfiguredStructureFeature<?, ?> ckc10 : bre8.structures()) {
            this.createStructure(ckc10, bqq, cgy, cva, long4, bph7, bre8);
        }
    }
    
    private void createStructure(final ConfiguredStructureFeature<?, ?> ckc, final StructureFeatureManager bqq, final ChunkAccess cgy, final StructureManager cva, final long long5, final ChunkPos bph, final Biome bre) {
        final StructureStart<?> ctz10 = bqq.getStartForFeature(SectionPos.of(cgy.getPos(), 0), ckc.feature, cgy);
        final int integer11 = (ctz10 != null) ? ctz10.getReferences() : 0;
        final StructureStart<?> ctz11 = ckc.generate(this, this.biomeSource, cva, long5, bph, bre, integer11, this.settings.getConfig(ckc.feature));
        bqq.setStartForFeature(SectionPos.of(cgy.getPos(), 0), ckc.feature, ctz11, cgy);
    }
    
    public void createReferences(final LevelAccessor bqc, final StructureFeatureManager bqq, final ChunkAccess cgy) {
        final int integer5 = 8;
        final int integer6 = cgy.getPos().x;
        final int integer7 = cgy.getPos().z;
        final int integer8 = integer6 << 4;
        final int integer9 = integer7 << 4;
        final SectionPos go10 = SectionPos.of(cgy.getPos(), 0);
        for (int integer10 = integer6 - 8; integer10 <= integer6 + 8; ++integer10) {
            for (int integer11 = integer7 - 8; integer11 <= integer7 + 8; ++integer11) {
                final long long13 = ChunkPos.asLong(integer10, integer11);
                for (final StructureStart<?> ctz16 : bqc.getChunk(integer10, integer11).getAllStarts().values()) {
                    try {
                        if (ctz16 == StructureStart.INVALID_START || !ctz16.getBoundingBox().intersects(integer8, integer9, integer8 + 15, integer9 + 15)) {
                            continue;
                        }
                        bqq.addReferenceForFeature(go10, ctz16.getFeature(), long13, cgy);
                        DebugPackets.sendStructurePacket(bqc, ctz16);
                    }
                    catch (Exception exception17) {
                        final CrashReport j18 = CrashReport.forThrowable(exception17, "Generating structure reference");
                        final CrashReportCategory k19 = j18.addCategory("Structure");
                        k19.setDetail("Id", () -> Registry.STRUCTURE_FEATURE.getKey(ctz16.getFeature()).toString());
                        k19.setDetail("Name", () -> ctz16.getFeature().getFeatureName());
                        k19.setDetail("Class", () -> ctz16.getFeature().getClass().getCanonicalName());
                        throw new ReportedException(j18);
                    }
                }
            }
        }
    }
    
    public abstract void fillFromNoise(final LevelAccessor bqc, final StructureFeatureManager bqq, final ChunkAccess cgy);
    
    public int getSeaLevel() {
        return 63;
    }
    
    public abstract int getBaseHeight(final int integer1, final int integer2, final Heightmap.Types a);
    
    public abstract BlockGetter getBaseColumn(final int integer1, final int integer2);
    
    public int getFirstFreeHeight(final int integer1, final int integer2, final Heightmap.Types a) {
        return this.getBaseHeight(integer1, integer2, a);
    }
    
    public int getFirstOccupiedHeight(final int integer1, final int integer2, final Heightmap.Types a) {
        return this.getBaseHeight(integer1, integer2, a) - 1;
    }
    
    public boolean hasStronghold(final ChunkPos bph) {
        this.generateStrongholds();
        return this.strongholdPositions.contains(bph);
    }
    
    static {
        Registry.<Codec<NoiseBasedChunkGenerator>>register(Registry.CHUNK_GENERATOR, "noise", NoiseBasedChunkGenerator.CODEC);
        Registry.<Codec<FlatLevelSource>>register(Registry.CHUNK_GENERATOR, "flat", FlatLevelSource.CODEC);
        Registry.<Codec<DebugLevelSource>>register(Registry.CHUNK_GENERATOR, "debug", DebugLevelSource.CODEC);
        CODEC = Registry.CHUNK_GENERATOR.dispatchStable((Function)ChunkGenerator::codec, (Function)Function.identity());
    }
}
