package net.minecraft.client.multiplayer;

import java.util.concurrent.atomic.AtomicReferenceArray;
import org.apache.logging.log4j.LogManager;
import net.minecraft.world.level.chunk.ChunkAccess;
import net.minecraft.util.Mth;
import net.minecraft.world.entity.Entity;
import net.minecraft.core.BlockPos;
import net.minecraft.client.Minecraft;
import net.minecraft.world.level.LightLayer;
import java.util.function.BooleanSupplier;
import net.minecraft.world.level.chunk.LevelChunkSection;
import net.minecraft.core.SectionPos;
import net.minecraft.nbt.CompoundTag;
import net.minecraft.network.FriendlyByteBuf;
import net.minecraft.world.level.chunk.ChunkBiomeContainer;
import net.minecraft.world.level.BlockGetter;
import net.minecraft.world.level.chunk.ChunkStatus;
import javax.annotation.Nullable;
import net.minecraft.world.level.chunk.LightChunkGetter;
import net.minecraft.world.level.Level;
import net.minecraft.world.level.chunk.EmptyLevelChunk;
import net.minecraft.world.level.ChunkPos;
import net.minecraft.world.level.lighting.LevelLightEngine;
import net.minecraft.world.level.chunk.LevelChunk;
import org.apache.logging.log4j.Logger;
import net.minecraft.world.level.chunk.ChunkSource;

public class ClientChunkCache extends ChunkSource {
    private static final Logger LOGGER;
    private final LevelChunk emptyChunk;
    private final LevelLightEngine lightEngine;
    private volatile Storage storage;
    private final ClientLevel level;
    
    public ClientChunkCache(final ClientLevel dno, final int integer) {
        this.level = dno;
        this.emptyChunk = new EmptyLevelChunk(dno, new ChunkPos(0, 0));
        this.lightEngine = new LevelLightEngine(this, true, dno.getDimension().isHasSkyLight());
        this.storage = new Storage(calculateStorageRange(integer));
    }
    
    @Override
    public LevelLightEngine getLightEngine() {
        return this.lightEngine;
    }
    
    private static boolean isValidChunk(@Nullable final LevelChunk cal, final int integer2, final int integer3) {
        if (cal == null) {
            return false;
        }
        final ChunkPos bjh4 = cal.getPos();
        return bjh4.x == integer2 && bjh4.z == integer3;
    }
    
    public void drop(final int integer1, final int integer2) {
        if (!this.storage.inRange(integer1, integer2)) {
            return;
        }
        final int integer3 = this.storage.getIndex(integer1, integer2);
        final LevelChunk cal5 = this.storage.getChunk(integer3);
        if (isValidChunk(cal5, integer1, integer2)) {
            this.storage.replace(integer3, cal5, null);
        }
    }
    
    @Nullable
    @Override
    public LevelChunk getChunk(final int integer1, final int integer2, final ChunkStatus cae, final boolean boolean4) {
        if (this.storage.inRange(integer1, integer2)) {
            final LevelChunk cal6 = this.storage.getChunk(this.storage.getIndex(integer1, integer2));
            if (isValidChunk(cal6, integer1, integer2)) {
                return cal6;
            }
        }
        if (boolean4) {
            return this.emptyChunk;
        }
        return null;
    }
    
    @Override
    public BlockGetter getLevel() {
        return this.level;
    }
    
    @Nullable
    public LevelChunk replaceWithPacketData(final int integer1, final int integer2, @Nullable final ChunkBiomeContainer bzz, final FriendlyByteBuf kv, final CompoundTag jt, final int integer6) {
        if (!this.storage.inRange(integer1, integer2)) {
            ClientChunkCache.LOGGER.warn("Ignoring chunk since it's not in the view range: {}, {}", integer1, integer2);
            return null;
        }
        final int integer7 = this.storage.getIndex(integer1, integer2);
        LevelChunk cal9 = this.storage.chunks.get(integer7);
        if (!isValidChunk(cal9, integer1, integer2)) {
            if (bzz == null) {
                ClientChunkCache.LOGGER.warn("Ignoring chunk since we don't have complete data: {}, {}", integer1, integer2);
                return null;
            }
            cal9 = new LevelChunk(this.level, new ChunkPos(integer1, integer2), bzz);
            cal9.replaceWithPacketData(bzz, kv, jt, integer6);
            this.storage.replace(integer7, cal9);
        }
        else {
            cal9.replaceWithPacketData(bzz, kv, jt, integer6);
        }
        final LevelChunkSection[] arr10 = cal9.getSections();
        final LevelLightEngine coa11 = this.getLightEngine();
        coa11.enableLightSources(new ChunkPos(integer1, integer2), true);
        for (int integer8 = 0; integer8 < arr10.length; ++integer8) {
            final LevelChunkSection cam13 = arr10[integer8];
            coa11.updateSectionStatus(SectionPos.of(integer1, integer8, integer2), LevelChunkSection.isEmpty(cam13));
        }
        this.level.onChunkLoaded(integer1, integer2);
        return cal9;
    }
    
    @Override
    public void tick(final BooleanSupplier booleanSupplier) {
    }
    
    public void updateViewCenter(final int integer1, final int integer2) {
        this.storage.viewCenterX = integer1;
        this.storage.viewCenterZ = integer2;
    }
    
    public void updateViewRadius(final int integer) {
        final int integer2 = this.storage.chunkRadius;
        final int integer3 = calculateStorageRange(integer);
        if (integer2 != integer3) {
            final Storage a5 = new Storage(integer3);
            a5.viewCenterX = this.storage.viewCenterX;
            a5.viewCenterZ = this.storage.viewCenterZ;
            for (int integer4 = 0; integer4 < this.storage.chunks.length(); ++integer4) {
                final LevelChunk cal7 = this.storage.chunks.get(integer4);
                if (cal7 != null) {
                    final ChunkPos bjh8 = cal7.getPos();
                    if (a5.inRange(bjh8.x, bjh8.z)) {
                        a5.replace(a5.getIndex(bjh8.x, bjh8.z), cal7);
                    }
                }
            }
            this.storage = a5;
        }
    }
    
    private static int calculateStorageRange(final int integer) {
        return Math.max(2, integer) + 3;
    }
    
    @Override
    public String gatherStats() {
        return "Client Chunk Cache: " + this.storage.chunks.length() + ", " + this.getLoadedChunksCount();
    }
    
    public int getLoadedChunksCount() {
        return this.storage.chunkCount;
    }
    
    @Override
    public void onLightUpdate(final LightLayer bkf, final SectionPos gd) {
        Minecraft.getInstance().levelRenderer.setSectionDirty(gd.x(), gd.y(), gd.z());
    }
    
    @Override
    public boolean isTickingChunk(final BlockPos fk) {
        return this.hasChunk(fk.getX() >> 4, fk.getZ() >> 4);
    }
    
    @Override
    public boolean isEntityTickingChunk(final ChunkPos bjh) {
        return this.hasChunk(bjh.x, bjh.z);
    }
    
    @Override
    public boolean isEntityTickingChunk(final Entity akq) {
        return this.hasChunk(Mth.floor(akq.getX()) >> 4, Mth.floor(akq.getZ()) >> 4);
    }
    
    static {
        LOGGER = LogManager.getLogger();
    }
    
    final class Storage {
        private final AtomicReferenceArray<LevelChunk> chunks;
        private final int chunkRadius;
        private final int viewRange;
        private volatile int viewCenterX;
        private volatile int viewCenterZ;
        private int chunkCount;
        
        private Storage(final int integer) {
            this.chunkRadius = integer;
            this.viewRange = integer * 2 + 1;
            this.chunks = new AtomicReferenceArray<LevelChunk>(this.viewRange * this.viewRange);
        }
        
        private int getIndex(final int integer1, final int integer2) {
            return Math.floorMod(integer2, this.viewRange) * this.viewRange + Math.floorMod(integer1, this.viewRange);
        }
        
        protected void replace(final int integer, @Nullable final LevelChunk cal) {
            final LevelChunk cal2 = this.chunks.getAndSet(integer, cal);
            if (cal2 != null) {
                --this.chunkCount;
                ClientChunkCache.this.level.unload(cal2);
            }
            if (cal != null) {
                ++this.chunkCount;
            }
        }
        
        protected LevelChunk replace(final int integer, final LevelChunk cal2, @Nullable final LevelChunk cal3) {
            if (this.chunks.compareAndSet(integer, cal2, cal3) && cal3 == null) {
                --this.chunkCount;
            }
            ClientChunkCache.this.level.unload(cal2);
            return cal2;
        }
        
        private boolean inRange(final int integer1, final int integer2) {
            return Math.abs(integer1 - this.viewCenterX) <= this.chunkRadius && Math.abs(integer2 - this.viewCenterZ) <= this.chunkRadius;
        }
        
        @Nullable
        protected LevelChunk getChunk(final int integer) {
            return this.chunks.get(integer);
        }
    }
}
