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 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 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<String, StructureStart> structureStarts;
    private final Map<String, 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 bjh, final UpgradeData cav) {
        this(bjh, cav, null, new ProtoTickList<Block>(bph -> bph == null || bph.defaultBlockState().isAir(), bjh), new ProtoTickList<Fluid>(coi -> coi == null || coi == Fluids.EMPTY, bjh));
    }
    
    public ProtoChunk(final ChunkPos bjh, final UpgradeData cav, @Nullable final LevelChunkSection[] arr, final ProtoTickList<Block> cau4, final ProtoTickList<Fluid> cau5) {
        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 = Maps.newHashMap();
        this.chunkPos = bjh;
        this.upgradeData = cav;
        this.blockTicks = cau4;
        this.liquidTicks = cau5;
        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 fk) {
        final int integer3 = fk.getY();
        if (Level.isOutsideBuildHeight(integer3)) {
            return Blocks.VOID_AIR.defaultBlockState();
        }
        final LevelChunkSection cam4 = this.getSections()[integer3 >> 4];
        if (LevelChunkSection.isEmpty(cam4)) {
            return Blocks.AIR.defaultBlockState();
        }
        return cam4.getBlockState(fk.getX() & 0xF, integer3 & 0xF, fk.getZ() & 0xF);
    }
    
    @Override
    public FluidState getFluidState(final BlockPos fk) {
        final int integer3 = fk.getY();
        if (Level.isOutsideBuildHeight(integer3)) {
            return Fluids.EMPTY.defaultFluidState();
        }
        final LevelChunkSection cam4 = this.getSections()[integer3 >> 4];
        if (LevelChunkSection.isEmpty(cam4)) {
            return Fluids.EMPTY.defaultFluidState();
        }
        return cam4.getFluidState(fk.getX() & 0xF, integer3 & 0xF, fk.getZ() & 0xF);
    }
    
    @Override
    public Stream<BlockPos> getLights() {
        return this.lights.stream();
    }
    
    public ShortList[] getPackedLights() {
        final ShortList[] arr2 = new ShortList[16];
        for (final BlockPos fk4 : this.lights) {
            ChunkAccess.getOrCreateOffsetList(arr2, fk4.getY() >> 4).add(packOffsetCoordinates(fk4));
        }
        return arr2;
    }
    
    public void addLight(final short short1, final int integer) {
        this.addLight(unpackOffsetCoordinates(short1, integer, this.chunkPos));
    }
    
    public void addLight(final BlockPos fk) {
        this.lights.add(fk.immutable());
    }
    
    @Nullable
    @Override
    public BlockState setBlockState(final BlockPos fk, final BlockState byj, final boolean boolean3) {
        final int integer5 = fk.getX();
        final int integer6 = fk.getY();
        final int integer7 = fk.getZ();
        if (integer6 < 0 || integer6 >= 256) {
            return Blocks.VOID_AIR.defaultBlockState();
        }
        if (this.sections[integer6 >> 4] == LevelChunk.EMPTY_SECTION && byj.getBlock() == Blocks.AIR) {
            return byj;
        }
        if (byj.getLightEmission() > 0) {
            this.lights.add(new BlockPos((integer5 & 0xF) + this.getPos().getMinBlockX(), integer6, (integer7 & 0xF) + this.getPos().getMinBlockZ()));
        }
        final LevelChunkSection cam8 = this.getOrCreateSection(integer6 >> 4);
        final BlockState byj2 = cam8.setBlockState(integer5 & 0xF, integer6 & 0xF, integer7 & 0xF, byj);
        if (this.status.isOrAfter(ChunkStatus.FEATURES) && byj != byj2 && (byj.getLightBlock(this, fk) != byj2.getLightBlock(this, fk) || byj.getLightEmission() != byj2.getLightEmission() || byj.useShapeForLightOcclusion() || byj2.useShapeForLightOcclusion())) {
            final LevelLightEngine coa10 = this.getLightEngine();
            coa10.checkBlock(fk);
        }
        final EnumSet<Heightmap.Types> enumSet10 = this.getStatus().heightmapsAfter();
        EnumSet<Heightmap.Types> enumSet11 = null;
        for (final Heightmap.Types a13 : enumSet10) {
            final Heightmap cbv14 = this.heightmaps.get(a13);
            if (cbv14 == 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, byj);
        }
        return byj2;
    }
    
    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 fk, final BlockEntity bwl) {
        bwl.setPosition(fk);
        this.blockEntities.put(fk, bwl);
    }
    
    @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 fk) {
        return this.blockEntities.get(fk);
    }
    
    public Map<BlockPos, BlockEntity> getBlockEntities() {
        return this.blockEntities;
    }
    
    public void addEntity(final CompoundTag jt) {
        this.entities.add(jt);
    }
    
    @Override
    public void addEntity(final Entity akq) {
        final CompoundTag jt3 = new CompoundTag();
        akq.save(jt3);
        this.addEntity(jt3);
    }
    
    public List<CompoundTag> getEntities() {
        return this.entities;
    }
    
    public void setBiomes(final ChunkBiomeContainer bzz) {
        this.biomes = bzz;
    }
    
    @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 cae) {
        this.status = cae;
        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 cbv5 = this.heightmaps.get(a);
        if (cbv5 == null) {
            Heightmap.primeHeightmaps(this, EnumSet.<Heightmap.Types>of(a));
            cbv5 = this.heightmaps.get(a);
        }
        return cbv5.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 String string) {
        return this.structureStarts.get(string);
    }
    
    @Override
    public void setStartForFeature(final String string, final StructureStart clv) {
        this.structureStarts.put(string, clv);
        this.isDirty = true;
    }
    
    @Override
    public Map<String, StructureStart> getAllStarts() {
        return Collections.<String, StructureStart>unmodifiableMap(this.structureStarts);
    }
    
    @Override
    public void setAllStarts(final Map<String, StructureStart> map) {
        this.structureStarts.clear();
        this.structureStarts.putAll(map);
        this.isDirty = true;
    }
    
    @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);
        this.isDirty = true;
    }
    
    @Override
    public Map<String, LongSet> getAllReferences() {
        return Collections.<String, LongSet>unmodifiableMap(this.structuresRefences);
    }
    
    @Override
    public void setAllReferences(final Map<String, LongSet> map) {
        this.structuresRefences.clear();
        this.structuresRefences.putAll(map);
        this.isDirty = true;
    }
    
    public static short packOffsetCoordinates(final BlockPos fk) {
        final int integer2 = fk.getX();
        final int integer3 = fk.getY();
        final int integer4 = fk.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 bjh) {
        final int integer2 = (short1 & 0xF) + (bjh.x << 4);
        final int integer3 = (short1 >>> 4 & 0xF) + (integer << 4);
        final int integer4 = (short1 >>> 8 & 0xF) + (bjh.z << 4);
        return new BlockPos(integer2, integer3, integer4);
    }
    
    @Override
    public void markPosForPostprocessing(final BlockPos fk) {
        if (!Level.isOutsideBuildHeight(fk)) {
            ChunkAccess.getOrCreateOffsetList(this.postProcessing, fk.getY() >> 4).add(packOffsetCoordinates(fk));
        }
    }
    
    @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 jt) {
        this.blockEntityNbts.put(new BlockPos(jt.getInt("x"), jt.getInt("y"), jt.getInt("z")), jt);
    }
    
    public Map<BlockPos, CompoundTag> getBlockEntityNbts() {
        return Collections.<BlockPos, CompoundTag>unmodifiableMap(this.blockEntityNbts);
    }
    
    @Override
    public CompoundTag getBlockEntityNbt(final BlockPos fk) {
        return this.blockEntityNbts.get(fk);
    }
    
    @Nullable
    @Override
    public CompoundTag getBlockEntityNbtForSaving(final BlockPos fk) {
        final BlockEntity bwl3 = this.getBlockEntity(fk);
        if (bwl3 != null) {
            return bwl3.save(new CompoundTag());
        }
        return this.blockEntityNbts.get(fk);
    }
    
    @Override
    public void removeBlockEntity(final BlockPos fk) {
        this.blockEntities.remove(fk);
        this.blockEntityNbts.remove(fk);
    }
    
    @Override
    public BitSet getCarvingMask(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 coa) {
        this.lightEngine = coa;
    }
    
    @Override
    public boolean isLightCorrect() {
        return this.isLightCorrect;
    }
    
    @Override
    public void setLightCorrect(final boolean boolean1) {
        this.isLightCorrect = boolean1;
        this.setUnsaved(true);
    }
    
    static {
        LOGGER = LogManager.getLogger();
    }
}
