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;
    
    public ClientboundLevelChunkPacket() {
    }
    
    public ClientboundLevelChunkPacket(final LevelChunk cal, final int integer) {
        final ChunkPos bjh4 = cal.getPos();
        this.x = bjh4.x;
        this.z = bjh4.z;
        this.fullChunk = (integer == 65535);
        this.heightmaps = new CompoundTag();
        for (final Map.Entry<Heightmap.Types, Heightmap> entry6 : cal.getHeightmaps()) {
            if (!entry6.getKey().sendToClient()) {
                continue;
            }
            this.heightmaps.put(entry6.getKey().getSerializationKey(), new LongArrayTag(entry6.getValue().getRawData()));
        }
        if (this.fullChunk) {
            this.biomes = cal.getBiomes().copy();
        }
        this.buffer = new byte[this.calculateChunkSize(cal, integer)];
        this.availableSections = this.extractChunkData(new FriendlyByteBuf(this.getWriteBuffer()), cal, integer);
        this.blockEntitiesTags = Lists.newArrayList();
        for (final Map.Entry<BlockPos, BlockEntity> entry7 : cal.getBlockEntities().entrySet()) {
            final BlockPos fk7 = entry7.getKey();
            final BlockEntity bwl8 = entry7.getValue();
            final int integer2 = fk7.getY() >> 4;
            if (!this.isFullChunk() && (integer & 1 << integer2) == 0x0) {
                continue;
            }
            final CompoundTag jt10 = bwl8.getUpdateTag();
            this.blockEntitiesTags.add(jt10);
        }
    }
    
    @Override
    public void read(final FriendlyByteBuf kv) throws IOException {
        this.x = kv.readInt();
        this.z = kv.readInt();
        this.fullChunk = kv.readBoolean();
        this.availableSections = kv.readVarInt();
        this.heightmaps = kv.readNbt();
        if (this.fullChunk) {
            this.biomes = new ChunkBiomeContainer(kv);
        }
        final int integer3 = kv.readVarInt();
        if (integer3 > 2097152) {
            throw new RuntimeException("Chunk Packet trying to allocate too much memory on read.");
        }
        kv.readBytes(this.buffer = new byte[integer3]);
        final int integer4 = kv.readVarInt();
        this.blockEntitiesTags = Lists.newArrayList();
        for (int integer5 = 0; integer5 < integer4; ++integer5) {
            this.blockEntitiesTags.add(kv.readNbt());
        }
    }
    
    @Override
    public void write(final FriendlyByteBuf kv) throws IOException {
        kv.writeInt(this.x);
        kv.writeInt(this.z);
        kv.writeBoolean(this.fullChunk);
        kv.writeVarInt(this.availableSections);
        kv.writeNbt(this.heightmaps);
        if (this.biomes != null) {
            this.biomes.write(kv);
        }
        kv.writeVarInt(this.buffer.length);
        kv.writeBytes(this.buffer);
        kv.writeVarInt(this.blockEntitiesTags.size());
        for (final CompoundTag jt4 : this.blockEntitiesTags) {
            kv.writeNbt(jt4);
        }
    }
    
    @Override
    public void handle(final ClientGamePacketListener lw) {
        lw.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 kv, final LevelChunk cal, final int integer) {
        int integer2 = 0;
        final LevelChunkSection[] arr6 = cal.getSections();
        for (int integer3 = 0, integer4 = arr6.length; integer3 < integer4; ++integer3) {
            final LevelChunkSection cam9 = arr6[integer3];
            if (cam9 != LevelChunk.EMPTY_SECTION && (!this.isFullChunk() || !cam9.isEmpty())) {
                if ((integer & 1 << integer3) != 0x0) {
                    integer2 |= 1 << integer3;
                    cam9.write(kv);
                }
            }
        }
        return integer2;
    }
    
    protected int calculateChunkSize(final LevelChunk cal, final int integer) {
        int integer2 = 0;
        final LevelChunkSection[] arr5 = cal.getSections();
        for (int integer3 = 0, integer4 = arr5.length; integer3 < integer4; ++integer3) {
            final LevelChunkSection cam8 = arr5[integer3];
            if (cam8 != LevelChunk.EMPTY_SECTION && (!this.isFullChunk() || !cam8.isEmpty())) {
                if ((integer & 1 << integer3) != 0x0) {
                    integer2 += cam8.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 CompoundTag getHeightmaps() {
        return this.heightmaps;
    }
    
    public List<CompoundTag> getBlockEntitiesTags() {
        return this.blockEntitiesTags;
    }
    
    @Nullable
    public ChunkBiomeContainer getBiomes() {
        return (this.biomes == null) ? null : this.biomes.copy();
    }
}
