package net.minecraft.world.level.chunk;

import org.apache.logging.log4j.LogManager;
import net.minecraft.world.level.TickList;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import java.util.Collections;
import net.minecraft.world.entity.Entity;
import java.util.Collection;
import com.google.common.collect.Sets;
import java.util.Set;
import java.util.EnumSet;
import net.minecraft.world.level.BlockGetter;
import java.util.Iterator;
import java.util.stream.Stream;
import net.minecraft.world.level.material.FluidState;
import net.minecraft.world.level.block.Blocks;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.block.state.BlockState;
import it.unimi.dsi.fastutil.objects.Object2ObjectArrayMap;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import net.minecraft.world.level.material.Fluids;
import java.util.BitSet;
import net.minecraft.world.level.levelgen.GenerationStep;
import net.minecraft.world.level.material.Fluid;
import net.minecraft.world.level.block.Block;
import it.unimi.dsi.fastutil.longs.LongSet;
import net.minecraft.world.level.levelgen.structure.StructureStart;
import net.minecraft.world.level.levelgen.feature.StructureFeature;
import it.unimi.dsi.fastutil.shorts.ShortList;
import java.util.List;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.core.BlockPos;
import net.minecraft.world.level.levelgen.Heightmap;
import java.util.Map;
import net.minecraft.world.level.lighting.LevelLightEngine;
import javax.annotation.Nullable;
import net.minecraft.world.level.ChunkPos;
import org.apache.logging.log4j.Logger;

public class ProtoChunk implements ChunkAccess {
    private static final Logger LOGGER;
    private final ChunkPos chunkPos;
    private volatile boolean isDirty;
    @Nullable
    private ChunkBiomeContainer biomes;
    @Nullable
    private volatile LevelLightEngine lightEngine;
    private final Map<Heightmap.Types, Heightmap> heightmaps;
    private volatile ChunkStatus status;
    private final Map<BlockPos, BlockEntity> blockEntities;
    private final Map<BlockPos, CompoundTag> blockEntityNbts;
    private final LevelChunkSection[] sections;
    private final List<CompoundTag> entities;
    private final List<BlockPos> lights;
    private final ShortList[] postProcessing;
    private final Map<StructureFeature<?>, StructureStart<?>> structureStarts;
    private final Map<StructureFeature<?>, LongSet> structuresRefences;
    private final UpgradeData upgradeData;
    private final ProtoTickList<Block> blockTicks;
    private final ProtoTickList<Fluid> liquidTicks;
    private long inhabitedTime;
    private final Map<GenerationStep.Carving, BitSet> carvingMasks;
    private volatile boolean isLightCorrect;
    
    public ProtoChunk(final ChunkPos bph, final UpgradeData cht) {
        this(bph, cht, null, new ProtoTickList<Block>(bvr -> bvr == null || bvr.defaultBlockState().isAir(), bph), new ProtoTickList<Fluid>(cwz -> cwz == null || cwz == Fluids.EMPTY, bph));
    }
    
    public ProtoChunk(final ChunkPos bph, final UpgradeData cht, @Nullable final LevelChunkSection[] arr, final ProtoTickList<Block> chs4, final ProtoTickList<Fluid> chs5) {
        this.heightmaps = Maps.newEnumMap(Heightmap.Types.class);
        this.status = ChunkStatus.EMPTY;
        this.blockEntities = Maps.newHashMap();
        this.blockEntityNbts = Maps.newHashMap();
        this.sections = new LevelChunkSection[16];
        this.entities = Lists.newArrayList();
        this.lights = Lists.newArrayList();
        this.postProcessing = new ShortList[16];
        this.structureStarts = Maps.newHashMap();
        this.structuresRefences = Maps.newHashMap();
        this.carvingMasks = (Map<GenerationStep.Carving, BitSet>)new Object2ObjectArrayMap();
        this.chunkPos = bph;
        this.upgradeData = cht;
        this.blockTicks = chs4;
        this.liquidTicks = chs5;
        if (arr != null) {
            if (this.sections.length == arr.length) {
                System.arraycopy(arr, 0, this.sections, 0, this.sections.length);
            }
            else {
                ProtoChunk.LOGGER.warn("Could not set level chunk sections, array length is {} instead of {}", arr.length, this.sections.length);
            }
        }
    }
    
    @Override
    public BlockState getBlockState(final BlockPos fu) {
        final int integer3 = fu.getY();
        if (Level.isOutsideBuildHeight(integer3)) {
            return Blocks.VOID_AIR.defaultBlockState();
        }
        final LevelChunkSection chk4 = this.getSections()[integer3 >> 4];
        if (LevelChunkSection.isEmpty(chk4)) {
            return Blocks.AIR.defaultBlockState();
        }
        return chk4.getBlockState(fu.getX() & 0xF, integer3 & 0xF, fu.getZ() & 0xF);
    }
    
    @Override
    public FluidState getFluidState(final BlockPos fu) {
        final int integer3 = fu.getY();
        if (Level.isOutsideBuildHeight(integer3)) {
            return Fluids.EMPTY.defaultFluidState();
        }
        final LevelChunkSection chk4 = this.getSections()[integer3 >> 4];
        if (LevelChunkSection.isEmpty(chk4)) {
            return Fluids.EMPTY.defaultFluidState();
        }
        return chk4.getFluidState(fu.getX() & 0xF, integer3 & 0xF, fu.getZ() & 0xF);
    }
    
    @Override
    public Stream<BlockPos> getLights() {
        return this.lights.stream();
    }
    
    public ShortList[] getPackedLights() {
        final ShortList[] arr2 = new ShortList[16];
        for (final BlockPos fu4 : this.lights) {
            ChunkAccess.getOrCreateOffsetList(arr2, fu4.getY() >> 4).add(packOffsetCoordinates(fu4));
        }
        return arr2;
    }
    
    public void addLight(final short short1, final int integer) {
        this.addLight(unpackOffsetCoordinates(short1, integer, this.chunkPos));
    }
    
    public void addLight(final BlockPos fu) {
        this.lights.add(fu.immutable());
    }
    
    @Nullable
    @Override
    public BlockState setBlockState(final BlockPos fu, final BlockState cfj, final boolean boolean3) {
        final int integer5 = fu.getX();
        final int integer6 = fu.getY();
        final int integer7 = fu.getZ();
        if (integer6 < 0 || integer6 >= 256) {
            return Blocks.VOID_AIR.defaultBlockState();
        }
        if (this.sections[integer6 >> 4] == LevelChunk.EMPTY_SECTION && cfj.is(Blocks.AIR)) {
            return cfj;
        }
        if (cfj.getLightEmission() > 0) {
            this.lights.add(new BlockPos((integer5 & 0xF) + this.getPos().getMinBlockX(), integer6, (integer7 & 0xF) + this.getPos().getMinBlockZ()));
        }
        final LevelChunkSection chk8 = this.getOrCreateSection(integer6 >> 4);
        final BlockState cfj2 = chk8.setBlockState(integer5 & 0xF, integer6 & 0xF, integer7 & 0xF, cfj);
        if (this.status.isOrAfter(ChunkStatus.FEATURES) && cfj != cfj2 && (cfj.getLightBlock(this, fu) != cfj2.getLightBlock(this, fu) || cfj.getLightEmission() != cfj2.getLightEmission() || cfj.useShapeForLightOcclusion() || cfj2.useShapeForLightOcclusion())) {
            final LevelLightEngine cwr10 = this.getLightEngine();
            cwr10.checkBlock(fu);
        }
        final EnumSet<Heightmap.Types> enumSet10 = this.getStatus().heightmapsAfter();
        EnumSet<Heightmap.Types> enumSet11 = null;
        for (final Heightmap.Types a13 : enumSet10) {
            final Heightmap cio14 = this.heightmaps.get(a13);
            if (cio14 == null) {
                if (enumSet11 == null) {
                    enumSet11 = EnumSet.<Heightmap.Types>noneOf(Heightmap.Types.class);
                }
                enumSet11.add(a13);
            }
        }
        if (enumSet11 != null) {
            Heightmap.primeHeightmaps(this, enumSet11);
        }
        for (final Heightmap.Types a13 : enumSet10) {
            this.heightmaps.get(a13).update(integer5 & 0xF, integer6, integer7 & 0xF, cfj);
        }
        return cfj2;
    }
    
    public LevelChunkSection getOrCreateSection(final int integer) {
        if (this.sections[integer] == LevelChunk.EMPTY_SECTION) {
            this.sections[integer] = new LevelChunkSection(integer << 4);
        }
        return this.sections[integer];
    }
    
    @Override
    public void setBlockEntity(final BlockPos fu, final BlockEntity cdl) {
        cdl.setPosition(fu);
        this.blockEntities.put(fu, cdl);
    }
    
    @Override
    public Set<BlockPos> getBlockEntitiesPos() {
        final Set<BlockPos> set2 = Sets.newHashSet(this.blockEntityNbts.keySet());
        set2.addAll(this.blockEntities.keySet());
        return set2;
    }
    
    @Nullable
    @Override
    public BlockEntity getBlockEntity(final BlockPos fu) {
        return this.blockEntities.get(fu);
    }
    
    public Map<BlockPos, BlockEntity> getBlockEntities() {
        return this.blockEntities;
    }
    
    public void addEntity(final CompoundTag le) {
        this.entities.add(le);
    }
    
    @Override
    public void addEntity(final Entity aom) {
        if (aom.isPassenger()) {
            return;
        }
        final CompoundTag le3 = new CompoundTag();
        aom.save(le3);
        this.addEntity(le3);
    }
    
    public List<CompoundTag> getEntities() {
        return this.entities;
    }
    
    public void setBiomes(final ChunkBiomeContainer cgz) {
        this.biomes = cgz;
    }
    
    @Nullable
    @Override
    public ChunkBiomeContainer getBiomes() {
        return this.biomes;
    }
    
    @Override
    public void setUnsaved(final boolean boolean1) {
        this.isDirty = boolean1;
    }
    
    @Override
    public boolean isUnsaved() {
        return this.isDirty;
    }
    
    @Override
    public ChunkStatus getStatus() {
        return this.status;
    }
    
    public void setStatus(final ChunkStatus chc) {
        this.status = chc;
        this.setUnsaved(true);
    }
    
    @Override
    public LevelChunkSection[] getSections() {
        return this.sections;
    }
    
    @Nullable
    public LevelLightEngine getLightEngine() {
        return this.lightEngine;
    }
    
    @Override
    public Collection<Map.Entry<Heightmap.Types, Heightmap>> getHeightmaps() {
        return Collections.unmodifiableSet(this.heightmaps.entrySet());
    }
    
    @Override
    public void setHeightmap(final Heightmap.Types a, final long[] arr) {
        this.getOrCreateHeightmapUnprimed(a).setRawData(arr);
    }
    
    @Override
    public Heightmap getOrCreateHeightmapUnprimed(final Heightmap.Types a) {
        return this.heightmaps.computeIfAbsent(a, a -> new Heightmap(this, a));
    }
    
    @Override
    public int getHeight(final Heightmap.Types a, final int integer2, final int integer3) {
        Heightmap cio5 = this.heightmaps.get(a);
        if (cio5 == null) {
            Heightmap.primeHeightmaps(this, EnumSet.<Heightmap.Types>of(a));
            cio5 = this.heightmaps.get(a);
        }
        return cio5.getFirstAvailable(integer2 & 0xF, integer3 & 0xF) - 1;
    }
    
    @Override
    public ChunkPos getPos() {
        return this.chunkPos;
    }
    
    @Override
    public void setLastSaveTime(final long long1) {
    }
    
    @Nullable
    @Override
    public StructureStart<?> getStartForFeature(final StructureFeature<?> cml) {
        return this.structureStarts.get(cml);
    }
    
    @Override
    public void setStartForFeature(final StructureFeature<?> cml, final StructureStart<?> ctz) {
        this.structureStarts.put(cml, ctz);
        this.isDirty = true;
    }
    
    @Override
    public Map<StructureFeature<?>, StructureStart<?>> getAllStarts() {
        return Collections.<StructureFeature<?>, StructureStart<?>>unmodifiableMap(this.structureStarts);
    }
    
    @Override
    public void setAllStarts(final Map<StructureFeature<?>, StructureStart<?>> map) {
        this.structureStarts.clear();
        this.structureStarts.putAll(map);
        this.isDirty = true;
    }
    
    @Override
    public LongSet getReferencesForFeature(final StructureFeature<?> cml) {
        return this.structuresRefences.computeIfAbsent(cml, cml -> new LongOpenHashSet());
    }
    
    @Override
    public void addReferenceForFeature(final StructureFeature<?> cml, final long long2) {
        this.structuresRefences.computeIfAbsent(cml, cml -> new LongOpenHashSet()).add(long2);
        this.isDirty = true;
    }
    
    @Override
    public Map<StructureFeature<?>, LongSet> getAllReferences() {
        return Collections.<StructureFeature<?>, LongSet>unmodifiableMap(this.structuresRefences);
    }
    
    @Override
    public void setAllReferences(final Map<StructureFeature<?>, LongSet> map) {
        this.structuresRefences.clear();
        this.structuresRefences.putAll(map);
        this.isDirty = true;
    }
    
    public static short packOffsetCoordinates(final BlockPos fu) {
        final int integer2 = fu.getX();
        final int integer3 = fu.getY();
        final int integer4 = fu.getZ();
        final int integer5 = integer2 & 0xF;
        final int integer6 = integer3 & 0xF;
        final int integer7 = integer4 & 0xF;
        return (short)(integer5 | integer6 << 4 | integer7 << 8);
    }
    
    public static BlockPos unpackOffsetCoordinates(final short short1, final int integer, final ChunkPos bph) {
        final int integer2 = (short1 & 0xF) + (bph.x << 4);
        final int integer3 = (short1 >>> 4 & 0xF) + (integer << 4);
        final int integer4 = (short1 >>> 8 & 0xF) + (bph.z << 4);
        return new BlockPos(integer2, integer3, integer4);
    }
    
    @Override
    public void markPosForPostprocessing(final BlockPos fu) {
        if (!Level.isOutsideBuildHeight(fu)) {
            ChunkAccess.getOrCreateOffsetList(this.postProcessing, fu.getY() >> 4).add(packOffsetCoordinates(fu));
        }
    }
    
    @Override
    public ShortList[] getPostProcessing() {
        return this.postProcessing;
    }
    
    @Override
    public void addPackedPostProcess(final short short1, final int integer) {
        ChunkAccess.getOrCreateOffsetList(this.postProcessing, integer).add(short1);
    }
    
    @Override
    public ProtoTickList<Block> getBlockTicks() {
        return this.blockTicks;
    }
    
    @Override
    public ProtoTickList<Fluid> getLiquidTicks() {
        return this.liquidTicks;
    }
    
    @Override
    public UpgradeData getUpgradeData() {
        return this.upgradeData;
    }
    
    @Override
    public void setInhabitedTime(final long long1) {
        this.inhabitedTime = long1;
    }
    
    @Override
    public long getInhabitedTime() {
        return this.inhabitedTime;
    }
    
    @Override
    public void setBlockEntityNbt(final CompoundTag le) {
        this.blockEntityNbts.put(new BlockPos(le.getInt("x"), le.getInt("y"), le.getInt("z")), le);
    }
    
    public Map<BlockPos, CompoundTag> getBlockEntityNbts() {
        return Collections.<BlockPos, CompoundTag>unmodifiableMap(this.blockEntityNbts);
    }
    
    @Override
    public CompoundTag getBlockEntityNbt(final BlockPos fu) {
        return this.blockEntityNbts.get(fu);
    }
    
    @Nullable
    @Override
    public CompoundTag getBlockEntityNbtForSaving(final BlockPos fu) {
        final BlockEntity cdl3 = this.getBlockEntity(fu);
        if (cdl3 != null) {
            return cdl3.save(new CompoundTag());
        }
        return this.blockEntityNbts.get(fu);
    }
    
    @Override
    public void removeBlockEntity(final BlockPos fu) {
        this.blockEntities.remove(fu);
        this.blockEntityNbts.remove(fu);
    }
    
    @Nullable
    public BitSet getCarvingMask(final GenerationStep.Carving a) {
        return this.carvingMasks.get(a);
    }
    
    public BitSet getOrCreateCarvingMask(final GenerationStep.Carving a) {
        return this.carvingMasks.computeIfAbsent(a, a -> new BitSet(65536));
    }
    
    public void setCarvingMask(final GenerationStep.Carving a, final BitSet bitSet) {
        this.carvingMasks.put(a, bitSet);
    }
    
    public void setLightEngine(final LevelLightEngine cwr) {
        this.lightEngine = cwr;
    }
    
    @Override
    public boolean isLightCorrect() {
        return this.isLightCorrect;
    }
    
    @Override
    public void setLightCorrect(final boolean boolean1) {
        this.isLightCorrect = boolean1;
        this.setUnsaved(true);
    }
    
    static {
        LOGGER = LogManager.getLogger();
    }
}
