package net.minecraft.world.level;

import org.apache.logging.log4j.LogManager;
import net.minecraft.world.level.pathfinder.PathComputationType;
import net.minecraft.tags.FluidTags;
import net.minecraft.tags.BlockTags;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.levelgen.Heightmap;
import javax.annotation.Nullable;
import java.util.List;
import net.minecraft.util.WeighedRandom;
import java.util.Random;
import net.minecraft.world.entity.EntityType;
import net.minecraft.world.entity.player.Player;
import net.minecraft.world.entity.SpawnGroupData;
import net.minecraft.world.level.biome.Biome;
import net.minecraft.world.level.block.state.BlockState;
import net.minecraft.world.level.chunk.ChunkGenerator;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.entity.Entity;
import net.minecraft.core.Registry;
import net.minecraft.world.entity.Mob;
import net.minecraft.world.entity.MobSpawnType;
import net.minecraft.world.entity.SpawnPlacements;
import java.util.Objects;
import net.minecraft.core.Position;
import net.minecraft.world.phys.Vec3;
import net.minecraft.util.Mth;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.chunk.LevelChunk;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.entity.MobCategory;
import org.apache.logging.log4j.Logger;

public final class NaturalSpawner {
    private static final Logger LOGGER;
    
    public static void spawnCategoryForChunk(final MobCategory alb, final ServerLevel xd, final LevelChunk cal, final BlockPos fk) {
        final ChunkGenerator<?> caa5 = xd.getChunkSource().getGenerator();
        int integer6 = 0;
        final BlockPos fk2 = getRandomPosWithin(xd, cal);
        final int integer7 = fk2.getX();
        final int integer8 = fk2.getY();
        final int integer9 = fk2.getZ();
        if (integer8 < 1) {
            return;
        }
        final BlockState byj11 = cal.getBlockState(fk2);
        if (byj11.isRedstoneConductor(cal, fk2)) {
            return;
        }
        final BlockPos.MutableBlockPos a12 = new BlockPos.MutableBlockPos();
        for (int integer10 = 0; integer10 < 3; ++integer10) {
            int integer11 = integer7;
            int integer12 = integer9;
            final int integer13 = 6;
            Biome.SpawnerData e17 = null;
            SpawnGroupData alm18 = null;
            int integer14 = Mth.ceil(Math.random() * 4.0);
            int integer15 = 0;
            for (int integer16 = 0; integer16 < integer14; ++integer16) {
                integer11 += xd.random.nextInt(6) - xd.random.nextInt(6);
                integer12 += xd.random.nextInt(6) - xd.random.nextInt(6);
                a12.set(integer11, integer8, integer12);
                final float float22 = integer11 + 0.5f;
                final float float23 = integer12 + 0.5f;
                final Player ayj24 = xd.getNearestPlayerIgnoreY(float22, float23, -1.0);
                if (ayj24 != null) {
                    final double double25 = ayj24.distanceToSqr(float22, integer8, float23);
                    if (double25 > 576.0) {
                        if (!fk.closerThan(new Vec3(float22, integer8, float23), 24.0)) {
                            final ChunkPos bjh27 = new ChunkPos(a12);
                            if (Objects.equals(bjh27, cal.getPos()) || xd.getChunkSource().isEntityTickingChunk(bjh27)) {
                                if (e17 == null) {
                                    e17 = getRandomSpawnMobAt(caa5, alb, xd.random, a12);
                                    if (e17 == null) {
                                        break;
                                    }
                                    integer14 = e17.minCount + xd.random.nextInt(1 + e17.maxCount - e17.minCount);
                                }
                                if (e17.type.getCategory() != MobCategory.MISC) {
                                    if (e17.type.canSpawnFarFromPlayer() || double25 <= 16384.0) {
                                        final EntityType<?> aku28 = e17.type;
                                        if (aku28.canSummon()) {
                                            if (canSpawnMobAt(caa5, alb, e17, a12)) {
                                                final SpawnPlacements.Type c29 = SpawnPlacements.getPlacementType(aku28);
                                                if (isSpawnPositionOk(c29, xd, a12, aku28)) {
                                                    if (SpawnPlacements.checkSpawnRules(aku28, xd, MobSpawnType.NATURAL, a12, xd.random)) {
                                                        if (xd.noCollision(aku28.getAABB(float22, integer8, float23))) {
                                                            Mob ala30;
                                                            try {
                                                                final Entity akq31 = (Entity)aku28.create(xd);
                                                                if (!(akq31 instanceof Mob)) {
                                                                    throw new IllegalStateException("Trying to spawn a non-mob: " + Registry.ENTITY_TYPE.getKey(aku28));
                                                                }
                                                                ala30 = (Mob)akq31;
                                                            }
                                                            catch (Exception exception31) {
                                                                NaturalSpawner.LOGGER.warn("Failed to create mob", (Throwable)exception31);
                                                                return;
                                                            }
                                                            ala30.moveTo(float22, integer8, float23, xd.random.nextFloat() * 360.0f, 0.0f);
                                                            if (double25 <= 16384.0 || !ala30.removeWhenFarAway(double25)) {
                                                                if (ala30.checkSpawnRules(xd, MobSpawnType.NATURAL)) {
                                                                    if (ala30.checkSpawnObstruction(xd)) {
                                                                        alm18 = ala30.finalizeSpawn(xd, xd.getCurrentDifficultyAt(new BlockPos(ala30)), MobSpawnType.NATURAL, alm18, null);
                                                                        ++integer6;
                                                                        ++integer15;
                                                                        xd.addFreshEntity(ala30);
                                                                        if (integer6 >= ala30.getMaxSpawnClusterSize()) {
                                                                            return;
                                                                        }
                                                                        if (ala30.isMaxGroupSizeReached(integer15)) {
                                                                            break;
                                                                        }
                                                                    }
                                                                }
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }
    
    @Nullable
    private static Biome.SpawnerData getRandomSpawnMobAt(final ChunkGenerator<?> caa, final MobCategory alb, final Random random, final BlockPos fk) {
        final List<Biome.SpawnerData> list5 = caa.getMobsAt(alb, fk);
        if (list5.isEmpty()) {
            return null;
        }
        return WeighedRandom.<Biome.SpawnerData>getRandomItem(random, list5);
    }
    
    private static boolean canSpawnMobAt(final ChunkGenerator<?> caa, final MobCategory alb, final Biome.SpawnerData e, final BlockPos fk) {
        final List<Biome.SpawnerData> list5 = caa.getMobsAt(alb, fk);
        return !list5.isEmpty() && list5.contains(e);
    }
    
    private static BlockPos getRandomPosWithin(final Level bjw, final LevelChunk cal) {
        final ChunkPos bjh3 = cal.getPos();
        final int integer4 = bjh3.getMinBlockX() + bjw.random.nextInt(16);
        final int integer5 = bjh3.getMinBlockZ() + bjw.random.nextInt(16);
        final int integer6 = cal.getHeight(Heightmap.Types.WORLD_SURFACE, integer4, integer5) + 1;
        final int integer7 = bjw.random.nextInt(integer6 + 1);
        return new BlockPos(integer4, integer7, integer5);
    }
    
    public static boolean isValidEmptySpawnBlock(final BlockGetter bjg, final BlockPos fk, final BlockState byj, final FluidState coj) {
        return !byj.isCollisionShapeFullBlock(bjg, fk) && !byj.isSignalSource() && coj.isEmpty() && !byj.is(BlockTags.RAILS);
    }
    
    public static boolean isSpawnPositionOk(final SpawnPlacements.Type c, final LevelReader bjz, final BlockPos fk, @Nullable final EntityType<?> aku) {
        if (c == SpawnPlacements.Type.NO_RESTRICTIONS) {
            return true;
        }
        if (aku == null || !bjz.getWorldBorder().isWithinBounds(fk)) {
            return false;
        }
        final BlockState byj5 = bjz.getBlockState(fk);
        final FluidState coj6 = bjz.getFluidState(fk);
        final BlockPos fk2 = fk.above();
        final BlockPos fk3 = fk.below();
        switch (c) {
            case IN_WATER: {
                return coj6.is(FluidTags.WATER) && bjz.getFluidState(fk3).is(FluidTags.WATER) && !bjz.getBlockState(fk2).isRedstoneConductor(bjz, fk2);
            }
            default: {
                final BlockState byj6 = bjz.getBlockState(fk3);
                return byj6.isValidSpawn(bjz, fk3, aku) && isValidEmptySpawnBlock(bjz, fk, byj5, coj6) && isValidEmptySpawnBlock(bjz, fk2, bjz.getBlockState(fk2), bjz.getFluidState(fk2));
            }
        }
    }
    
    public static void spawnMobsForChunkGeneration(final LevelAccessor bjx, final Biome bkt, final int integer3, final int integer4, final Random random) {
        final List<Biome.SpawnerData> list6 = bkt.getMobs(MobCategory.CREATURE);
        if (list6.isEmpty()) {
            return;
        }
        final int integer5 = integer3 << 4;
        final int integer6 = integer4 << 4;
        while (random.nextFloat() < bkt.getCreatureProbability()) {
            final Biome.SpawnerData e9 = WeighedRandom.<Biome.SpawnerData>getRandomItem(random, list6);
            final int integer7 = e9.minCount + random.nextInt(1 + e9.maxCount - e9.minCount);
            SpawnGroupData alm11 = null;
            int integer8 = integer5 + random.nextInt(16);
            int integer9 = integer6 + random.nextInt(16);
            final int integer10 = integer8;
            final int integer11 = integer9;
            for (int integer12 = 0; integer12 < integer7; ++integer12) {
                boolean boolean17 = false;
                for (int integer13 = 0; !boolean17 && integer13 < 4; ++integer13) {
                    final BlockPos fk19 = getTopNonCollidingPos(bjx, e9.type, integer8, integer9);
                    if (e9.type.canSummon() && isSpawnPositionOk(SpawnPlacements.Type.ON_GROUND, bjx, fk19, e9.type)) {
                        final float float20 = e9.type.getWidth();
                        final double double21 = Mth.clamp(integer8, integer5 + (double)float20, integer5 + 16.0 - float20);
                        final double double22 = Mth.clamp(integer9, integer6 + (double)float20, integer6 + 16.0 - float20);
                        if (!bjx.noCollision(e9.type.getAABB(double21, fk19.getY(), double22))) {
                            continue;
                        }
                        if (!SpawnPlacements.<Entity>checkSpawnRules(e9.type, bjx, MobSpawnType.CHUNK_GENERATION, new BlockPos(double21, fk19.getY(), double22), bjx.getRandom())) {
                            continue;
                        }
                        Entity akq25;
                        try {
                            akq25 = (Entity)e9.type.create(bjx.getLevel());
                        }
                        catch (Exception exception26) {
                            NaturalSpawner.LOGGER.warn("Failed to create mob", (Throwable)exception26);
                            continue;
                        }
                        akq25.moveTo(double21, fk19.getY(), double22, random.nextFloat() * 360.0f, 0.0f);
                        if (akq25 instanceof Mob) {
                            final Mob ala26 = (Mob)akq25;
                            if (ala26.checkSpawnRules(bjx, MobSpawnType.CHUNK_GENERATION) && ala26.checkSpawnObstruction(bjx)) {
                                alm11 = ala26.finalizeSpawn(bjx, bjx.getCurrentDifficultyAt(new BlockPos(ala26)), MobSpawnType.CHUNK_GENERATION, alm11, null);
                                bjx.addFreshEntity(ala26);
                                boolean17 = true;
                            }
                        }
                    }
                    for (integer8 += random.nextInt(5) - random.nextInt(5), integer9 += random.nextInt(5) - random.nextInt(5); integer8 < integer5 || integer8 >= integer5 + 16 || integer9 < integer6 || integer9 >= integer6 + 16; integer8 = integer10 + random.nextInt(5) - random.nextInt(5), integer9 = integer11 + random.nextInt(5) - random.nextInt(5)) {}
                }
            }
        }
    }
    
    private static BlockPos getTopNonCollidingPos(final LevelReader bjz, @Nullable final EntityType<?> aku, final int integer3, final int integer4) {
        final BlockPos fk5 = new BlockPos(integer3, bjz.getHeight(SpawnPlacements.getHeightmapType(aku), integer3, integer4), integer4);
        final BlockPos fk6 = fk5.below();
        if (bjz.getBlockState(fk6).isPathfindable(bjz, fk6, PathComputationType.LAND)) {
            return fk6;
        }
        return fk5;
    }
    
    static {
        LOGGER = LogManager.getLogger();
    }
}
