package net.minecraft.network.protocol.game;

import net.minecraft.network.PacketListener;
import net.minecraft.world.level.chunk.LevelChunkSection;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import java.io.IOException;
import java.util.Iterator;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.block.entity.BlockEntity;
import net.minecraft.core.BlockPos;
import com.google.common.collect.Lists;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.nbt.Tag;
import net.minecraft.nbt.LongArrayTag;
import net.minecraft.world.level.levelgen.Heightmap;
import java.util.Map;
import net.minecraft.world.level.chunk.LevelChunk;
import java.util.List;
import javax.annotation.Nullable;
import net.minecraft.world.level.chunk.ChunkBiomeContainer;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.protocol.Packet;

public class ClientboundLevelChunkPacket implements Packet<ClientGamePacketListener> {
    private int x;
    private int z;
    private int availableSections;
    private CompoundTag heightmaps;
    @Nullable
    private ChunkBiomeContainer biomes;
    private byte[] buffer;
    private List<CompoundTag> blockEntitiesTags;
    private boolean fullChunk;
    private boolean forgetOldData;
    
    public ClientboundLevelChunkPacket() {
    }
    
    public ClientboundLevelChunkPacket(final LevelChunk chj, final int integer, final boolean boolean3) {
        final ChunkPos bph5 = chj.getPos();
        this.x = bph5.x;
        this.z = bph5.z;
        this.fullChunk = (integer == 65535);
        this.forgetOldData = boolean3;
        this.heightmaps = new CompoundTag();
        for (final Map.Entry<Heightmap.Types, Heightmap> entry7 : chj.getHeightmaps()) {
            if (!entry7.getKey().sendToClient()) {
                continue;
            }
            this.heightmaps.put(entry7.getKey().getSerializationKey(), new LongArrayTag(entry7.getValue().getRawData()));
        }
        if (this.fullChunk) {
            this.biomes = chj.getBiomes().copy();
        }
        this.buffer = new byte[this.calculateChunkSize(chj, integer)];
        this.availableSections = this.extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), chj, integer);
        this.blockEntitiesTags = Lists.newArrayList();
        for (final Map.Entry<BlockPos, BlockEntity> entry8 : chj.getBlockEntities().entrySet()) {
            final BlockPos fu8 = entry8.getKey();
            final BlockEntity cdl9 = entry8.getValue();
            final int integer2 = fu8.getY() >> 4;
            if (!this.isFullChunk() && (integer & 1 << integer2) == 0x0) {
                continue;
            }
            final CompoundTag le11 = cdl9.getUpdateTag();
            this.blockEntitiesTags.add(le11);
        }
    }
    
    @Override
    public void read(final FriendlyByteBuf mg) throws IOException {
        this.x = mg.readInt();
        this.z = mg.readInt();
        this.fullChunk = mg.readBoolean();
        this.forgetOldData = mg.readBoolean();
        this.availableSections = mg.readVarInt();
        this.heightmaps = mg.readNbt();
        if (this.fullChunk) {
            this.biomes = new ChunkBiomeContainer(mg);
        }
        final int integer3 = mg.readVarInt();
        if (integer3 > 2097152) {
            throw new RuntimeException("Chunk Packet trying to allocate too much memory on read.");
        }
        mg.readBytes(this.buffer = new byte[integer3]);
        final int integer4 = mg.readVarInt();
        this.blockEntitiesTags = Lists.newArrayList();
        for (int integer5 = 0; integer5 < integer4; ++integer5) {
            this.blockEntitiesTags.add(mg.readNbt());
        }
    }
    
    @Override
    public void write(final FriendlyByteBuf mg) throws IOException {
        mg.writeInt(this.x);
        mg.writeInt(this.z);
        mg.writeBoolean(this.fullChunk);
        mg.writeBoolean(this.forgetOldData);
        mg.writeVarInt(this.availableSections);
        mg.writeNbt(this.heightmaps);
        if (this.biomes != null) {
            this.biomes.write(mg);
        }
        mg.writeVarInt(this.buffer.length);
        mg.writeBytes(this.buffer);
        mg.writeVarInt(this.blockEntitiesTags.size());
        for (final CompoundTag le4 : this.blockEntitiesTags) {
            mg.writeNbt(le4);
        }
    }
    
    @Override
    public void handle(final ClientGamePacketListener nl) {
        nl.handleLevelChunk(this);
    }
    
    public FriendlyByteBuf getReadBuffer() {
        return new FriendlyByteBuf(Unpooled.wrappedBuffer(this.buffer));
    }
    
    private ByteBuf getWriteBuffer() {
        final ByteBuf byteBuf2 = Unpooled.wrappedBuffer(this.buffer);
        byteBuf2.writerIndex(0);
        return byteBuf2;
    }
    
    public int extractChunkData(final FriendlyByteBuf mg, final LevelChunk chj, final int integer) {
        int integer2 = 0;
        final LevelChunkSection[] arr6 = chj.getSections();
        for (int integer3 = 0, integer4 = arr6.length; integer3 < integer4; ++integer3) {
            final LevelChunkSection chk9 = arr6[integer3];
            if (chk9 != LevelChunk.EMPTY_SECTION && (!this.isFullChunk() || !chk9.isEmpty())) {
                if ((integer & 1 << integer3) != 0x0) {
                    integer2 |= 1 << integer3;
                    chk9.write(mg);
                }
            }
        }
        return integer2;
    }
    
    protected int calculateChunkSize(final LevelChunk chj, final int integer) {
        int integer2 = 0;
        final LevelChunkSection[] arr5 = chj.getSections();
        for (int integer3 = 0, integer4 = arr5.length; integer3 < integer4; ++integer3) {
            final LevelChunkSection chk8 = arr5[integer3];
            if (chk8 != LevelChunk.EMPTY_SECTION && (!this.isFullChunk() || !chk8.isEmpty())) {
                if ((integer & 1 << integer3) != 0x0) {
                    integer2 += chk8.getSerializedSize();
                }
            }
        }
        return integer2;
    }
    
    public int getX() {
        return this.x;
    }
    
    public int getZ() {
        return this.z;
    }
    
    public int getAvailableSections() {
        return this.availableSections;
    }
    
    public boolean isFullChunk() {
        return this.fullChunk;
    }
    
    public boolean forgetOldData() {
        return this.forgetOldData;
    }
    
    public CompoundTag getHeightmaps() {
        return this.heightmaps;
    }
    
    public List<CompoundTag> getBlockEntitiesTags() {
        return this.blockEntitiesTags;
    }
    
    @Nullable
    public ChunkBiomeContainer getBiomes() {
        return (this.biomes == null) ? null : this.biomes.copy();
    }
}
