package net.minecraft.world.level.biome;

import net.minecraft.world.level.levelgen.surfacebuilders.SurfaceBuilder;
import net.minecraft.world.entity.EntityType;
import net.minecraft.util.WeighedRandom;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.Arrays;
import com.google.common.collect.Sets;
import org.apache.logging.log4j.LogManager;
import net.minecraft.world.level.levelgen.surfacebuilders.SurfaceBuilderConfiguration;
import net.minecraft.network.chat.TranslatableComponent;
import net.minecraft.network.chat.Component;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.world.level.FoliageColor;
import net.minecraft.world.level.GrassColor;
import java.util.Iterator;
import net.minecraft.ReportedException;
import net.minecraft.CrashReport;
import java.util.Random;
import net.minecraft.world.level.levelgen.WorldgenRandom;
import net.minecraft.world.level.LevelAccessor;
import net.minecraft.world.level.levelgen.ChunkGeneratorSettings;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.world.level.levelgen.feature.Feature;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.block.LiquidBlock;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.level.LightLayer;
import net.minecraft.world.level.LevelReader;
import net.minecraft.core.BlockPos;
import net.minecraft.util.Mth;
import net.minecraft.Util;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import net.minecraft.world.level.levelgen.carver.CarverConfiguration;
import net.minecraft.world.level.levelgen.carver.WorldCarver;
import net.minecraft.core.Registry;
import it.unimi.dsi.fastutil.longs.Long2FloatLinkedOpenHashMap;
import net.minecraft.world.entity.MobCategory;
import net.minecraft.world.level.levelgen.feature.configurations.FeatureConfiguration;
import net.minecraft.world.level.levelgen.feature.StructureFeature;
import net.minecraft.world.level.levelgen.feature.ConfiguredFeature;
import net.minecraft.world.level.levelgen.carver.ConfiguredWorldCarver;
import java.util.List;
import net.minecraft.world.level.levelgen.GenerationStep;
import java.util.Map;
import net.minecraft.world.level.levelgen.surfacebuilders.ConfiguredSurfaceBuilder;
import javax.annotation.Nullable;
import net.minecraft.world.level.levelgen.synth.PerlinSimplexNoise;
import net.minecraft.core.IdMapper;
import java.util.Set;
import org.apache.logging.log4j.Logger;

public abstract class Biome {
    public static final Logger LOGGER;
    public static final Set<Biome> EXPLORABLE_BIOMES;
    public static final IdMapper<Biome> MUTATED_BIOMES;
    protected static final PerlinSimplexNoise TEMPERATURE_NOISE;
    public static final PerlinSimplexNoise BIOME_INFO_NOISE;
    @Nullable
    protected String descriptionId;
    protected final float depth;
    protected final float scale;
    protected final float temperature;
    protected final float downfall;
    protected final int waterColor;
    protected final int waterFogColor;
    private final int skyColor;
    @Nullable
    protected final String parent;
    protected final ConfiguredSurfaceBuilder<?> surfaceBuilder;
    protected final BiomeCategory biomeCategory;
    protected final Precipitation precipitation;
    protected final Map<GenerationStep.Carving, List<ConfiguredWorldCarver<?>>> carvers;
    protected final Map<GenerationStep.Decoration, List<ConfiguredFeature<?, ?>>> features;
    protected final List<ConfiguredFeature<?, ?>> flowerFeatures;
    protected final Map<StructureFeature<?>, FeatureConfiguration> validFeatureStarts;
    private final Map<MobCategory, List<SpawnerData>> spawners;
    private final ThreadLocal<Long2FloatLinkedOpenHashMap> temperatureCache;
    
    @Nullable
    public static Biome getMutatedVariant(final Biome bkt) {
        return Biome.MUTATED_BIOMES.byId(Registry.BIOME.getId(bkt));
    }
    
    public static <C extends CarverConfiguration> ConfiguredWorldCarver<C> makeCarver(final WorldCarver<C> cco, final C cch) {
        return new ConfiguredWorldCarver<C>(cco, cch);
    }
    
    protected Biome(final BiomeBuilder a) {
        this.carvers = Maps.newHashMap();
        this.features = Maps.newHashMap();
        this.flowerFeatures = Lists.newArrayList();
        this.validFeatureStarts = Maps.newHashMap();
        this.spawners = Maps.newHashMap();
        final Long2FloatLinkedOpenHashMap long2FloatLinkedOpenHashMap2;
        this.temperatureCache = ThreadLocal.<Long2FloatLinkedOpenHashMap>withInitial(() -> Util.<Long2FloatLinkedOpenHashMap>make(() -> {
            long2FloatLinkedOpenHashMap2 = new Long2FloatLinkedOpenHashMap(1024, 0.25f) {
                protected void rehash(final int integer) {
                }
            };
            long2FloatLinkedOpenHashMap2.defaultReturnValue(Float.NaN);
            return long2FloatLinkedOpenHashMap2;
        }));
        if (a.surfaceBuilder == null || a.precipitation == null || a.biomeCategory == null || a.depth == null || a.scale == null || a.temperature == null || a.downfall == null || a.waterColor == null || a.waterFogColor == null) {
            throw new IllegalStateException("You are missing parameters to build a proper biome for " + this.getClass().getSimpleName() + "\n" + a);
        }
        this.surfaceBuilder = a.surfaceBuilder;
        this.precipitation = a.precipitation;
        this.biomeCategory = a.biomeCategory;
        this.depth = a.depth;
        this.scale = a.scale;
        this.temperature = a.temperature;
        this.downfall = a.downfall;
        this.waterColor = a.waterColor;
        this.waterFogColor = a.waterFogColor;
        this.skyColor = this.calculateSkyColor();
        this.parent = a.parent;
        for (final GenerationStep.Decoration b6 : GenerationStep.Decoration.values()) {
            this.features.put(b6, Lists.newArrayList());
        }
        for (final MobCategory alb6 : MobCategory.values()) {
            this.spawners.put(alb6, Lists.newArrayList());
        }
    }
    
    public boolean isMutated() {
        return this.parent != null;
    }
    
    private int calculateSkyColor() {
        float float2 = this.temperature;
        float2 /= 3.0f;
        float2 = Mth.clamp(float2, -1.0f, 1.0f);
        return Mth.hsvToRgb(0.62222224f - float2 * 0.05f, 0.5f + float2 * 0.1f, 1.0f);
    }
    
    public int getSkyColor() {
        return this.skyColor;
    }
    
    protected void addSpawn(final MobCategory alb, final SpawnerData e) {
        this.spawners.get(alb).add(e);
    }
    
    public List<SpawnerData> getMobs(final MobCategory alb) {
        return this.spawners.get(alb);
    }
    
    public Precipitation getPrecipitation() {
        return this.precipitation;
    }
    
    public boolean isHumid() {
        return this.getDownfall() > 0.85f;
    }
    
    public float getCreatureProbability() {
        return 0.1f;
    }
    
    protected float getTemperatureNoCache(final BlockPos fk) {
        if (fk.getY() > 64) {
            final float float3 = (float)(Biome.TEMPERATURE_NOISE.getValue(fk.getX() / 8.0f, fk.getZ() / 8.0f, false) * 4.0);
            return this.getTemperature() - (float3 + fk.getY() - 64.0f) * 0.05f / 30.0f;
        }
        return this.getTemperature();
    }
    
    public final float getTemperature(final BlockPos fk) {
        final long long3 = fk.asLong();
        final Long2FloatLinkedOpenHashMap long2FloatLinkedOpenHashMap5 = this.temperatureCache.get();
        final float float6 = long2FloatLinkedOpenHashMap5.get(long3);
        if (!Float.isNaN(float6)) {
            return float6;
        }
        final float float7 = this.getTemperatureNoCache(fk);
        if (long2FloatLinkedOpenHashMap5.size() == 1024) {
            long2FloatLinkedOpenHashMap5.removeFirstFloat();
        }
        long2FloatLinkedOpenHashMap5.put(long3, float7);
        return float7;
    }
    
    public boolean shouldFreeze(final LevelReader bjz, final BlockPos fk) {
        return this.shouldFreeze(bjz, fk, true);
    }
    
    public boolean shouldFreeze(final LevelReader bjz, final BlockPos fk, final boolean boolean3) {
        if (this.getTemperature(fk) >= 0.15f) {
            return false;
        }
        if (fk.getY() >= 0 && fk.getY() < 256 && bjz.getBrightness(LightLayer.BLOCK, fk) < 10) {
            final BlockState byj5 = bjz.getBlockState(fk);
            final FluidState coj6 = bjz.getFluidState(fk);
            if (coj6.getType() == Fluids.WATER && byj5.getBlock() instanceof LiquidBlock) {
                if (!boolean3) {
                    return true;
                }
                final boolean boolean4 = bjz.isWaterAt(fk.west()) && bjz.isWaterAt(fk.east()) && bjz.isWaterAt(fk.north()) && bjz.isWaterAt(fk.south());
                if (!boolean4) {
                    return true;
                }
            }
        }
        return false;
    }
    
    public boolean shouldSnow(final LevelReader bjz, final BlockPos fk) {
        if (this.getTemperature(fk) >= 0.15f) {
            return false;
        }
        if (fk.getY() >= 0 && fk.getY() < 256 && bjz.getBrightness(LightLayer.BLOCK, fk) < 10) {
            final BlockState byj4 = bjz.getBlockState(fk);
            if (byj4.isAir() && Blocks.SNOW.defaultBlockState().canSurvive(bjz, fk)) {
                return true;
            }
        }
        return false;
    }
    
    public void addFeature(final GenerationStep.Decoration b, final ConfiguredFeature<?, ?> cdc) {
        if (cdc.feature == Feature.DECORATED_FLOWER) {
            this.flowerFeatures.add(cdc);
        }
        this.features.get(b).add(cdc);
    }
    
    public <C extends CarverConfiguration> void addCarver(final GenerationStep.Carving a, final ConfiguredWorldCarver<C> ccj) {
        this.carvers.computeIfAbsent(a, a -> Lists.newArrayList()).add(ccj);
    }
    
    public List<ConfiguredWorldCarver<?>> getCarvers(final GenerationStep.Carving a) {
        return this.carvers.computeIfAbsent(a, a -> Lists.newArrayList());
    }
    
    public <C extends FeatureConfiguration> void addStructureStart(final ConfiguredFeature<C, ? extends StructureFeature<C>> cdc) {
        this.validFeatureStarts.put(cdc.feature, cdc.config);
    }
    
    public <C extends FeatureConfiguration> boolean isValidStart(final StructureFeature<C> cfl) {
        return this.validFeatureStarts.containsKey(cfl);
    }
    
    @Nullable
    public <C extends FeatureConfiguration> C getStructureConfiguration(final StructureFeature<C> cfl) {
        return (C)this.validFeatureStarts.get(cfl);
    }
    
    public List<ConfiguredFeature<?, ?>> getFlowerFeatures() {
        return this.flowerFeatures;
    }
    
    public List<ConfiguredFeature<?, ?>> getFeaturesForStep(final GenerationStep.Decoration b) {
        return this.features.get(b);
    }
    
    public void generate(final GenerationStep.Decoration b, final ChunkGenerator<? extends ChunkGeneratorSettings> caa, final LevelAccessor bjx, final long long4, final WorldgenRandom ccf, final BlockPos fk) {
        int integer9 = 0;
        for (final ConfiguredFeature<?, ?> cdc11 : this.features.get(b)) {
            ccf.setFeatureSeed(long4, integer9, b.ordinal());
            try {
                cdc11.place(bjx, caa, ccf, fk);
            }
            catch (Exception exception12) {
                final CrashReport h13 = CrashReport.forThrowable(exception12, "Feature placement");
                h13.addCategory("Feature").setDetail("Id", Registry.FEATURE.getKey(cdc11.feature)).setDetail("Description", () -> cdc11.feature.toString());
                throw new ReportedException(h13);
            }
            ++integer9;
        }
    }
    
    public int getGrassColor(final double double1, final double double2) {
        final double double3 = Mth.clamp(this.getTemperature(), 0.0f, 1.0f);
        final double double4 = Mth.clamp(this.getDownfall(), 0.0f, 1.0f);
        return GrassColor.get(double3, double4);
    }
    
    public int getFoliageColor() {
        final double double2 = Mth.clamp(this.getTemperature(), 0.0f, 1.0f);
        final double double3 = Mth.clamp(this.getDownfall(), 0.0f, 1.0f);
        return FoliageColor.get(double2, double3);
    }
    
    public void buildSurfaceAt(final Random random, final ChunkAccess bzy, final int integer3, final int integer4, final int integer5, final double double6, final BlockState byj7, final BlockState byj8, final int integer9, final long long10) {
        this.surfaceBuilder.initNoise(long10);
        this.surfaceBuilder.apply(random, bzy, this, integer3, integer4, integer5, double6, byj7, byj8, integer9, long10);
    }
    
    public BiomeTempCategory getTemperatureCategory() {
        if (this.biomeCategory == BiomeCategory.OCEAN) {
            return BiomeTempCategory.OCEAN;
        }
        if (this.getTemperature() < 0.2) {
            return BiomeTempCategory.COLD;
        }
        if (this.getTemperature() < 1.0) {
            return BiomeTempCategory.MEDIUM;
        }
        return BiomeTempCategory.WARM;
    }
    
    public final float getDepth() {
        return this.depth;
    }
    
    public final float getDownfall() {
        return this.downfall;
    }
    
    public Component getName() {
        return new TranslatableComponent(this.getDescriptionId(), new Object[0]);
    }
    
    public String getDescriptionId() {
        if (this.descriptionId == null) {
            this.descriptionId = Util.makeDescriptionId("biome", Registry.BIOME.getKey(this));
        }
        return this.descriptionId;
    }
    
    public final float getScale() {
        return this.scale;
    }
    
    public final float getTemperature() {
        return this.temperature;
    }
    
    public final int getWaterColor() {
        return this.waterColor;
    }
    
    public final int getWaterFogColor() {
        return this.waterFogColor;
    }
    
    public final BiomeCategory getBiomeCategory() {
        return this.biomeCategory;
    }
    
    public ConfiguredSurfaceBuilder<?> getSurfaceBuilder() {
        return this.surfaceBuilder;
    }
    
    public SurfaceBuilderConfiguration getSurfaceBuilderConfig() {
        return (SurfaceBuilderConfiguration)this.surfaceBuilder.getSurfaceBuilderConfiguration();
    }
    
    @Nullable
    public String getParent() {
        return this.parent;
    }
    
    static {
        LOGGER = LogManager.getLogger();
        EXPLORABLE_BIOMES = Sets.newHashSet();
        MUTATED_BIOMES = new IdMapper<Biome>();
        TEMPERATURE_NOISE = new PerlinSimplexNoise(new WorldgenRandom(1234L), 0, 0);
        BIOME_INFO_NOISE = new PerlinSimplexNoise(new WorldgenRandom(2345L), 0, 0);
    }
    
    public enum BiomeTempCategory {
        OCEAN("ocean"), 
        COLD("cold"), 
        MEDIUM("medium"), 
        WARM("warm");
        
        private static final Map<String, BiomeTempCategory> BY_NAME;
        private final String name;
        
        private BiomeTempCategory(final String string3) {
            this.name = string3;
        }
        
        public String getName() {
            return this.name;
        }
        
        static {
            BY_NAME = Arrays.<BiomeTempCategory>stream(values()).collect(Collectors.toMap(BiomeTempCategory::getName, c -> c));
        }
    }
    
    public enum BiomeCategory {
        NONE("none"), 
        TAIGA("taiga"), 
        EXTREME_HILLS("extreme_hills"), 
        JUNGLE("jungle"), 
        MESA("mesa"), 
        PLAINS("plains"), 
        SAVANNA("savanna"), 
        ICY("icy"), 
        THEEND("the_end"), 
        BEACH("beach"), 
        FOREST("forest"), 
        OCEAN("ocean"), 
        DESERT("desert"), 
        RIVER("river"), 
        SWAMP("swamp"), 
        MUSHROOM("mushroom"), 
        NETHER("nether");
        
        private static final Map<String, BiomeCategory> BY_NAME;
        private final String name;
        
        private BiomeCategory(final String string3) {
            this.name = string3;
        }
        
        public String getName() {
            return this.name;
        }
        
        static {
            BY_NAME = Arrays.<BiomeCategory>stream(values()).collect(Collectors.toMap(BiomeCategory::getName, b -> b));
        }
    }
    
    public enum Precipitation {
        NONE("none"), 
        RAIN("rain"), 
        SNOW("snow");
        
        private static final Map<String, Precipitation> BY_NAME;
        private final String name;
        
        private Precipitation(final String string3) {
            this.name = string3;
        }
        
        public String getName() {
            return this.name;
        }
        
        static {
            BY_NAME = Arrays.<Precipitation>stream(values()).collect(Collectors.toMap(Precipitation::getName, d -> d));
        }
    }
    
    public static class SpawnerData extends WeighedRandom.WeighedRandomItem {
        public final EntityType<?> type;
        public final int minCount;
        public final int maxCount;
        
        public SpawnerData(final EntityType<?> aku, final int integer2, final int integer3, final int integer4) {
            super(integer2);
            this.type = aku;
            this.minCount = integer3;
            this.maxCount = integer4;
        }
        
        @Override
        public String toString() {
            return EntityType.getKey(this.type) + "*(" + this.minCount + "-" + this.maxCount + "):" + this.weight;
        }
    }
    
    public static class BiomeBuilder {
        @Nullable
        private ConfiguredSurfaceBuilder<?> surfaceBuilder;
        @Nullable
        private Precipitation precipitation;
        @Nullable
        private BiomeCategory biomeCategory;
        @Nullable
        private Float depth;
        @Nullable
        private Float scale;
        @Nullable
        private Float temperature;
        @Nullable
        private Float downfall;
        @Nullable
        private Integer waterColor;
        @Nullable
        private Integer waterFogColor;
        @Nullable
        private String parent;
        
        public <SC extends SurfaceBuilderConfiguration> BiomeBuilder surfaceBuilder(final SurfaceBuilder<SC> cng, final SC cni) {
            this.surfaceBuilder = new ConfiguredSurfaceBuilder<>(cng, cni);
            return this;
        }
        
        public BiomeBuilder surfaceBuilder(final ConfiguredSurfaceBuilder<?> cmw) {
            this.surfaceBuilder = cmw;
            return this;
        }
        
        public BiomeBuilder precipitation(final Precipitation d) {
            this.precipitation = d;
            return this;
        }
        
        public BiomeBuilder biomeCategory(final BiomeCategory b) {
            this.biomeCategory = b;
            return this;
        }
        
        public BiomeBuilder depth(final float float1) {
            this.depth = float1;
            return this;
        }
        
        public BiomeBuilder scale(final float float1) {
            this.scale = float1;
            return this;
        }
        
        public BiomeBuilder temperature(final float float1) {
            this.temperature = float1;
            return this;
        }
        
        public BiomeBuilder downfall(final float float1) {
            this.downfall = float1;
            return this;
        }
        
        public BiomeBuilder waterColor(final int integer) {
            this.waterColor = integer;
            return this;
        }
        
        public BiomeBuilder waterFogColor(final int integer) {
            this.waterFogColor = integer;
            return this;
        }
        
        public BiomeBuilder parent(@Nullable final String string) {
            this.parent = string;
            return this;
        }
        
        @Override
        public String toString() {
            return "BiomeBuilder{\nsurfaceBuilder=" + this.surfaceBuilder + ",\nprecipitation=" + this.precipitation + ",\nbiomeCategory=" + this.biomeCategory + ",\ndepth=" + this.depth + ",\nscale=" + this.scale + ",\ntemperature=" + this.temperature + ",\ndownfall=" + this.downfall + ",\nwaterColor=" + this.waterColor + ",\nwaterFogColor=" + this.waterFogColor + ",\nparent='" + this.parent + '\'' + "\n" + '}';
        }
    }
}
