package net.minecraft.world.level.chunk;

import org.apache.logging.log4j.LogManager;
import net.minecraft.core.Registry;
import net.minecraft.server.level.ServerLevel;
import net.minecraft.world.level.ChunkTickList;
import it.unimi.dsi.fastutil.shorts.ShortListIterator;
import net.minecraft.world.level.LevelAccessor;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import java.util.stream.StreamSupport;
import java.util.stream.Stream;
import java.util.Collections;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.entity.boss.EnderDragonPart;
import net.minecraft.world.entity.boss.enderdragon.EnderDragon;
import java.util.function.Predicate;
import java.util.List;
import net.minecraft.world.phys.AABB;
import net.minecraft.util.Mth;
import net.minecraft.world.level.lighting.LevelLightEngine;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.block.EntityBlock;
import net.minecraft.world.level.material.Fluids;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.ReportedException;
import net.minecraft.CrashReportCategory;
import net.minecraft.CrashReport;
import net.minecraft.world.level.levelgen.DebugLevelSource;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.LevelType;
import net.minecraft.world.level.block.state.BlockState;
import java.util.Collection;
import com.google.common.collect.Sets;
import java.util.Set;
import java.util.Iterator;
import net.minecraft.world.entity.EntityType;
import com.google.common.collect.Maps;
import net.minecraft.world.level.EmptyTickList;
import net.minecraft.world.level.ChunkPos;
import java.util.function.Consumer;
import javax.annotation.Nullable;
import net.minecraft.server.level.ChunkHolder;
import java.util.function.Supplier;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.block.Block;
import net.minecraft.world.level.TickList;
import it.unimi.dsi.fastutil.shorts.ShortList;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.entity.Entity;
import net.minecraft.util.ClassInstanceMultiMap;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.world.level.levelgen.Heightmap;
import net.minecraft.world.level.Level;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.core.BlockPos;
import java.util.Map;
import org.apache.logging.log4j.Logger;

public class LevelChunk implements ChunkAccess {
    private static final Logger LOGGER;
    public static final LevelChunkSection EMPTY_SECTION;
    private final LevelChunkSection[] sections;
    private ChunkBiomeContainer biomes;
    private final Map<BlockPos, CompoundTag> pendingBlockEntities;
    private boolean loaded;
    private final Level level;
    private final Map<Heightmap.Types, Heightmap> heightmaps;
    private final UpgradeData upgradeData;
    private final Map<BlockPos, BlockEntity> blockEntities;
    private final ClassInstanceMultiMap<Entity>[] entitySections;
    private final Map<String, StructureStart> structureStarts;
    private final Map<String, LongSet> structuresRefences;
    private final ShortList[] postProcessing;
    private TickList<Block> blockTicks;
    private TickList<Fluid> liquidTicks;
    private boolean lastSaveHadEntities;
    private long lastSaveTime;
    private volatile boolean unsaved;
    private long inhabitedTime;
    @Nullable
    private Supplier<ChunkHolder.FullChunkStatus> fullStatus;
    @Nullable
    private Consumer<LevelChunk> postLoad;
    private final ChunkPos chunkPos;
    private volatile boolean isLightCorrect;
    
    public LevelChunk(final Level bjw, final ChunkPos bjh, final ChunkBiomeContainer bzz) {
        this(bjw, bjh, bzz, UpgradeData.EMPTY, EmptyTickList.empty(), EmptyTickList.empty(), 0L, null, null);
    }
    
    public LevelChunk(final Level bjw, final ChunkPos bjh, final ChunkBiomeContainer bzz, final UpgradeData cav, final TickList<Block> bkl5, final TickList<Fluid> bkl6, final long long7, @Nullable final LevelChunkSection[] arr, @Nullable final Consumer<LevelChunk> consumer) {
        this.sections = new LevelChunkSection[16];
        this.pendingBlockEntities = Maps.newHashMap();
        this.heightmaps = Maps.newEnumMap(Heightmap.Types.class);
        this.blockEntities = Maps.newHashMap();
        this.structureStarts = Maps.newHashMap();
        this.structuresRefences = Maps.newHashMap();
        this.postProcessing = new ShortList[16];
        this.entitySections = new ClassInstanceMultiMap[16];
        this.level = bjw;
        this.chunkPos = bjh;
        this.upgradeData = cav;
        for (final Heightmap.Types a15 : Heightmap.Types.values()) {
            if (ChunkStatus.FULL.heightmapsAfter().contains(a15)) {
                this.heightmaps.put(a15, new Heightmap(this, a15));
            }
        }
        for (int integer12 = 0; integer12 < this.entitySections.length; ++integer12) {
            this.entitySections[integer12] = new ClassInstanceMultiMap<Entity>(Entity.class);
        }
        this.biomes = bzz;
        this.blockTicks = bkl5;
        this.liquidTicks = bkl6;
        this.inhabitedTime = long7;
        this.postLoad = consumer;
        if (arr != null) {
            if (this.sections.length == arr.length) {
                System.arraycopy(arr, 0, this.sections, 0, this.sections.length);
            }
            else {
                LevelChunk.LOGGER.warn("Could not set level chunk sections, array length is {} instead of {}", arr.length, this.sections.length);
            }
        }
    }
    
    public LevelChunk(final Level bjw, final ProtoChunk cat) {
        this(bjw, cat.getPos(), cat.getBiomes(), cat.getUpgradeData(), cat.getBlockTicks(), cat.getLiquidTicks(), cat.getInhabitedTime(), cat.getSections(), null);
        for (final CompoundTag jt5 : cat.getEntities()) {
            EntityType.loadEntityRecursive(jt5, bjw, akq -> {
                this.addEntity(akq);
                return akq;
            });
        }
        for (final BlockEntity bwl5 : cat.getBlockEntities().values()) {
            this.addBlockEntity(bwl5);
        }
        this.pendingBlockEntities.putAll(cat.getBlockEntityNbts());
        for (int integer4 = 0; integer4 < cat.getPostProcessing().length; ++integer4) {
            this.postProcessing[integer4] = cat.getPostProcessing()[integer4];
        }
        this.setAllStarts(cat.getAllStarts());
        this.setAllReferences(cat.getAllReferences());
        for (final Map.Entry<Heightmap.Types, Heightmap> entry5 : cat.getHeightmaps()) {
            if (ChunkStatus.FULL.heightmapsAfter().contains(entry5.getKey())) {
                this.getOrCreateHeightmapUnprimed(entry5.getKey()).setRawData(entry5.getValue().getRawData());
            }
        }
        this.setLightCorrect(cat.isLightCorrect());
        this.unsaved = true;
    }
    
    @Override
    public Heightmap getOrCreateHeightmapUnprimed(final Heightmap.Types a) {
        return this.heightmaps.computeIfAbsent(a, a -> new Heightmap(this, a));
    }
    
    @Override
    public Set<BlockPos> getBlockEntitiesPos() {
        final Set<BlockPos> set2 = Sets.newHashSet(this.pendingBlockEntities.keySet());
        set2.addAll(this.blockEntities.keySet());
        return set2;
    }
    
    @Override
    public LevelChunkSection[] getSections() {
        return this.sections;
    }
    
    @Override
    public BlockState getBlockState(final BlockPos fk) {
        final int integer3 = fk.getX();
        final int integer4 = fk.getY();
        final int integer5 = fk.getZ();
        if (this.level.getGeneratorType() == LevelType.DEBUG_ALL_BLOCK_STATES) {
            BlockState byj6 = null;
            if (integer4 == 60) {
                byj6 = Blocks.BARRIER.defaultBlockState();
            }
            if (integer4 == 70) {
                byj6 = DebugLevelSource.getBlockStateFor(integer3, integer5);
            }
            return (byj6 == null) ? Blocks.AIR.defaultBlockState() : byj6;
        }
        try {
            if (integer4 >= 0 && integer4 >> 4 < this.sections.length) {
                final LevelChunkSection cam6 = this.sections[integer4 >> 4];
                if (!LevelChunkSection.isEmpty(cam6)) {
                    return cam6.getBlockState(integer3 & 0xF, integer4 & 0xF, integer5 & 0xF);
                }
            }
            return Blocks.AIR.defaultBlockState();
        }
        catch (Throwable throwable6) {
            final CrashReport h7 = CrashReport.forThrowable(throwable6, "Getting block state");
            final CrashReportCategory i8 = h7.addCategory("Block being got");
            i8.setDetail("Location", () -> CrashReportCategory.formatLocation(integer3, integer4, integer5));
            throw new ReportedException(h7);
        }
    }
    
    @Override
    public FluidState getFluidState(final BlockPos fk) {
        return this.getFluidState(fk.getX(), fk.getY(), fk.getZ());
    }
    
    public FluidState getFluidState(final int integer1, final int integer2, final int integer3) {
        try {
            if (integer2 >= 0 && integer2 >> 4 < this.sections.length) {
                final LevelChunkSection cam5 = this.sections[integer2 >> 4];
                if (!LevelChunkSection.isEmpty(cam5)) {
                    return cam5.getFluidState(integer1 & 0xF, integer2 & 0xF, integer3 & 0xF);
                }
            }
            return Fluids.EMPTY.defaultFluidState();
        }
        catch (Throwable throwable5) {
            final CrashReport h6 = CrashReport.forThrowable(throwable5, "Getting fluid state");
            final CrashReportCategory i7 = h6.addCategory("Block being got");
            i7.setDetail("Location", () -> CrashReportCategory.formatLocation(integer1, integer2, integer3));
            throw new ReportedException(h6);
        }
    }
    
    @Nullable
    @Override
    public BlockState setBlockState(final BlockPos fk, final BlockState byj, final boolean boolean3) {
        final int integer5 = fk.getX() & 0xF;
        final int integer6 = fk.getY();
        final int integer7 = fk.getZ() & 0xF;
        LevelChunkSection cam8 = this.sections[integer6 >> 4];
        if (cam8 == LevelChunk.EMPTY_SECTION) {
            if (byj.isAir()) {
                return null;
            }
            cam8 = new LevelChunkSection(integer6 >> 4 << 4);
            this.sections[integer6 >> 4] = cam8;
        }
        final boolean boolean4 = cam8.isEmpty();
        final BlockState byj2 = cam8.setBlockState(integer5, integer6 & 0xF, integer7, byj);
        if (byj2 == byj) {
            return null;
        }
        final Block bph11 = byj.getBlock();
        final Block bph12 = byj2.getBlock();
        this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING).update(integer5, integer6, integer7, byj);
        this.heightmaps.get(Heightmap.Types.MOTION_BLOCKING_NO_LEAVES).update(integer5, integer6, integer7, byj);
        this.heightmaps.get(Heightmap.Types.OCEAN_FLOOR).update(integer5, integer6, integer7, byj);
        this.heightmaps.get(Heightmap.Types.WORLD_SURFACE).update(integer5, integer6, integer7, byj);
        final boolean boolean5 = cam8.isEmpty();
        if (boolean4 != boolean5) {
            this.level.getChunkSource().getLightEngine().updateSectionStatus(fk, boolean5);
        }
        if (!this.level.isClientSide) {
            byj2.onRemove(this.level, fk, byj, boolean3);
        }
        else if (bph12 != bph11 && bph12 instanceof EntityBlock) {
            this.level.removeBlockEntity(fk);
        }
        if (cam8.getBlockState(integer5, integer6 & 0xF, integer7).getBlock() != bph11) {
            return null;
        }
        if (bph12 instanceof EntityBlock) {
            final BlockEntity bwl14 = this.getBlockEntity(fk, EntityCreationType.CHECK);
            if (bwl14 != null) {
                bwl14.clearCache();
            }
        }
        if (!this.level.isClientSide) {
            byj.onPlace(this.level, fk, byj2, boolean3);
        }
        if (bph11 instanceof EntityBlock) {
            BlockEntity bwl14 = this.getBlockEntity(fk, EntityCreationType.CHECK);
            if (bwl14 == null) {
                bwl14 = ((EntityBlock)bph11).newBlockEntity(this.level);
                this.level.setBlockEntity(fk, bwl14);
            }
            else {
                bwl14.clearCache();
            }
        }
        this.unsaved = true;
        return byj2;
    }
    
    @Nullable
    public LevelLightEngine getLightEngine() {
        return this.level.getChunkSource().getLightEngine();
    }
    
    @Override
    public void addEntity(final Entity akq) {
        this.lastSaveHadEntities = true;
        final int integer3 = Mth.floor(akq.getX() / 16.0);
        final int integer4 = Mth.floor(akq.getZ() / 16.0);
        if (integer3 != this.chunkPos.x || integer4 != this.chunkPos.z) {
            LevelChunk.LOGGER.warn("Wrong location! ({}, {}) should be ({}, {}), {}", integer3, integer4, this.chunkPos.x, this.chunkPos.z, akq);
            akq.removed = true;
        }
        int integer5 = Mth.floor(akq.getY() / 16.0);
        if (integer5 < 0) {
            integer5 = 0;
        }
        if (integer5 >= this.entitySections.length) {
            integer5 = this.entitySections.length - 1;
        }
        akq.inChunk = true;
        akq.xChunk = this.chunkPos.x;
        akq.yChunk = integer5;
        akq.zChunk = this.chunkPos.z;
        this.entitySections[integer5].add(akq);
    }
    
    @Override
    public void setHeightmap(final Heightmap.Types a, final long[] arr) {
        this.heightmaps.get(a).setRawData(arr);
    }
    
    public void removeEntity(final Entity akq) {
        this.removeEntity(akq, akq.yChunk);
    }
    
    public void removeEntity(final Entity akq, int integer) {
        if (integer < 0) {
            integer = 0;
        }
        if (integer >= this.entitySections.length) {
            integer = this.entitySections.length - 1;
        }
        this.entitySections[integer].remove(akq);
    }
    
    @Override
    public int getHeight(final Heightmap.Types a, final int integer2, final int integer3) {
        return this.heightmaps.get(a).getFirstAvailable(integer2 & 0xF, integer3 & 0xF) - 1;
    }
    
    @Nullable
    private BlockEntity createBlockEntity(final BlockPos fk) {
        final BlockState byj3 = this.getBlockState(fk);
        final Block bph4 = byj3.getBlock();
        if (!bph4.isEntityBlock()) {
            return null;
        }
        return ((EntityBlock)bph4).newBlockEntity(this.level);
    }
    
    @Nullable
    @Override
    public BlockEntity getBlockEntity(final BlockPos fk) {
        return this.getBlockEntity(fk, EntityCreationType.CHECK);
    }
    
    @Nullable
    public BlockEntity getBlockEntity(final BlockPos fk, final EntityCreationType a) {
        BlockEntity bwl4 = this.blockEntities.get(fk);
        if (bwl4 == null) {
            final CompoundTag jt5 = this.pendingBlockEntities.remove(fk);
            if (jt5 != null) {
                final BlockEntity bwl5 = this.promotePendingBlockEntity(fk, jt5);
                if (bwl5 != null) {
                    return bwl5;
                }
            }
        }
        if (bwl4 == null) {
            if (a == EntityCreationType.IMMEDIATE) {
                bwl4 = this.createBlockEntity(fk);
                this.level.setBlockEntity(fk, bwl4);
            }
        }
        else if (bwl4.isRemoved()) {
            this.blockEntities.remove(fk);
            return null;
        }
        return bwl4;
    }
    
    public void addBlockEntity(final BlockEntity bwl) {
        this.setBlockEntity(bwl.getBlockPos(), bwl);
        if (this.loaded || this.level.isClientSide()) {
            this.level.setBlockEntity(bwl.getBlockPos(), bwl);
        }
    }
    
    @Override
    public void setBlockEntity(final BlockPos fk, final BlockEntity bwl) {
        if (!(this.getBlockState(fk).getBlock() instanceof EntityBlock)) {
            return;
        }
        bwl.setLevelAndPosition(this.level, fk);
        bwl.clearRemoved();
        final BlockEntity bwl2 = this.blockEntities.put(fk.immutable(), bwl);
        if (bwl2 != null && bwl2 != bwl) {
            bwl2.setRemoved();
        }
    }
    
    @Override
    public void setBlockEntityNbt(final CompoundTag jt) {
        this.pendingBlockEntities.put(new BlockPos(jt.getInt("x"), jt.getInt("y"), jt.getInt("z")), jt);
    }
    
    @Nullable
    @Override
    public CompoundTag getBlockEntityNbtForSaving(final BlockPos fk) {
        final BlockEntity bwl3 = this.getBlockEntity(fk);
        if (bwl3 != null && !bwl3.isRemoved()) {
            final CompoundTag jt4 = bwl3.save(new CompoundTag());
            jt4.putBoolean("keepPacked", false);
            return jt4;
        }
        CompoundTag jt4 = this.pendingBlockEntities.get(fk);
        if (jt4 != null) {
            jt4 = jt4.copy();
            jt4.putBoolean("keepPacked", true);
        }
        return jt4;
    }
    
    @Override
    public void removeBlockEntity(final BlockPos fk) {
        if (this.loaded || this.level.isClientSide()) {
            final BlockEntity bwl3 = this.blockEntities.remove(fk);
            if (bwl3 != null) {
                bwl3.setRemoved();
            }
        }
    }
    
    public void runPostLoad() {
        if (this.postLoad != null) {
            this.postLoad.accept(this);
            this.postLoad = null;
        }
    }
    
    public void markUnsaved() {
        this.unsaved = true;
    }
    
    public void getEntities(@Nullable final Entity akq, final AABB cvf, final List<Entity> list, @Nullable final Predicate<? super Entity> predicate) {
        int integer6 = Mth.floor((cvf.minY - 2.0) / 16.0);
        int integer7 = Mth.floor((cvf.maxY + 2.0) / 16.0);
        integer6 = Mth.clamp(integer6, 0, this.entitySections.length - 1);
        integer7 = Mth.clamp(integer7, 0, this.entitySections.length - 1);
        for (int integer8 = integer6; integer8 <= integer7; ++integer8) {
            if (!this.entitySections[integer8].isEmpty()) {
                for (final Entity akq2 : this.entitySections[integer8]) {
                    if (akq2.getBoundingBox().intersects(cvf) && akq2 != akq) {
                        if (predicate == null || predicate.test(akq2)) {
                            list.add(akq2);
                        }
                        if (!(akq2 instanceof EnderDragon)) {
                            continue;
                        }
                        for (final EnderDragonPart auq14 : ((EnderDragon)akq2).getSubEntities()) {
                            if (auq14 != akq && auq14.getBoundingBox().intersects(cvf) && (predicate == null || predicate.test(auq14))) {
                                list.add(auq14);
                            }
                        }
                    }
                }
            }
        }
    }
    
    public <T extends Entity> void getEntities(@Nullable final EntityType<?> aku, final AABB cvf, final List<? super T> list, final Predicate<? super T> predicate) {
        int integer6 = Mth.floor((cvf.minY - 2.0) / 16.0);
        int integer7 = Mth.floor((cvf.maxY + 2.0) / 16.0);
        integer6 = Mth.clamp(integer6, 0, this.entitySections.length - 1);
        integer7 = Mth.clamp(integer7, 0, this.entitySections.length - 1);
        for (int integer8 = integer6; integer8 <= integer7; ++integer8) {
            for (final Entity akq10 : this.entitySections[integer8].<Entity>find(Entity.class)) {
                if (aku != null && akq10.getType() != aku) {
                    continue;
                }
                final T akq11 = (T)akq10;
                if (!akq10.getBoundingBox().intersects(cvf) || !predicate.test(akq11)) {
                    continue;
                }
                list.add(akq11);
            }
        }
    }
    
    public <T extends Entity> void getEntitiesOfClass(final Class<? extends T> class1, final AABB cvf, final List<T> list, @Nullable final Predicate<? super T> predicate) {
        int integer6 = Mth.floor((cvf.minY - 2.0) / 16.0);
        int integer7 = Mth.floor((cvf.maxY + 2.0) / 16.0);
        integer6 = Mth.clamp(integer6, 0, this.entitySections.length - 1);
        integer7 = Mth.clamp(integer7, 0, this.entitySections.length - 1);
        for (int integer8 = integer6; integer8 <= integer7; ++integer8) {
            for (final T akq10 : this.entitySections[integer8].find(class1)) {
                if (akq10.getBoundingBox().intersects(cvf) && (predicate == null || predicate.test(akq10))) {
                    list.add(akq10);
                }
            }
        }
    }
    
    public boolean isEmpty() {
        return false;
    }
    
    @Override
    public ChunkPos getPos() {
        return this.chunkPos;
    }
    
    public void replaceWithPacketData(@Nullable final ChunkBiomeContainer bzz, final FriendlyByteBuf kv, final CompoundTag jt, final int integer) {
        final boolean boolean6 = bzz != null;
        final Predicate<BlockPos> predicate7 = boolean6 ? (fk -> true) : (fk -> (integer & 1 << (fk.getY() >> 4)) != 0x0);
        Sets.newHashSet(this.blockEntities.keySet()).stream().filter(predicate7).forEach(this.level::removeBlockEntity);
        for (int integer2 = 0; integer2 < this.sections.length; ++integer2) {
            LevelChunkSection cam9 = this.sections[integer2];
            if ((integer & 1 << integer2) == 0x0) {
                if (boolean6 && cam9 != LevelChunk.EMPTY_SECTION) {
                    this.sections[integer2] = LevelChunk.EMPTY_SECTION;
                }
            }
            else {
                if (cam9 == LevelChunk.EMPTY_SECTION) {
                    cam9 = new LevelChunkSection(integer2 << 4);
                    this.sections[integer2] = cam9;
                }
                cam9.read(kv);
            }
        }
        if (bzz != null) {
            this.biomes = bzz;
        }
        for (final Heightmap.Types a11 : Heightmap.Types.values()) {
            final String string12 = a11.getSerializationKey();
            if (jt.contains(string12, 12)) {
                this.setHeightmap(a11, jt.getLongArray(string12));
            }
        }
        for (final BlockEntity bwl9 : this.blockEntities.values()) {
            bwl9.clearCache();
        }
    }
    
    @Override
    public ChunkBiomeContainer getBiomes() {
        return this.biomes;
    }
    
    public void setLoaded(final boolean boolean1) {
        this.loaded = boolean1;
    }
    
    public Level getLevel() {
        return this.level;
    }
    
    @Override
    public Collection<Map.Entry<Heightmap.Types, Heightmap>> getHeightmaps() {
        return Collections.unmodifiableSet(this.heightmaps.entrySet());
    }
    
    public Map<BlockPos, BlockEntity> getBlockEntities() {
        return this.blockEntities;
    }
    
    public ClassInstanceMultiMap<Entity>[] getEntitySections() {
        return this.entitySections;
    }
    
    @Override
    public CompoundTag getBlockEntityNbt(final BlockPos fk) {
        return this.pendingBlockEntities.get(fk);
    }
    
    @Override
    public Stream<BlockPos> getLights() {
        return StreamSupport.<BlockPos>stream(BlockPos.betweenClosed(this.chunkPos.getMinBlockX(), 0, this.chunkPos.getMinBlockZ(), this.chunkPos.getMaxBlockX(), 255, this.chunkPos.getMaxBlockZ()).spliterator(), false).filter(fk -> this.getBlockState(fk).getLightEmission() != 0);
    }
    
    @Override
    public TickList<Block> getBlockTicks() {
        return this.blockTicks;
    }
    
    @Override
    public TickList<Fluid> getLiquidTicks() {
        return this.liquidTicks;
    }
    
    @Override
    public void setUnsaved(final boolean boolean1) {
        this.unsaved = boolean1;
    }
    
    @Override
    public boolean isUnsaved() {
        return this.unsaved || (this.lastSaveHadEntities && this.level.getGameTime() != this.lastSaveTime);
    }
    
    public void setLastSaveHadEntities(final boolean boolean1) {
        this.lastSaveHadEntities = boolean1;
    }
    
    @Override
    public void setLastSaveTime(final long long1) {
        this.lastSaveTime = long1;
    }
    
    @Nullable
    @Override
    public StructureStart getStartForFeature(final String string) {
        return this.structureStarts.get(string);
    }
    
    @Override
    public void setStartForFeature(final String string, final StructureStart clv) {
        this.structureStarts.put(string, clv);
    }
    
    @Override
    public Map<String, StructureStart> getAllStarts() {
        return this.structureStarts;
    }
    
    @Override
    public void setAllStarts(final Map<String, StructureStart> map) {
        this.structureStarts.clear();
        this.structureStarts.putAll(map);
    }
    
    @Override
    public LongSet getReferencesForFeature(final String string) {
        return this.structuresRefences.computeIfAbsent(string, string -> new LongOpenHashSet());
    }
    
    @Override
    public void addReferenceForFeature(final String string, final long long2) {
        this.structuresRefences.computeIfAbsent(string, string -> new LongOpenHashSet()).add(long2);
    }
    
    @Override
    public Map<String, LongSet> getAllReferences() {
        return this.structuresRefences;
    }
    
    @Override
    public void setAllReferences(final Map<String, LongSet> map) {
        this.structuresRefences.clear();
        this.structuresRefences.putAll(map);
    }
    
    @Override
    public long getInhabitedTime() {
        return this.inhabitedTime;
    }
    
    @Override
    public void setInhabitedTime(final long long1) {
        this.inhabitedTime = long1;
    }
    
    public void postProcessGeneration() {
        final ChunkPos bjh2 = this.getPos();
        for (int integer3 = 0; integer3 < this.postProcessing.length; ++integer3) {
            if (this.postProcessing[integer3] != null) {
                for (final Short short5 : this.postProcessing[integer3]) {
                    final BlockPos fk6 = ProtoChunk.unpackOffsetCoordinates(short5, integer3, bjh2);
                    final BlockState byj7 = this.getBlockState(fk6);
                    final BlockState byj8 = Block.updateFromNeighbourShapes(byj7, this.level, fk6);
                    this.level.setBlock(fk6, byj8, 20);
                }
                this.postProcessing[integer3].clear();
            }
        }
        this.unpackTicks();
        for (final BlockPos fk7 : Sets.<BlockPos>newHashSet(this.pendingBlockEntities.keySet())) {
            this.getBlockEntity(fk7);
        }
        this.pendingBlockEntities.clear();
        this.upgradeData.upgrade(this);
    }
    
    @Nullable
    private BlockEntity promotePendingBlockEntity(final BlockPos fk, final CompoundTag jt) {
        BlockEntity bwl4;
        if ("DUMMY".equals(jt.getString("id"))) {
            final Block bph5 = this.getBlockState(fk).getBlock();
            if (bph5 instanceof EntityBlock) {
                bwl4 = ((EntityBlock)bph5).newBlockEntity(this.level);
            }
            else {
                bwl4 = null;
                LevelChunk.LOGGER.warn("Tried to load a DUMMY block entity @ {} but found not block entity block {} at location", fk, this.getBlockState(fk));
            }
        }
        else {
            bwl4 = BlockEntity.loadStatic(jt);
        }
        if (bwl4 != null) {
            bwl4.setLevelAndPosition(this.level, fk);
            this.addBlockEntity(bwl4);
        }
        else {
            LevelChunk.LOGGER.warn("Tried to load a block entity for block {} but failed at location {}", this.getBlockState(fk), fk);
        }
        return bwl4;
    }
    
    @Override
    public UpgradeData getUpgradeData() {
        return this.upgradeData;
    }
    
    @Override
    public ShortList[] getPostProcessing() {
        return this.postProcessing;
    }
    
    public void unpackTicks() {
        if (this.blockTicks instanceof ProtoTickList) {
            ((ProtoTickList)this.blockTicks).copyOut(this.level.getBlockTicks(), fk -> this.getBlockState(fk).getBlock());
            this.blockTicks = EmptyTickList.empty();
        }
        else if (this.blockTicks instanceof ChunkTickList) {
            this.level.getBlockTicks().addAll(((ChunkTickList)this.blockTicks).ticks());
            this.blockTicks = EmptyTickList.empty();
        }
        if (this.liquidTicks instanceof ProtoTickList) {
            ((ProtoTickList)this.liquidTicks).copyOut(this.level.getLiquidTicks(), fk -> this.getFluidState(fk).getType());
            this.liquidTicks = EmptyTickList.empty();
        }
        else if (this.liquidTicks instanceof ChunkTickList) {
            this.level.getLiquidTicks().addAll(((ChunkTickList)this.liquidTicks).ticks());
            this.liquidTicks = EmptyTickList.empty();
        }
    }
    
    public void packTicks(final ServerLevel xd) {
        if (this.blockTicks == EmptyTickList.empty()) {
            this.blockTicks = new ChunkTickList<Block>(Registry.BLOCK::getKey, xd.getBlockTicks().fetchTicksInChunk(this.chunkPos, true, false));
            this.setUnsaved(true);
        }
        if (this.liquidTicks == EmptyTickList.empty()) {
            this.liquidTicks = new ChunkTickList<Fluid>(Registry.FLUID::getKey, xd.getLiquidTicks().fetchTicksInChunk(this.chunkPos, true, false));
            this.setUnsaved(true);
        }
    }
    
    @Override
    public ChunkStatus getStatus() {
        return ChunkStatus.FULL;
    }
    
    public ChunkHolder.FullChunkStatus getFullStatus() {
        if (this.fullStatus == null) {
            return ChunkHolder.FullChunkStatus.BORDER;
        }
        return this.fullStatus.get();
    }
    
    public void setFullStatus(final Supplier<ChunkHolder.FullChunkStatus> supplier) {
        this.fullStatus = supplier;
    }
    
    @Override
    public boolean isLightCorrect() {
        return this.isLightCorrect;
    }
    
    @Override
    public void setLightCorrect(final boolean boolean1) {
        this.isLightCorrect = boolean1;
        this.setUnsaved(true);
    }
    
    static {
        LOGGER = LogManager.getLogger();
        EMPTY_SECTION = null;
    }
    
    public enum EntityCreationType {
        IMMEDIATE, 
        QUEUED, 
        CHECK;
    }
}
