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

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

import it.unimi.dsi.fastutil.ints.IntIterator;
import com.hypixel.hytale.server.core.modules.entity.player.ChunkTracker;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import java.util.Iterator;
import com.hypixel.hytale.protocol.packets.world.SetChunk;
import com.hypixel.hytale.protocol.CachedPacket;
import java.util.concurrent.CompletableFuture;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import com.hypixel.hytale.protocol.packets.world.ServerSetBlocks;
import com.hypixel.hytale.protocol.packets.world.SetBlockCmd;
import com.hypixel.hytale.protocol.packets.world.ServerSetBlock;
import java.util.logging.Level;
import com.hypixel.hytale.protocol.Packet;
import java.util.Collection;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.component.ArchetypeChunk;
import com.hypixel.hytale.component.system.tick.RunWhenPausedSystem;
import com.hypixel.hytale.component.system.tick.EntityTickingSystem;
import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection;
import com.hypixel.hytale.component.system.HolderSystem;
import com.hypixel.hytale.component.Component;
import javax.annotation.Nullable;
import com.hypixel.hytale.component.Archetype;
import com.hypixel.hytale.component.NonTicking;
import com.hypixel.hytale.component.system.RefChangeSystem;
import com.hypixel.hytale.component.dependency.SystemDependency;
import com.hypixel.hytale.component.dependency.Order;
import java.util.Arrays;
import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkSection;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.component.system.RefSystem;
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
import com.hypixel.hytale.component.dependency.RootDependency;
import com.hypixel.hytale.component.dependency.Dependency;
import java.util.Set;
import com.hypixel.hytale.component.RemoveReason;
import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.component.AddReason;
import javax.annotation.Nonnull;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.component.query.Query;
import com.hypixel.hytale.server.core.modules.migrations.ChunkColumnMigrationSystem;
import com.hypixel.hytale.logger.HytaleLogger;

public class ChunkSystems
{
    private static final HytaleLogger LOGGER;
    private static final int MAX_CHANGES_PER_PACKET = 1024;
    
    static {
        LOGGER = HytaleLogger.forEnclosingClass();
    }
    
    public static class OnNewChunk extends ChunkColumnMigrationSystem
    {
        private static final Query<ChunkStore> QUERY;
        
        @Override
        public void onEntityAdd(@Nonnull final Holder<ChunkStore> holder, @Nonnull final AddReason reason, @Nonnull final Store<ChunkStore> store) {
            final Holder[] sectionHolders = new Holder[10];
            for (int i = 0; i < sectionHolders.length; ++i) {
                sectionHolders[i] = ChunkStore.REGISTRY.newHolder();
            }
            holder.addComponent(ChunkColumn.getComponentType(), new ChunkColumn(sectionHolders));
        }
        
        @Override
        public void onEntityRemoved(@Nonnull final Holder<ChunkStore> holder, @Nonnull final RemoveReason reason, @Nonnull final Store<ChunkStore> store) {
        }
        
        @Nonnull
        @Override
        public Query<ChunkStore> getQuery() {
            return OnNewChunk.QUERY;
        }
        
        @Nonnull
        @Override
        public Set<Dependency<ChunkStore>> getDependencies() {
            return RootDependency.firstSet();
        }
        
        static {
            QUERY = Query.and(WorldChunk.getComponentType(), Query.not((Query<Object>)ChunkColumn.getComponentType()));
        }
    }
    
    public static class OnChunkLoad extends RefSystem<ChunkStore>
    {
        private static final Query<ChunkStore> QUERY;
        private static final Set<Dependency<ChunkStore>> DEPENDENCIES;
        
        @Override
        public void onEntityAdded(@Nonnull final Ref<ChunkStore> ref, @Nonnull final AddReason reason, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
            final ChunkColumn chunk = commandBuffer.getComponent(ref, ChunkColumn.getComponentType());
            assert chunk != null;
            final WorldChunk worldChunk = commandBuffer.getComponent(ref, WorldChunk.getComponentType());
            assert worldChunk != null;
            final Ref<ChunkStore>[] sections = chunk.getSections();
            final Holder<ChunkStore>[] sectionHolders = chunk.takeSectionHolders();
            final boolean isNonTicking = commandBuffer.getArchetype(ref).contains(ChunkStore.REGISTRY.getNonTickingComponentType());
            if (sectionHolders != null && sectionHolders.length > 0 && sectionHolders[0] != null) {
                for (int i = 0; i < sectionHolders.length; ++i) {
                    if (isNonTicking) {
                        sectionHolders[i].ensureComponent(ChunkStore.REGISTRY.getNonTickingComponentType());
                    }
                    else {
                        sectionHolders[i].tryRemoveComponent(ChunkStore.REGISTRY.getNonTickingComponentType());
                    }
                    final ChunkSection section = sectionHolders[i].getComponent(ChunkSection.getComponentType());
                    if (section == null) {
                        sectionHolders[i].addComponent(ChunkSection.getComponentType(), new ChunkSection(ref, worldChunk.getX(), i, worldChunk.getZ()));
                    }
                    else {
                        section.load(ref, worldChunk.getX(), i, worldChunk.getZ());
                    }
                }
                commandBuffer.addEntities(sectionHolders, 0, sections, 0, sections.length, AddReason.LOAD);
            }
            for (int i = 0; i < sections.length; ++i) {
                if (sections[i] == null) {
                    final Holder<ChunkStore> newSection = ChunkStore.REGISTRY.newHolder();
                    if (isNonTicking) {
                        newSection.ensureComponent(ChunkStore.REGISTRY.getNonTickingComponentType());
                    }
                    else {
                        newSection.tryRemoveComponent(ChunkStore.REGISTRY.getNonTickingComponentType());
                    }
                    newSection.addComponent(ChunkSection.getComponentType(), new ChunkSection(ref, worldChunk.getX(), i, worldChunk.getZ()));
                    sections[i] = commandBuffer.addEntity(newSection, AddReason.SPAWN);
                }
            }
        }
        
        @Override
        public void onEntityRemove(@Nonnull final Ref<ChunkStore> ref, @Nonnull final RemoveReason reason, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
            final ChunkColumn chunk = commandBuffer.getComponent(ref, ChunkColumn.getComponentType());
            assert chunk != null;
            final Ref<ChunkStore>[] sections = chunk.getSections();
            final Holder<ChunkStore>[] holders = new Holder[sections.length];
            for (int i = 0; i < sections.length; ++i) {
                final Ref<ChunkStore> section = sections[i];
                commandBuffer.removeEntity(section, holders[i] = ChunkStore.REGISTRY.newHolder(), reason);
            }
            chunk.putSectionHolders(holders);
            Arrays.fill(sections, null);
        }
        
        @Nonnull
        @Override
        public Query<ChunkStore> getQuery() {
            return OnChunkLoad.QUERY;
        }
        
        @Nonnull
        @Override
        public Set<Dependency<ChunkStore>> getDependencies() {
            return OnChunkLoad.DEPENDENCIES;
        }
        
        static {
            QUERY = Query.and(ChunkColumn.getComponentType(), WorldChunk.getComponentType());
            DEPENDENCIES = Set.of(new SystemDependency(Order.AFTER, OnNewChunk.class));
        }
    }
    
    public static class OnNonTicking extends RefChangeSystem<ChunkStore, NonTicking<ChunkStore>>
    {
        private final Archetype<ChunkStore> archetype;
        
        public OnNonTicking() {
            this.archetype = Archetype.of(WorldChunk.getComponentType(), ChunkColumn.getComponentType());
        }
        
        @Nonnull
        @Override
        public ComponentType<ChunkStore, NonTicking<ChunkStore>> componentType() {
            return ChunkStore.REGISTRY.getNonTickingComponentType();
        }
        
        @Override
        public void onComponentAdded(@Nonnull final Ref<ChunkStore> ref, @Nonnull final NonTicking<ChunkStore> component, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
            final ChunkColumn column = commandBuffer.getComponent(ref, ChunkColumn.getComponentType());
            assert column != null;
            final Ref<ChunkStore>[] sections = column.getSections();
            for (int i = 0; i < sections.length; ++i) {
                final Ref<ChunkStore> section = sections[i];
                commandBuffer.ensureComponent(section, ChunkStore.REGISTRY.getNonTickingComponentType());
            }
        }
        
        @Override
        public void onComponentSet(@Nonnull final Ref<ChunkStore> ref, @Nullable final NonTicking<ChunkStore> oldComponent, @Nonnull final NonTicking<ChunkStore> newComponent, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
        }
        
        @Override
        public void onComponentRemoved(@Nonnull final Ref<ChunkStore> ref, @Nonnull final NonTicking<ChunkStore> component, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
            final ChunkColumn column = commandBuffer.getComponent(ref, ChunkColumn.getComponentType());
            assert column != null;
            final Ref<ChunkStore>[] sections = column.getSections();
            for (int i = 0; i < sections.length; ++i) {
                final Ref<ChunkStore> section = sections[i];
                commandBuffer.tryRemoveComponent(section, ChunkStore.REGISTRY.getNonTickingComponentType());
            }
        }
        
        @Override
        public Query<ChunkStore> getQuery() {
            return this.archetype;
        }
    }
    
    public static class EnsureBlockSection extends HolderSystem<ChunkStore>
    {
        private static final Query<ChunkStore> QUERY;
        
        @Override
        public void onEntityAdd(@Nonnull final Holder<ChunkStore> holder, @Nonnull final AddReason reason, @Nonnull final Store<ChunkStore> store) {
            holder.ensureComponent(BlockSection.getComponentType());
        }
        
        @Override
        public void onEntityRemoved(@Nonnull final Holder<ChunkStore> holder, @Nonnull final RemoveReason reason, @Nonnull final Store<ChunkStore> store) {
        }
        
        @Nonnull
        @Override
        public Query<ChunkStore> getQuery() {
            return EnsureBlockSection.QUERY;
        }
        
        @Nonnull
        @Override
        public Set<Dependency<ChunkStore>> getDependencies() {
            return RootDependency.firstSet();
        }
        
        static {
            QUERY = Query.and(ChunkSection.getComponentType(), Query.not((Query<Object>)BlockSection.getComponentType()));
        }
    }
    
    public static class LoadBlockSection extends HolderSystem<ChunkStore>
    {
        @Override
        public void onEntityAdd(@Nonnull final Holder<ChunkStore> holder, @Nonnull final AddReason reason, @Nonnull final Store<ChunkStore> store) {
            final BlockSection section = holder.getComponent(BlockSection.getComponentType());
            assert section != null;
            section.loaded = true;
        }
        
        @Override
        public void onEntityRemoved(@Nonnull final Holder<ChunkStore> holder, @Nonnull final RemoveReason reason, @Nonnull final Store<ChunkStore> store) {
        }
        
        @Override
        public Query<ChunkStore> getQuery() {
            return BlockSection.getComponentType();
        }
    }
    
    public static class ReplicateChanges extends EntityTickingSystem<ChunkStore> implements RunWhenPausedSystem<ChunkStore>
    {
        private static final Query<ChunkStore> QUERY;
        
        @Override
        public boolean isParallel(final int archetypeChunkSize, final int taskCount) {
            return EntityTickingSystem.maybeUseParallel(archetypeChunkSize, taskCount);
        }
        
        @Override
        public void tick(final float dt, final int index, @Nonnull final ArchetypeChunk<ChunkStore> archetypeChunk, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
            final BlockSection blockSection = archetypeChunk.getComponent(index, BlockSection.getComponentType());
            assert blockSection != null;
            final IntOpenHashSet changes = blockSection.getAndClearChangedPositions();
            if (changes.isEmpty()) {
                return;
            }
            final ChunkSection section = archetypeChunk.getComponent(index, ChunkSection.getComponentType());
            assert section != null;
            final Collection<PlayerRef> players = store.getExternalData().getWorld().getPlayerRefs();
            if (players.isEmpty()) {
                changes.clear();
                return;
            }
            final long chunkIndex = ChunkUtil.indexChunk(section.getX(), section.getZ());
            if (changes.size() >= 1024) {
                final ObjectArrayList<PlayerRef> playersCopy = new ObjectArrayList<PlayerRef>(players);
                final CompletableFuture<CachedPacket<SetChunk>> set = blockSection.getCachedChunkPacket(section.getX(), section.getY(), section.getZ());
                set.thenAccept(s -> {
                    for (final PlayerRef player3 : playersCopy) {
                        final Ref<EntityStore> ref3 = player3.getReference();
                        if (ref3 == null) {
                            continue;
                        }
                        else {
                            final ChunkTracker tracker3 = player3.getChunkTracker();
                            if (tracker3 != null && tracker3.isLoaded(chunkIndex)) {
                                player3.getPacketHandler().writeNoCache(s);
                            }
                            else {
                                continue;
                            }
                        }
                    }
                    return;
                }).exceptionally(throwable -> {
                    if (throwable != null) {
                        ChunkSystems.LOGGER.at(Level.SEVERE).withCause(throwable).log("Exception when compressing chunk fluids:");
                    }
                    return null;
                });
                changes.clear();
                return;
            }
            if (changes.size() == 1) {
                final int change = changes.iterator().nextInt();
                final int x = ChunkUtil.minBlock(section.getX()) + ChunkUtil.xFromIndex(change);
                final int y = ChunkUtil.minBlock(section.getY()) + ChunkUtil.yFromIndex(change);
                final int z = ChunkUtil.minBlock(section.getZ()) + ChunkUtil.zFromIndex(change);
                final int blockId = blockSection.get(change);
                final int filler = blockSection.getFiller(change);
                final int rotation = blockSection.getRotationIndex(change);
                final ServerSetBlock packet = new ServerSetBlock(x, y, z, blockId, (short)filler, (byte)rotation);
                for (final PlayerRef player : players) {
                    final Ref<EntityStore> ref = player.getReference();
                    if (ref == null) {
                        continue;
                    }
                    final ChunkTracker tracker = player.getChunkTracker();
                    if (tracker == null || !tracker.isLoaded(chunkIndex)) {
                        continue;
                    }
                    player.getPacketHandler().writeNoCache(packet);
                }
            }
            else {
                final SetBlockCmd[] cmds = new SetBlockCmd[changes.size()];
                final IntIterator iter = changes.intIterator();
                int i = 0;
                while (iter.hasNext()) {
                    final int change2 = iter.nextInt();
                    final int blockId = blockSection.get(change2);
                    final int filler = blockSection.getFiller(change2);
                    final int rotation = blockSection.getRotationIndex(change2);
                    cmds[i++] = new SetBlockCmd((short)change2, blockId, (short)filler, (byte)rotation);
                }
                final ServerSetBlocks packet2 = new ServerSetBlocks(section.getX(), section.getY(), section.getZ(), cmds);
                for (final PlayerRef player2 : players) {
                    final Ref<EntityStore> ref2 = player2.getReference();
                    if (ref2 == null) {
                        continue;
                    }
                    final ChunkTracker tracker2 = player2.getChunkTracker();
                    if (tracker2 == null || !tracker2.isLoaded(chunkIndex)) {
                        continue;
                    }
                    player2.getPacketHandler().writeNoCache(packet2);
                }
            }
            changes.clear();
        }
        
        @Nonnull
        @Override
        public Query<ChunkStore> getQuery() {
            return ReplicateChanges.QUERY;
        }
        
        @Nonnull
        @Override
        public Set<Dependency<ChunkStore>> getDependencies() {
            return RootDependency.lastSet();
        }
        
        static {
            QUERY = Query.and(ChunkSection.getComponentType(), BlockSection.getComponentType());
        }
    }
}
