// 
// Decompiled by Procyon v0.6.0
// 

package com.hypixel.hytale.server.core.universe.world.chunk;

import com.hypixel.hytale.component.AddReason;
import com.hypixel.hytale.server.core.modules.block.BlockModule;
import com.hypixel.hytale.component.RemoveReason;
import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.server.core.blocktype.component.BlockPhysics;
import com.hypixel.hytale.server.core.asset.type.blocktick.config.TickProcedure;
import com.hypixel.hytale.server.core.universe.world.meta.BlockState;
import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap;
import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap;
import com.hypixel.hytale.server.core.universe.world.WorldNotificationHandler;
import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection;
import com.hypixel.hytale.common.util.CompletableFutureUtil;
import java.util.concurrent.Executor;
import java.util.concurrent.CompletableFuture;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.server.core.asset.type.blocktick.BlockTickManager;
import com.hypixel.hytale.server.core.util.FillerBlockUtil;
import com.hypixel.hytale.server.core.inventory.container.ItemContainer;
import com.hypixel.hytale.server.core.universe.world.meta.state.ItemContainerState;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.server.core.universe.world.meta.BlockStateModule;
import com.hypixel.hytale.server.core.asset.type.blockhitbox.BlockBoundingBoxes;
import com.hypixel.hytale.protocol.BlockParticleEvent;
import com.hypixel.hytale.protocol.Opacity;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.server.core.universe.world.accessor.ChunkAccessor;
import java.util.logging.Level;
import javax.annotation.Nonnull;
import com.hypixel.hytale.component.Archetype;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.server.core.universe.world.chunk.environment.EnvironmentChunk;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.server.core.modules.LegacyModule;
import com.hypixel.hytale.component.ComponentType;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.Nullable;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.common.collection.Flags;
import java.util.concurrent.locks.StampedLock;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.component.Component;
import com.hypixel.hytale.server.core.universe.world.accessor.BlockAccessor;

public class WorldChunk implements BlockAccessor, Component<ChunkStore>
{
    public static final int KEEP_ALIVE_DEFAULT = 15;
    public static final BuilderCodec<WorldChunk> CODEC;
    private static final HytaleLogger LOGGER;
    private World world;
    private final StampedLock flagsLock;
    private final Flags<ChunkFlag> flags;
    private Ref<ChunkStore> reference;
    @Nullable
    private BlockChunk blockChunk;
    @Nullable
    private BlockComponentChunk blockComponentChunk;
    @Nullable
    private EntityChunk entityChunk;
    private int keepAlive;
    private int activeTimer;
    private boolean needsSaving;
    private boolean isSaving;
    private final AtomicInteger keepLoaded;
    private boolean lightingUpdatesEnabled;
    @Deprecated
    public final AtomicLong chunkLightTiming;
    
    public static ComponentType<ChunkStore, WorldChunk> getComponentType() {
        return LegacyModule.get().getWorldChunkComponentType();
    }
    
    private WorldChunk() {
        this.flagsLock = new StampedLock();
        this.keepAlive = 15;
        this.activeTimer = 15;
        this.keepLoaded = new AtomicInteger();
        this.lightingUpdatesEnabled = true;
        this.chunkLightTiming = new AtomicLong();
        this.flags = new Flags<ChunkFlag>(new ChunkFlag[0]);
    }
    
    private WorldChunk(final World world, final Flags<ChunkFlag> flags) {
        this.flagsLock = new StampedLock();
        this.keepAlive = 15;
        this.activeTimer = 15;
        this.keepLoaded = new AtomicInteger();
        this.lightingUpdatesEnabled = true;
        this.chunkLightTiming = new AtomicLong();
        this.world = world;
        this.flags = flags;
    }
    
    public WorldChunk(final World world, final Flags<ChunkFlag> state, final BlockChunk blockChunk, final BlockComponentChunk blockComponentChunk, final EntityChunk entityChunk) {
        this(world, state);
        this.blockChunk = blockChunk;
        this.blockComponentChunk = blockComponentChunk;
        this.entityChunk = entityChunk;
    }
    
    @Nonnull
    public Holder<ChunkStore> toHolder() {
        if (this.reference != null && this.reference.isValid() && this.world != null) {
            final Holder<ChunkStore> holder = ChunkStore.REGISTRY.newHolder();
            final Store<ChunkStore> componentStore = this.world.getChunkStore().getStore();
            final Archetype<ChunkStore> archetype = componentStore.getArchetype(this.reference);
            for (int i = archetype.getMinIndex(); i < archetype.length(); ++i) {
                final ComponentType componentType = archetype.get(i);
                if (componentType != null) {
                    holder.addComponent(componentType, (Component)componentStore.getComponent(this.reference, (ComponentType<ChunkStore, T>)componentType));
                }
            }
            return holder;
        }
        final Holder<ChunkStore> holder = ChunkStore.REGISTRY.newHolder();
        holder.addComponent(getComponentType(), this);
        holder.addComponent(BlockChunk.getComponentType(), this.blockChunk);
        holder.addComponent(EnvironmentChunk.getComponentType(), this.blockChunk.getEnvironmentChunk());
        holder.addComponent(EntityChunk.getComponentType(), this.entityChunk);
        holder.addComponent(BlockComponentChunk.getComponentType(), this.blockComponentChunk);
        return holder;
    }
    
    @Deprecated
    public void setReference(final Ref<ChunkStore> reference) {
        if (this.reference != null && this.reference.isValid()) {
            throw new IllegalArgumentException("Chunk already has a valid EntityReference: " + String.valueOf(this.reference) + " new reference " + String.valueOf(reference));
        }
        this.reference = reference;
    }
    
    public Ref<ChunkStore> getReference() {
        return this.reference;
    }
    
    @Nonnull
    @Override
    public Component<ChunkStore> clone() {
        return new WorldChunk();
    }
    
    public boolean is(@Nonnull final ChunkFlag flag) {
        long stamp = this.flagsLock.tryOptimisticRead();
        final boolean value = this.flags.is(flag);
        if (this.flagsLock.validate(stamp)) {
            return value;
        }
        stamp = this.flagsLock.readLock();
        try {
            return this.flags.is(flag);
        }
        finally {
            this.flagsLock.unlockRead(stamp);
        }
    }
    
    public boolean not(@Nonnull final ChunkFlag flag) {
        long stamp = this.flagsLock.tryOptimisticRead();
        final boolean value = this.flags.not(flag);
        if (this.flagsLock.validate(stamp)) {
            return value;
        }
        stamp = this.flagsLock.readLock();
        try {
            return this.flags.not(flag);
        }
        finally {
            this.flagsLock.unlockRead(stamp);
        }
    }
    
    public void setFlag(@Nonnull final ChunkFlag flag, final boolean value) {
        final long lock = this.flagsLock.writeLock();
        boolean isInit;
        try {
            if (!this.flags.set(flag, value)) {
                return;
            }
            isInit = this.flags.is(ChunkFlag.INIT);
        }
        finally {
            this.flagsLock.unlockWrite(lock);
        }
        if (isInit) {
            this.updateFlag(flag, value);
        }
        WorldChunk.LOGGER.at(Level.FINER).log("[%d, %d] updated chunk flag (init: %s): %s, %s ", this.getX(), this.getZ(), isInit, flag, value);
    }
    
    public boolean toggleFlag(@Nonnull final ChunkFlag flag) {
        final long lock = this.flagsLock.writeLock();
        boolean value;
        boolean isInit;
        try {
            value = this.flags.toggle(flag);
            isInit = this.flags.is(ChunkFlag.INIT);
        }
        finally {
            this.flagsLock.unlockWrite(lock);
        }
        if (isInit) {
            this.updateFlag(flag, value);
        }
        WorldChunk.LOGGER.at(Level.FINER).log("[%d, %d] updated chunk flag (init: %s): %s, %s ", this.getX(), this.getZ(), isInit, flag, value);
        return value;
    }
    
    @Deprecated
    public void loadFromHolder(final World world, final int x, final int z, @Nonnull final Holder<ChunkStore> holder) {
        this.world = world;
        (this.blockChunk = holder.getComponent(BlockChunk.getComponentType())).setEnvironmentChunk(holder.getComponent(EnvironmentChunk.getComponentType()));
        this.blockComponentChunk = holder.getComponent(BlockComponentChunk.getComponentType());
        this.entityChunk = holder.getComponent(EntityChunk.getComponentType());
        this.blockChunk.load(x, z);
    }
    
    @Deprecated
    public void setBlockComponentChunk(final BlockComponentChunk blockComponentChunk) {
        this.blockComponentChunk = blockComponentChunk;
    }
    
    public void initFlags() {
        this.world.debugAssertInTickingThread();
        if (!this.is(ChunkFlag.START_INIT)) {
            throw new IllegalArgumentException("START_INIT hasn't been set!");
        }
        if (this.is(ChunkFlag.INIT)) {
            throw new IllegalArgumentException("INIT is already set!");
        }
        for (int i = 0; i < ChunkFlag.VALUES.length; ++i) {
            final ChunkFlag flag = ChunkFlag.VALUES[i];
            this.updateFlag(flag, this.is(flag));
        }
        this.setFlag(ChunkFlag.INIT, true);
    }
    
    private void updateFlag(final ChunkFlag flag, final boolean value) {
        if (flag == ChunkFlag.TICKING) {
            this.world.debugAssertInTickingThread();
            this.resetKeepAlive();
            if (value) {
                this.startsTicking();
            }
            else {
                this.stopsTicking();
            }
        }
    }
    
    private void startsTicking() {
        this.world.debugAssertInTickingThread();
        WorldChunk.LOGGER.at(Level.FINER).log("Chunk started ticking %s", this);
        final Store<ChunkStore> componentStore = this.world.getChunkStore().getStore();
        componentStore.tryRemoveComponent(this.reference, ChunkStore.REGISTRY.getNonTickingComponentType());
    }
    
    private void stopsTicking() {
        this.world.debugAssertInTickingThread();
        WorldChunk.LOGGER.at(Level.FINER).log("Chunk stopped ticking %s", this);
        final Store<ChunkStore> componentStore = this.world.getChunkStore().getStore();
        componentStore.ensureComponent(this.reference, ChunkStore.REGISTRY.getNonTickingComponentType());
    }
    
    @Nullable
    public BlockChunk getBlockChunk() {
        return this.blockChunk;
    }
    
    @Nullable
    public BlockComponentChunk getBlockComponentChunk() {
        return this.blockComponentChunk;
    }
    
    @Nullable
    public EntityChunk getEntityChunk() {
        return this.entityChunk;
    }
    
    public boolean shouldKeepLoaded() {
        return this.keepLoaded.get() > 0;
    }
    
    public void addKeepLoaded() {
        this.keepLoaded.incrementAndGet();
    }
    
    public void removeKeepLoaded() {
        this.keepLoaded.decrementAndGet();
    }
    
    public int pollKeepAlive(final int pollCount) {
        return this.keepAlive = Math.max(this.keepAlive - pollCount, 0);
    }
    
    public void resetKeepAlive() {
        this.keepAlive = 15;
    }
    
    public int pollActiveTimer(final int pollCount) {
        return this.activeTimer = Math.max(this.activeTimer - pollCount, 0);
    }
    
    public void resetActiveTimer() {
        this.activeTimer = 15;
    }
    
    @Override
    public ChunkAccessor getChunkAccessor() {
        return this.world;
    }
    
    @Override
    public int getBlock(final int x, final int y, final int z) {
        if (y < 0 || y >= 320) {
            return 0;
        }
        return this.blockChunk.getBlock(x, y, z);
    }
    
    @Override
    public boolean setBlock(final int x, final int y, final int z, final int id, @Nonnull final BlockType blockType, final int rotation, final int filler, final int settings) {
        if (y < 0 || y >= 320) {
            return false;
        }
        final short oldHeight = this.blockChunk.getHeight(x, z);
        final BlockSection blockSection = this.blockChunk.getSectionAtBlockY(y);
        final int oldRotation = blockSection.getRotationIndex(x, y, z);
        final int oldFiller = blockSection.getFiller(x, y, z);
        final int oldBlock = blockSection.get(x, y, z);
        final boolean changed = (oldBlock != id || rotation != oldRotation) && blockSection.set(x, y, z, id, rotation, filler);
        if (changed || (settings & 0x40) != 0x0) {
            final int worldX = (this.getX() << 5) + (x & 0x1F);
            final int worldZ = (this.getZ() << 5) + (z & 0x1F);
            if ((settings & 0x40) != 0x0) {
                blockSection.invalidateBlock(x, y, z);
            }
            short newHeight = oldHeight;
            if ((settings & 0x200) == 0x0 && oldHeight <= y) {
                if (oldHeight == y && id == 0) {
                    newHeight = this.blockChunk.updateHeight(x, z, (short)y);
                }
                else if (oldHeight < y && id != 0 && blockType.getOpacity() != Opacity.Transparent) {
                    newHeight = (short)y;
                    this.blockChunk.setHeight(x, z, newHeight);
                }
            }
            final WorldNotificationHandler notificationHandler = this.getWorld().getNotificationHandler();
            if ((settings & 0x4) == 0x0) {
                if (oldBlock == 0 && id != 0) {
                    notificationHandler.sendBlockParticle(worldX + 0.5, y + 0.5, worldZ + 0.5, id, BlockParticleEvent.Build);
                }
                else if (oldBlock != 0 && id == 0) {
                    final BlockParticleEvent particleType = ((settings & 0x20) != 0x0) ? BlockParticleEvent.Physics : BlockParticleEvent.Break;
                    notificationHandler.sendBlockParticle(worldX + 0.5, y + 0.5, worldZ + 0.5, oldBlock, particleType);
                }
                else {
                    notificationHandler.sendBlockParticle(worldX + 0.5, y + 0.5, worldZ + 0.5, oldBlock, BlockParticleEvent.Break);
                    notificationHandler.sendBlockParticle(worldX + 0.5, y + 0.5, worldZ + 0.5, id, BlockParticleEvent.Build);
                }
            }
            final BlockTypeAssetMap<String, BlockType> blockTypeAssetMap = BlockType.getAssetMap();
            final IndexedLookupTableAssetMap<String, BlockBoundingBoxes> hitboxAssetMap = BlockBoundingBoxes.getAssetMap();
            final String blockTypeKey = blockType.getId();
            if ((settings & 0x2) == 0x0) {
                final Holder<ChunkStore> blockEntity = blockType.getBlockEntity();
                if (blockEntity != null && filler == 0) {
                    final Holder<ChunkStore> newComponents = blockEntity.clone();
                    this.setState(x, y, z, newComponents);
                }
                else {
                    BlockState blockState = null;
                    final String blockStateType = (blockType.getState() != null) ? blockType.getState().getId() : null;
                    if (id != 0 && blockStateType != null && filler == 0) {
                        blockState = BlockStateModule.get().createBlockState(blockStateType, this, new Vector3i(x, y, z), blockType);
                        if (blockState == null) {
                            WorldChunk.LOGGER.at(Level.WARNING).log("Failed to create BlockState: %s for BlockType: %s", blockStateType, blockTypeKey);
                        }
                    }
                    final BlockState oldState = this.getState(x, y, z);
                    if (blockState instanceof final ItemContainerState newState) {
                        FillerBlockUtil.forEachFillerBlock(hitboxAssetMap.getAsset(blockType.getHitboxTypeIndex()).get(rotation), (x1, y1, z1) -> {
                            final int blockX = worldX + x1;
                            final int blockY = y + y1;
                            final int blockZ = worldZ + z1;
                            final boolean isZero = x1 == 0 && y1 == 0 && z1 == 0;
                            final BlockState stateAtFiller = isZero ? oldState : this.getState(blockX, blockY, blockZ);
                            if (stateAtFiller instanceof final ItemContainerState oldContainer) {
                                oldContainer.getItemContainer().moveAllItemStacksTo(newState.getItemContainer());
                            }
                            return;
                        });
                    }
                    this.setState(x, y, z, blockState, (settings & 0x1) == 0x0);
                }
            }
            if (this.lightingUpdatesEnabled) {
                this.world.getChunkLighting().invalidateLightAtBlock(this, x, y, z, blockType, oldHeight, newHeight);
            }
            final TickProcedure tickProcedure = BlockTickManager.getBlockTickProvider().getTickProcedure(id);
            this.blockChunk.setTicking(x, y, z, tickProcedure != null);
            if ((settings & 0x10) == 0x0) {
                final int settingsWithoutFiller = settings | 0x8 | 0x10;
                final BlockType oldBlockType = blockTypeAssetMap.getAsset(oldBlock);
                final String oldBlockKey = oldBlockType.getId();
                final int baseX = worldX - FillerBlockUtil.unpackX(oldFiller);
                final int baseY = y - FillerBlockUtil.unpackY(oldFiller);
                final int baseZ = worldZ - FillerBlockUtil.unpackZ(oldFiller);
                FillerBlockUtil.forEachFillerBlock(hitboxAssetMap.getAsset(oldBlockType.getHitboxTypeIndex()).get(oldRotation), (x1, y1, z1) -> {
                    if (x1 == 0 && y1 == 0 && z1 == 0) {
                        return;
                    }
                    else {
                        final int blockX2 = baseX + x1;
                        final int blockY2 = baseY + y1;
                        final int blockZ2 = baseZ + z1;
                        if (ChunkUtil.isSameChunk(worldX, worldZ, blockX2, blockZ2)) {
                            final String blockTypeKey2 = this.getBlockType(blockX2, blockY2, blockZ2).getId();
                            if (blockTypeKey2.equals(oldBlockKey)) {
                                this.breakBlock(blockX2, blockY2, blockZ2, settingsWithoutFiller);
                            }
                        }
                        else {
                            final String blockTypeKey3 = this.getWorld().getBlockType(blockX2, blockY2, blockZ2).getId();
                            if (blockTypeKey3.equals(oldBlockKey)) {
                                this.getWorld().breakBlock(blockX2, blockY2, blockZ2, settingsWithoutFiller);
                            }
                        }
                        return;
                    }
                });
            }
            if ((settings & 0x8) == 0x0 && filler == 0) {
                final int settingsWithoutSetFiller = settings | 0x8;
                final BlockType finalBlockType = blockType;
                final int blockId = id;
                final int finalRotation = rotation;
                FillerBlockUtil.forEachFillerBlock(hitboxAssetMap.getAsset(blockType.getHitboxTypeIndex()).get(rotation), (x1, y1, z1) -> {
                    if (x1 == 0 && y1 == 0 && z1 == 0) {
                        return;
                    }
                    else {
                        final int blockX3 = worldX + x1;
                        final int blockY3 = y + y1;
                        final int blockZ3 = worldZ + z1;
                        final boolean sameChunk = ChunkUtil.isSameChunk(worldX, worldZ, blockX3, blockZ3);
                        if (sameChunk) {
                            this.setBlock(blockX3, blockY3, blockZ3, blockId, finalBlockType, finalRotation, FillerBlockUtil.pack(x1, y1, z1), settingsWithoutSetFiller);
                        }
                        else {
                            this.getWorld().getNonTickingChunk(ChunkUtil.indexChunkFromBlock(blockX3, blockZ3)).setBlock(blockX3, blockY3, blockZ3, blockId, finalBlockType, finalRotation, FillerBlockUtil.pack(x1, y1, z1), settingsWithoutSetFiller);
                        }
                        return;
                    }
                });
            }
            if ((settings & 0x100) != 0x0) {
                FillerBlockUtil.forEachFillerBlock(hitboxAssetMap.getAsset(blockType.getHitboxTypeIndex()).get(rotation), (x1, y1, z1) -> this.getChunkAccessor().performBlockUpdate(worldX + x1, y + y1, worldZ + z1));
            }
            if (this.reference != null && this.reference.isValid()) {
                if (this.world.isInThread()) {
                    this.setBlockPhysics(x, y, z, blockType);
                }
                else {
                    final BlockType tempFinalBlockType = blockType;
                    CompletableFutureUtil._catch(CompletableFuture.runAsync(() -> this.setBlockPhysics(x, y, z, tempFinalBlockType), this.world));
                }
            }
        }
        return changed;
    }
    
    private void setBlockPhysics(final int x, final int y, final int z, @Nonnull final BlockType blockType) {
        final Store<ChunkStore> store = this.reference.getStore();
        final ChunkColumn column = store.getComponent(this.reference, ChunkColumn.getComponentType());
        final Ref<ChunkStore> section = column.getSection(ChunkUtil.chunkCoordinate(y));
        if (section != null) {
            if (!blockType.hasSupport()) {
                BlockPhysics.clear(store, section, x, y, z);
            }
            else {
                BlockPhysics.reset(store, section, x, y, z);
            }
        }
    }
    
    @Deprecated(forRemoval = true)
    @Override
    public int getFiller(final int x, final int y, final int z) {
        if (y < 0 || y >= 320) {
            return 0;
        }
        return this.blockChunk.getSectionAtBlockY(y).getFiller(x, y, z);
    }
    
    @Deprecated(forRemoval = true)
    @Override
    public int getRotationIndex(final int x, final int y, final int z) {
        if (y < 0 || y >= 320) {
            return 0;
        }
        return this.blockChunk.getSectionAtBlockY(y).getRotationIndex(x, y, z);
    }
    
    @Override
    public boolean setTicking(final int x, final int y, final int z, final boolean ticking) {
        return this.blockChunk.setTicking(x, y, z, ticking);
    }
    
    @Override
    public boolean isTicking(final int x, final int y, final int z) {
        return this.blockChunk.isTicking(x, y, z);
    }
    
    public short getHeight(final int x, final int z) {
        return this.blockChunk.getHeight(x, z);
    }
    
    public short getHeight(final int index) {
        return this.blockChunk.getHeight(index);
    }
    
    public int getTint(final int x, final int z) {
        return this.blockChunk.getTint(x, z);
    }
    
    @Nullable
    @Override
    public BlockState getState(final int x, final int y, final int z) {
        if (y < 0 || y >= 320) {
            return null;
        }
        if (!this.world.isInThread()) {
            return CompletableFuture.supplyAsync(() -> this.getState(x, y, z), this.world).join();
        }
        final int index = ChunkUtil.indexBlockInColumn(x, y, z);
        final Ref<ChunkStore> reference = this.blockComponentChunk.getEntityReference(index);
        if (reference != null) {
            return BlockState.getBlockState(reference, reference.getStore());
        }
        final Holder<ChunkStore> holder = this.blockComponentChunk.getEntityHolder(index);
        if (holder != null) {
            return BlockState.getBlockState(holder);
        }
        return null;
    }
    
    @Nullable
    public Ref<ChunkStore> getBlockComponentEntity(final int x, final int y, final int z) {
        if (y < 0 || y >= 320) {
            return null;
        }
        if (!this.world.isInThread()) {
            return CompletableFuture.supplyAsync(() -> this.getBlockComponentEntity(x, y, z), this.world).join();
        }
        final int index = ChunkUtil.indexBlockInColumn(x, y, z);
        return this.blockComponentChunk.getEntityReference(index);
    }
    
    @Nullable
    @Override
    public Holder<ChunkStore> getBlockComponentHolder(final int x, final int y, final int z) {
        if (y < 0 || y >= 320) {
            return null;
        }
        if (!this.world.isInThread()) {
            return CompletableFuture.supplyAsync(() -> this.getBlockComponentHolder(x, y, z), this.world).join();
        }
        final int index = ChunkUtil.indexBlockInColumn(x, y, z);
        final Ref<ChunkStore> reference = this.blockComponentChunk.getEntityReference(index);
        if (reference != null) {
            return reference.getStore().copyEntity(reference);
        }
        final Holder<ChunkStore> holder = this.blockComponentChunk.getEntityHolder(index);
        return (holder != null) ? holder.clone() : null;
    }
    
    @Override
    public void setState(final int x, final int y, final int z, @Nullable final BlockState state, final boolean notify) {
        if (y < 0 || y >= 320) {
            return;
        }
        final Holder<ChunkStore> holder = (state != null) ? state.toHolder() : null;
        this.setState(x, y, z, holder);
    }
    
    @Deprecated(forRemoval = true)
    @Override
    public int getFluidId(final int x, final int y, final int z) {
        final Ref<ChunkStore> columnRef = this.getReference();
        final Store<ChunkStore> store = columnRef.getStore();
        final ChunkColumn column = store.getComponent(columnRef, ChunkColumn.getComponentType());
        final Ref<ChunkStore> section = column.getSection(ChunkUtil.chunkCoordinate(y));
        if (section == null) {
            return Integer.MIN_VALUE;
        }
        final FluidSection fluidSection = store.getComponent(section, FluidSection.getComponentType());
        return fluidSection.getFluidId(x, y, z);
    }
    
    @Deprecated(forRemoval = true)
    @Override
    public byte getFluidLevel(final int x, final int y, final int z) {
        final Ref<ChunkStore> columnRef = this.getReference();
        final Store<ChunkStore> store = columnRef.getStore();
        final ChunkColumn column = store.getComponent(columnRef, ChunkColumn.getComponentType());
        final Ref<ChunkStore> section = column.getSection(ChunkUtil.chunkCoordinate(y));
        if (section == null) {
            return 0;
        }
        final FluidSection fluidSection = store.getComponent(section, FluidSection.getComponentType());
        return fluidSection.getFluidLevel(x, y, z);
    }
    
    @Deprecated(forRemoval = true)
    @Override
    public int getSupportValue(final int x, final int y, final int z) {
        final Ref<ChunkStore> columnRef = this.getReference();
        final Store<ChunkStore> store = columnRef.getStore();
        final ChunkColumn column = store.getComponent(columnRef, ChunkColumn.getComponentType());
        final Ref<ChunkStore> section = column.getSection(ChunkUtil.chunkCoordinate(y));
        if (section == null) {
            return 0;
        }
        final BlockPhysics blockPhysics = store.getComponent(section, BlockPhysics.getComponentType());
        return (blockPhysics != null) ? blockPhysics.get(x, y, z) : 0;
    }
    
    @Deprecated
    public void setState(final int x, final int y, final int z, @Nullable final Holder<ChunkStore> holder) {
        if (y < 0 || y >= 320) {
            return;
        }
        if (!this.world.isInThread()) {
            CompletableFutureUtil._catch(CompletableFuture.runAsync(() -> this.setState(x, y, z, holder), this.world));
            return;
        }
        final boolean notify = true;
        final int index = ChunkUtil.indexBlockInColumn(x, y, z);
        if (holder == null) {
            final Ref<ChunkStore> reference = this.blockComponentChunk.getEntityReference(index);
            if (reference != null) {
                final Holder<ChunkStore> oldHolder = reference.getStore().removeEntity(reference, RemoveReason.REMOVE);
                final BlockState oldState = BlockState.getBlockState(oldHolder);
                if (notify) {
                    this.world.getNotificationHandler().updateState(x, y, z, null, oldState);
                }
            }
            else {
                this.blockComponentChunk.removeEntityHolder(index);
            }
            return;
        }
        final BlockState state = BlockState.getBlockState(holder);
        if (state != null) {
            state.setPosition(this, new Vector3i(x, y, z));
        }
        final Store<ChunkStore> blockComponentStore = this.world.getChunkStore().getStore();
        if (!this.is(ChunkFlag.TICKING)) {
            final Holder<ChunkStore> oldHolder2 = this.blockComponentChunk.getEntityHolder(index);
            BlockState oldState2 = null;
            if (oldHolder2 != null) {
                oldState2 = BlockState.getBlockState(oldHolder2);
            }
            this.blockComponentChunk.removeEntityHolder(index);
            holder.putComponent(BlockModule.BlockStateInfo.getComponentType(), new BlockModule.BlockStateInfo(index, this.reference));
            this.blockComponentChunk.addEntityHolder(index, holder);
            if (notify) {
                this.world.getNotificationHandler().updateState(x, y, z, state, oldState2);
            }
            return;
        }
        final Ref<ChunkStore> oldReference = this.blockComponentChunk.getEntityReference(index);
        BlockState oldState2 = null;
        if (oldReference != null) {
            final Holder<ChunkStore> oldEntityHolder = blockComponentStore.removeEntity(oldReference, RemoveReason.REMOVE);
            oldState2 = BlockState.getBlockState(oldEntityHolder);
        }
        else {
            this.blockComponentChunk.removeEntityHolder(index);
        }
        holder.putComponent(BlockModule.BlockStateInfo.getComponentType(), new BlockModule.BlockStateInfo(index, this.reference));
        blockComponentStore.addEntity(holder, AddReason.SPAWN);
        if (notify) {
            this.world.getNotificationHandler().updateState(x, y, z, state, oldState2);
        }
    }
    
    public void markNeedsSaving() {
        this.needsSaving = true;
    }
    
    public boolean getNeedsSaving() {
        return this.needsSaving || this.blockChunk.getNeedsSaving() || this.blockComponentChunk.getNeedsSaving() || this.entityChunk.getNeedsSaving();
    }
    
    public boolean consumeNeedsSaving() {
        boolean out = this.needsSaving;
        if (this.blockChunk.consumeNeedsSaving()) {
            out = true;
        }
        if (this.blockComponentChunk.consumeNeedsSaving()) {
            out = true;
        }
        if (this.entityChunk.consumeNeedsSaving()) {
            out = true;
        }
        this.needsSaving = false;
        return out;
    }
    
    public boolean isSaving() {
        return this.isSaving;
    }
    
    public void setSaving(final boolean saving) {
        this.isSaving = saving;
    }
    
    public long getIndex() {
        return this.blockChunk.getIndex();
    }
    
    @Override
    public int getX() {
        return this.blockChunk.getX();
    }
    
    @Override
    public int getZ() {
        return this.blockChunk.getZ();
    }
    
    public void setLightingUpdatesEnabled(final boolean enableLightUpdates) {
        this.lightingUpdatesEnabled = enableLightUpdates;
    }
    
    public boolean isLightingUpdatesEnabled() {
        return this.lightingUpdatesEnabled;
    }
    
    public World getWorld() {
        return this.world;
    }
    
    @Nonnull
    @Override
    public String toString() {
        return "WorldChunk{x=" + this.blockChunk.getX() + ", z=" + this.blockChunk.getZ() + ", flags=" + String.valueOf(this.flags);
    }
    
    static {
        CODEC = BuilderCodec.builder(WorldChunk.class, WorldChunk::new).build();
        LOGGER = HytaleLogger.forEnclosingClass();
    }
}
