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

package com.hypixel.hytale.builtin.fluid;

import com.hypixel.hytale.builtin.blocktick.system.ChunkBlockTickSystem;
import com.hypixel.hytale.server.core.asset.type.fluid.Fluid;
import com.hypixel.hytale.server.core.asset.type.blocktick.BlockTickStrategy;
import com.hypixel.hytale.server.core.asset.type.fluid.FluidTicker;
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.server.core.universe.world.World;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import com.hypixel.hytale.protocol.packets.world.ServerSetFluids;
import com.hypixel.hytale.protocol.packets.world.SetFluidCmd;
import com.hypixel.hytale.protocol.packets.world.ServerSetFluid;
import java.util.Collection;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
import com.hypixel.hytale.component.system.tick.RunWhenPausedSystem;
import com.hypixel.hytale.component.system.tick.EntityTickingSystem;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.protocol.packets.world.SetFluids;
import com.hypixel.hytale.protocol.CachedPacket;
import java.util.function.Function;
import java.util.logging.Level;
import com.hypixel.hytale.protocol.Packet;
import java.util.concurrent.CompletableFuture;
import java.util.List;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.ArchetypeChunk;
import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection;
import com.hypixel.hytale.component.dependency.SystemDependency;
import com.hypixel.hytale.server.core.modules.LegacyModule;
import com.hypixel.hytale.component.dependency.Order;
import com.hypixel.hytale.server.core.universe.world.chunk.BlockChunk;
import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn;
import com.hypixel.hytale.server.core.modules.migrations.ChunkColumnMigrationSystem;
import com.hypixel.hytale.server.core.universe.world.chunk.section.ChunkSection;
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.section.FluidSection;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.component.AddReason;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.component.query.Query;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.component.system.HolderSystem;
import javax.annotation.Nonnull;
import com.hypixel.hytale.logger.HytaleLogger;

public class FluidSystems
{
    @Nonnull
    private static final HytaleLogger LOGGER;
    private static final int MAX_CHANGES_PER_PACKET = 1024;
    
    static {
        LOGGER = HytaleLogger.forEnclosingClass();
    }
    
    public static class EnsureFluidSection extends HolderSystem<ChunkStore>
    {
        @Nonnull
        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.addComponent(FluidSection.getComponentType(), new FluidSection());
        }
        
        @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 EnsureFluidSection.QUERY;
        }
        
        @Nonnull
        @Override
        public Set<Dependency<ChunkStore>> getDependencies() {
            return RootDependency.firstSet();
        }
        
        static {
            QUERY = Query.and(ChunkSection.getComponentType(), Query.not((Query<Object>)FluidSection.getComponentType()));
        }
    }
    
    public static class MigrateFromColumn extends ChunkColumnMigrationSystem
    {
        @Nonnull
        private final Query<ChunkStore> QUERY;
        @Nonnull
        private final Set<Dependency<ChunkStore>> DEPENDENCIES;
        
        public MigrateFromColumn() {
            this.QUERY = (Query<ChunkStore>)Query.and(ChunkColumn.getComponentType(), BlockChunk.getComponentType());
            this.DEPENDENCIES = (Set<Dependency<ChunkStore>>)Set.of(new SystemDependency(Order.BEFORE, LegacyModule.MigrateLegacySections.class));
        }
        
        @Override
        public void onEntityAdd(@Nonnull final Holder<ChunkStore> holder, @Nonnull final AddReason reason, @Nonnull final Store<ChunkStore> store) {
            final ChunkColumn chunkColumnComponent = holder.getComponent(ChunkColumn.getComponentType());
            assert chunkColumnComponent != null;
            final BlockChunk blockChunkComponent = holder.getComponent(BlockChunk.getComponentType());
            assert blockChunkComponent != null;
            final Holder<ChunkStore>[] sections = chunkColumnComponent.getSectionHolders();
            final BlockSection[] legacySections = blockChunkComponent.getMigratedSections();
            if (legacySections == null) {
                return;
            }
            for (int i = 0; i < sections.length; ++i) {
                final Holder<ChunkStore> section = sections[i];
                final BlockSection paletteSection = legacySections[i];
                if (section != null) {
                    if (paletteSection != null) {
                        final FluidSection fluid = paletteSection.takeMigratedFluid();
                        if (fluid != null) {
                            section.putComponent(FluidSection.getComponentType(), fluid);
                            blockChunkComponent.markNeedsSaving();
                        }
                    }
                }
            }
        }
        
        @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 this.QUERY;
        }
        
        @Nonnull
        @Override
        public Set<Dependency<ChunkStore>> getDependencies() {
            return this.DEPENDENCIES;
        }
    }
    
    public static class SetupSection extends HolderSystem<ChunkStore>
    {
        @Nonnull
        private static final Query<ChunkStore> QUERY;
        @Nonnull
        private static final Set<Dependency<ChunkStore>> DEPENDENCIES;
        
        @Override
        public void onEntityAdd(@Nonnull final Holder<ChunkStore> holder, @Nonnull final AddReason reason, @Nonnull final Store<ChunkStore> store) {
            final ChunkSection chunkSectionComponent = holder.getComponent(ChunkSection.getComponentType());
            assert chunkSectionComponent != null;
            final FluidSection fluidSectionComponent = holder.getComponent(FluidSection.getComponentType());
            assert fluidSectionComponent != null;
            fluidSectionComponent.load(chunkSectionComponent.getX(), chunkSectionComponent.getY(), chunkSectionComponent.getZ());
        }
        
        @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 SetupSection.QUERY;
        }
        
        @Nonnull
        @Override
        public Set<Dependency<ChunkStore>> getDependencies() {
            return SetupSection.DEPENDENCIES;
        }
        
        static {
            QUERY = Query.and(ChunkSection.getComponentType(), FluidSection.getComponentType());
            DEPENDENCIES = Set.of(new SystemDependency(Order.AFTER, MigrateFromColumn.class));
        }
    }
    
    public static class LoadPacketGenerator extends ChunkStore.LoadFuturePacketDataQuerySystem
    {
        @Override
        public void fetch(final int index, @Nonnull final ArchetypeChunk<ChunkStore> archetypeChunk, final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer, final PlayerRef query, @Nonnull final List<CompletableFuture<Packet>> results) {
            final ChunkColumn chunkColumnComponent = archetypeChunk.getComponent(index, ChunkColumn.getComponentType());
            assert chunkColumnComponent != null;
            for (final Ref<ChunkStore> sectionRef : chunkColumnComponent.getSections()) {
                final FluidSection fluidSectionComponent = commandBuffer.getComponent(sectionRef, FluidSection.getComponentType());
                if (fluidSectionComponent != null) {
                    results.add(fluidSectionComponent.getCachedPacket().exceptionally(throwable -> {
                        if (throwable != null) {
                            FluidSystems.LOGGER.at(Level.SEVERE).withCause(throwable).log("Exception when compressing chunk fluids:");
                        }
                        return null;
                    }).thenApply((Function<? super CachedPacket<SetFluids>, ? extends Packet>)Function.identity()));
                }
            }
        }
        
        @Override
        public Query<ChunkStore> getQuery() {
            return ChunkColumn.getComponentType();
        }
    }
    
    public static class ReplicateChanges extends EntityTickingSystem<ChunkStore> implements RunWhenPausedSystem<ChunkStore>
    {
        @Nonnull
        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 FluidSection fluidSectionComponent = archetypeChunk.getComponent(index, FluidSection.getComponentType());
            assert fluidSectionComponent != null;
            final IntOpenHashSet changes = fluidSectionComponent.getAndClearChangedPositions();
            if (changes.isEmpty()) {
                return;
            }
            final ChunkSection chunkSectionComponent = archetypeChunk.getComponent(index, ChunkSection.getComponentType());
            assert chunkSectionComponent != null;
            final World world = commandBuffer.getExternalData().getWorld();
            final WorldChunk worldChunkComponent = commandBuffer.getComponent(chunkSectionComponent.getChunkColumnReference(), WorldChunk.getComponentType());
            final int sectionY = chunkSectionComponent.getY();
            world.execute(() -> {
                if (worldChunkComponent == null || worldChunkComponent.getWorld() == null) {
                    return;
                }
                else {
                    worldChunkComponent.getWorld().getChunkLighting().invalidateLightInChunkSection(worldChunkComponent, sectionY);
                    return;
                }
            });
            final Collection<PlayerRef> playerRefs = store.getExternalData().getWorld().getPlayerRefs();
            if (playerRefs.isEmpty()) {
                changes.clear();
                return;
            }
            final long chunkIndex = ChunkUtil.indexChunk(fluidSectionComponent.getX(), fluidSectionComponent.getZ());
            if (changes.size() >= 1024) {
                final ObjectArrayList<PlayerRef> playersCopy = new ObjectArrayList<PlayerRef>(playerRefs);
                final ServerSetFluid packet;
                fluidSectionComponent.getCachedPacket().whenComplete((packet, throwable) -> {
                    if (throwable != null) {
                        FluidSystems.LOGGER.at(Level.SEVERE).withCause(throwable).log("Exception when compressing chunk fluids:");
                        return;
                    }
                    else {
                        for (final PlayerRef playerRef3 : playersCopy) {
                            final Ref<EntityStore> ref3 = playerRef3.getReference();
                            if (ref3 != null) {
                                if (!ref3.isValid()) {
                                    continue;
                                }
                                else {
                                    final ChunkTracker tracker3 = playerRef3.getChunkTracker();
                                    if (tracker3.isLoaded(chunkIndex)) {
                                        playerRef3.getPacketHandler().writeNoCache(packet);
                                    }
                                    else {
                                        continue;
                                    }
                                }
                            }
                        }
                        return;
                    }
                });
                changes.clear();
                return;
            }
            if (changes.size() == 1) {
                final int change = changes.iterator().nextInt();
                final int x = ChunkUtil.minBlock(fluidSectionComponent.getX()) + ChunkUtil.xFromIndex(change);
                final int y = ChunkUtil.minBlock(fluidSectionComponent.getY()) + ChunkUtil.yFromIndex(change);
                final int z = ChunkUtil.minBlock(fluidSectionComponent.getZ()) + ChunkUtil.zFromIndex(change);
                final int fluid = fluidSectionComponent.getFluidId(change);
                final byte level = fluidSectionComponent.getFluidLevel(change);
                final ServerSetFluid packet = new ServerSetFluid(x, y, z, fluid, level);
                for (final PlayerRef playerRef : playerRefs) {
                    final Ref<EntityStore> ref = playerRef.getReference();
                    if (ref != null) {
                        if (!ref.isValid()) {
                            continue;
                        }
                        final ChunkTracker tracker = playerRef.getChunkTracker();
                        if (!tracker.isLoaded(chunkIndex)) {
                            continue;
                        }
                        playerRef.getPacketHandler().writeNoCache(packet);
                    }
                }
            }
            else {
                final SetFluidCmd[] cmds = new SetFluidCmd[changes.size()];
                final IntIterator iter = changes.intIterator();
                int i = 0;
                while (iter.hasNext()) {
                    final int change2 = iter.nextInt();
                    final int fluid = fluidSectionComponent.getFluidId(change2);
                    final byte level = fluidSectionComponent.getFluidLevel(change2);
                    cmds[i++] = new SetFluidCmd((short)change2, fluid, level);
                }
                final ServerSetFluids packet2 = new ServerSetFluids(fluidSectionComponent.getX(), fluidSectionComponent.getY(), fluidSectionComponent.getZ(), cmds);
                for (final PlayerRef playerRef2 : playerRefs) {
                    final Ref<EntityStore> ref2 = playerRef2.getReference();
                    if (ref2 != null) {
                        if (!ref2.isValid()) {
                            continue;
                        }
                        final ChunkTracker tracker2 = playerRef2.getChunkTracker();
                        if (!tracker2.isLoaded(chunkIndex)) {
                            continue;
                        }
                        playerRef2.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(), FluidSection.getComponentType());
        }
    }
    
    public static class Ticking extends EntityTickingSystem<ChunkStore>
    {
        @Nonnull
        private static final Query<ChunkStore> QUERY;
        @Nonnull
        private static final Set<Dependency<ChunkStore>> DEPENDENCIES;
        
        @Override
        public boolean isParallel(final int archetypeChunkSize, final int taskCount) {
            return EntityTickingSystem.useParallel(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 ChunkSection chunkSectionComponent = archetypeChunk.getComponent(index, ChunkSection.getComponentType());
            assert chunkSectionComponent != null;
            final FluidSection fluidSectionComponent = archetypeChunk.getComponent(index, FluidSection.getComponentType());
            assert fluidSectionComponent != null;
            final Ref<ChunkStore> chunkRef = chunkSectionComponent.getChunkColumnReference();
            final BlockChunk blockChunkComponent = commandBuffer.getComponent(chunkRef, BlockChunk.getComponentType());
            assert blockChunkComponent != null;
            final BlockSection blockSection = blockChunkComponent.getSectionAtIndex(fluidSectionComponent.getY());
            if (blockSection == null) {
                return;
            }
            if (blockSection.getTickingBlocksCountCopy() == 0) {
                return;
            }
            final FluidTicker.CachedAccessor accessor = FluidTicker.CachedAccessor.of(commandBuffer, fluidSectionComponent, blockSection, 5);
            blockSection.forEachTicking(accessor, commandBuffer, fluidSectionComponent.getY(), (accessor1, commandBuffer1, x, y, z, block) -> {
                final FluidSection fluidSection1 = accessor1.selfFluidSection;
                final BlockSection blockSection2 = accessor1.selfBlockSection;
                final int fluidId = fluidSection1.getFluidId(x, y, z);
                if (fluidId == 0) {
                    return BlockTickStrategy.IGNORED;
                }
                else {
                    final Fluid fluid = Fluid.getAssetMap().getAsset(fluidId);
                    final int blockX = fluidSection1.getX() << 5 | x;
                    final int blockY = y;
                    final int blockZ = fluidSection1.getZ() << 5 | z;
                    return fluid.getTicker().tick(commandBuffer1, accessor1, fluidSection1, blockSection2, fluid, fluidId, blockX, blockY, blockZ);
                }
            });
        }
        
        @Nonnull
        @Override
        public Query<ChunkStore> getQuery() {
            return Ticking.QUERY;
        }
        
        @Nonnull
        @Override
        public Set<Dependency<ChunkStore>> getDependencies() {
            return Ticking.DEPENDENCIES;
        }
        
        static {
            QUERY = Query.and(FluidSection.getComponentType(), ChunkSection.getComponentType());
            DEPENDENCIES = Set.of(new SystemDependency(Order.AFTER, ChunkBlockTickSystem.PreTick.class), new SystemDependency(Order.BEFORE, ChunkBlockTickSystem.Ticking.class));
        }
    }
}
