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

package com.hypixel.hytale.server.spawning.world.system;

import com.hypixel.hytale.component.Component;
import com.hypixel.hytale.component.NonTicking;
import com.hypixel.hytale.component.system.RefChangeSystem;
import com.hypixel.hytale.component.RemoveReason;
import com.hypixel.hytale.component.AddReason;
import com.hypixel.hytale.component.ResourceType;
import com.hypixel.hytale.component.system.RefSystem;
import com.hypixel.hytale.component.Archetype;
import com.hypixel.hytale.server.core.universe.world.chunk.environment.EnvironmentColumn;
import com.hypixel.hytale.server.spawning.SpawningPlugin;
import com.hypixel.hytale.server.spawning.world.component.ChunkSpawnedNPCData;
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
import com.hypixel.hytale.server.spawning.world.WorldEnvironmentSpawnData;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.ComponentAccessor;
import java.util.logging.Level;
import com.hypixel.hytale.server.core.asset.type.environment.config.Environment;
import com.hypixel.hytale.server.spawning.world.ChunkEnvironmentSpawnData;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.ints.Int2ObjectMaps;
import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.server.spawning.world.component.ChunkSpawnData;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.server.spawning.world.component.WorldSpawnData;
import com.hypixel.hytale.component.Store;
import javax.annotation.Nonnull;
import com.hypixel.hytale.component.Ref;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.component.query.Query;
import com.hypixel.hytale.logger.HytaleLogger;

public class ChunkSpawningSystems
{
    private static final HytaleLogger LOGGER;
    private static final Query<ChunkStore> QUERY;
    private static final Query<ChunkStore> TICKING_QUERY;
    
    protected static boolean processStoppedChunk(@Nonnull final Ref<ChunkStore> ref, @Nonnull final Store<ChunkStore> store, @Nonnull final WorldSpawnData worldSpawnData, @Nonnull final ComponentType<ChunkStore, ChunkSpawnData> chunkSpawnDataComponentType, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
        final ChunkSpawnData chunkSpawnData = store.getComponent(ref, chunkSpawnDataComponentType);
        if (chunkSpawnData == null) {
            return false;
        }
        commandBuffer.removeComponent(ref, chunkSpawnDataComponentType);
        if (!chunkSpawnData.isStarted()) {
            return false;
        }
        final World world = store.getExternalData().getWorld();
        final Store<EntityStore> entityStore = world.getEntityStore().getStore();
        final ObjectIterator<Int2ObjectMap.Entry<ChunkEnvironmentSpawnData>> iterator = Int2ObjectMaps.fastIterator(chunkSpawnData.getChunkEnvironmentSpawnDataMap());
        while (iterator.hasNext()) {
            final Int2ObjectMap.Entry<ChunkEnvironmentSpawnData> entry = iterator.next();
            final WorldEnvironmentSpawnData worldEnvironmentSpawnData = worldSpawnData.getWorldEnvironmentSpawnData(entry.getIntKey());
            if (worldEnvironmentSpawnData == null) {
                continue;
            }
            final ChunkEnvironmentSpawnData chunkEnvironmentSpawnData = entry.getValue();
            if (!chunkEnvironmentSpawnData.wasProcessedAsUnspawnable()) {
                final int segmentCount = -chunkEnvironmentSpawnData.getSegmentCount();
                worldEnvironmentSpawnData.adjustSegmentCount(segmentCount);
                if (worldEnvironmentSpawnData.getSegmentCount() < 0) {
                    final Environment environment = Environment.getAssetMap().getAsset(worldEnvironmentSpawnData.getEnvironmentIndex());
                    final String environmentName = (environment != null) ? environment.getId() : null;
                    ChunkSpawningSystems.LOGGER.at(Level.SEVERE).log("Block count for environment %s dropped below 0 to %s", environmentName, worldEnvironmentSpawnData.getSegmentCount());
                }
                worldSpawnData.adjustSegmentCount(segmentCount);
            }
            worldEnvironmentSpawnData.removeChunk(ref, entityStore);
        }
        return true;
    }
    
    protected static boolean processStartedChunk(@Nonnull final Ref<ChunkStore> ref, @Nonnull final Store<ChunkStore> store, @Nonnull final WorldChunk worldChunk, @Nonnull final WorldSpawnData worldSpawnData, @Nonnull final ComponentType<ChunkStore, ChunkSpawnData> chunkSpawnDataComponentType, @Nonnull final ComponentType<ChunkStore, ChunkSpawnedNPCData> chunkSpawnedNPCDataComponentType, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
        ChunkSpawnData chunkSpawnData = store.getComponent(ref, chunkSpawnDataComponentType);
        if (chunkSpawnData == null) {
            chunkSpawnData = new ChunkSpawnData();
            commandBuffer.putComponent(ref, chunkSpawnDataComponentType, chunkSpawnData);
        }
        commandBuffer.ensureComponent(ref, chunkSpawnedNPCDataComponentType);
        if (chunkSpawnData.isStarted()) {
            return false;
        }
        ObjectIterator<Int2ObjectMap.Entry<ChunkEnvironmentSpawnData>> iterator = Int2ObjectMaps.fastIterator(chunkSpawnData.getChunkEnvironmentSpawnDataMap());
        while (iterator.hasNext()) {
            final Int2ObjectMap.Entry<ChunkEnvironmentSpawnData> entry = iterator.next();
            entry.getValue().init(entry.getIntKey(), worldChunk);
        }
        preprocessChunk(chunkSpawnData, worldChunk);
        chunkSpawnData.setStarted(true);
        final World world = store.getExternalData().getWorld();
        final Store<EntityStore> entityStore = world.getEntityStore().getStore();
        iterator = Int2ObjectMaps.fastIterator(chunkSpawnData.getChunkEnvironmentSpawnDataMap());
        while (iterator.hasNext()) {
            final Int2ObjectMap.Entry<ChunkEnvironmentSpawnData> entry2 = iterator.next();
            final int environmentIndex = entry2.getIntKey();
            final ChunkEnvironmentSpawnData chunkEnvironmentSpawnData = entry2.getValue();
            chunkEnvironmentSpawnData.updateDensity(SpawningPlugin.get().getEnvironmentDensity(environmentIndex));
            final WorldEnvironmentSpawnData worldEnvironmentSpawnData = worldSpawnData.getOrCreateWorldEnvironmentSpawnData(environmentIndex, world, entityStore);
            final int segmentCount = chunkEnvironmentSpawnData.getSegmentCount();
            worldEnvironmentSpawnData.adjustSegmentCount(segmentCount);
            worldSpawnData.adjustSegmentCount(segmentCount);
            worldEnvironmentSpawnData.addChunk(ref, entityStore);
            final HytaleLogger.Api context = ChunkSpawningSystems.LOGGER.at(Level.FINER);
            if (context.isEnabled()) {
                final Environment environment = Environment.getAssetMap().getAsset(environmentIndex);
                context.log("   Add chunk [%s/%s] to env=%s, exp=%s, act=%s, blks=%s", worldChunk.getX(), worldChunk.getZ(), (environment == null) ? null : environment.getId(), worldEnvironmentSpawnData.getExpectedNPCs(), worldEnvironmentSpawnData.getActualNPCs(), worldEnvironmentSpawnData.getSegmentCount());
            }
        }
        return true;
    }
    
    private static void preprocessChunk(@Nonnull final ChunkSpawnData chunkSpawnData, @Nonnull final WorldChunk worldChunk) {
        for (int x = 0; x < 32; ++x) {
            for (int z = 0; z < 32; ++z) {
                preprocessColumn(chunkSpawnData, worldChunk, x, z);
            }
        }
    }
    
    private static void preprocessColumn(@Nonnull final ChunkSpawnData chunkSpawnData, @Nonnull final WorldChunk worldChunk, final int x, final int z) {
        final EnvironmentColumn column = worldChunk.getBlockChunk().getEnvironmentColumn(x, z);
        final Int2ObjectMap<ChunkEnvironmentSpawnData> environmentSpawnDataMap = chunkSpawnData.getChunkEnvironmentSpawnDataMap();
        for (int i = column.indexOf(0); i < column.size() && column.getValueMin(i) <= 320; ++i) {
            final int environmentIndex = column.getValue(i);
            ChunkEnvironmentSpawnData data = environmentSpawnDataMap.get(environmentIndex);
            if (data == null) {
                data = new ChunkEnvironmentSpawnData();
                data.init(environmentIndex, worldChunk);
                environmentSpawnDataMap.put(environmentIndex, data);
            }
            data.registerSegment(x, z);
        }
    }
    
    protected static void updateChunkCount(final int newChunks, @Nonnull final WorldSpawnData worldSpawnData) {
        if (newChunks > 0) {
            worldSpawnData.setUnspawnable(false);
        }
        worldSpawnData.adjustChunkCount(newChunks);
        worldSpawnData.recalculateWorldCount();
    }
    
    static {
        LOGGER = HytaleLogger.forEnclosingClass();
        QUERY = Archetype.of(WorldChunk.getComponentType(), ChunkSpawnData.getComponentType());
        TICKING_QUERY = Query.and(Query.not((Query<Object>)ChunkStore.REGISTRY.getNonTickingComponentType()), ChunkSpawningSystems.QUERY);
    }
    
    public static class ChunkRefAdded extends RefSystem<ChunkStore>
    {
        private final ResourceType<EntityStore, WorldSpawnData> worldSpawnDataResourceType;
        private final ComponentType<ChunkStore, ChunkSpawnData> chunkSpawnDataComponentType;
        private final ComponentType<ChunkStore, ChunkSpawnedNPCData> chunkSpawnedNPCDataComponentType;
        private final ComponentType<ChunkStore, WorldChunk> worldChunkComponentType;
        
        public ChunkRefAdded(final ResourceType<EntityStore, WorldSpawnData> worldSpawnDataResourceType, final ComponentType<ChunkStore, ChunkSpawnData> chunkSpawnDataComponentType, final ComponentType<ChunkStore, ChunkSpawnedNPCData> chunkSpawnedNPCDataComponentType) {
            this.worldSpawnDataResourceType = worldSpawnDataResourceType;
            this.chunkSpawnDataComponentType = chunkSpawnDataComponentType;
            this.chunkSpawnedNPCDataComponentType = chunkSpawnedNPCDataComponentType;
            this.worldChunkComponentType = WorldChunk.getComponentType();
        }
        
        @Nonnull
        @Override
        public Query<ChunkStore> getQuery() {
            return ChunkSpawningSystems.TICKING_QUERY;
        }
        
        @Override
        public void onEntityAdded(@Nonnull final Ref<ChunkStore> ref, @Nonnull final AddReason reason, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
            final WorldSpawnData worldSpawnData = store.getExternalData().getWorld().getEntityStore().getStore().getResource(this.worldSpawnDataResourceType);
            final WorldChunk worldChunk = store.getComponent(ref, this.worldChunkComponentType);
            if (ChunkSpawningSystems.processStartedChunk(ref, store, worldChunk, worldSpawnData, this.chunkSpawnDataComponentType, this.chunkSpawnedNPCDataComponentType, commandBuffer)) {
                ChunkSpawningSystems.LOGGER.at(Level.FINE).log("Adding chunk [%s/%s]", worldChunk.getX(), worldChunk.getZ());
                ChunkSpawningSystems.updateChunkCount(1, worldSpawnData);
            }
        }
        
        @Override
        public void onEntityRemove(@Nonnull final Ref<ChunkStore> ref, @Nonnull final RemoveReason reason, @Nonnull final Store<ChunkStore> store, @Nonnull final CommandBuffer<ChunkStore> commandBuffer) {
        }
    }
    
    public static class TickingState extends RefChangeSystem<ChunkStore, NonTicking<ChunkStore>>
    {
        private static final HytaleLogger LOGGER;
        private final ResourceType<EntityStore, WorldSpawnData> worldSpawnDataResourceType;
        private final ComponentType<ChunkStore, ChunkSpawnData> chunkSpawnDataComponentType;
        private final ComponentType<ChunkStore, ChunkSpawnedNPCData> chunkSpawnedNPCDataComponentType;
        private final ComponentType<ChunkStore, WorldChunk> worldChunkComponentType;
        
        public TickingState(final ResourceType<EntityStore, WorldSpawnData> worldSpawnDataResourceType, final ComponentType<ChunkStore, ChunkSpawnData> chunkSpawnDataComponentType, final ComponentType<ChunkStore, ChunkSpawnedNPCData> chunkSpawnedNPCDataComponentType) {
            this.worldSpawnDataResourceType = worldSpawnDataResourceType;
            this.chunkSpawnDataComponentType = chunkSpawnDataComponentType;
            this.chunkSpawnedNPCDataComponentType = chunkSpawnedNPCDataComponentType;
            this.worldChunkComponentType = WorldChunk.getComponentType();
        }
        
        @Override
        public Query<ChunkStore> getQuery() {
            return this.worldChunkComponentType;
        }
        
        @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 WorldSpawnData worldSpawnData = store.getExternalData().getWorld().getEntityStore().getStore().getResource(this.worldSpawnDataResourceType);
            final WorldChunk worldChunk = store.getComponent(ref, this.worldChunkComponentType);
            if (ChunkSpawningSystems.processStoppedChunk(ref, store, worldSpawnData, this.chunkSpawnDataComponentType, commandBuffer)) {
                TickingState.LOGGER.at(Level.FINE).log("Removing chunk [%s/%s]", worldChunk.getX(), worldChunk.getZ());
                ChunkSpawningSystems.updateChunkCount(-1, worldSpawnData);
            }
        }
        
        @Override
        public void onComponentSet(@Nonnull final Ref<ChunkStore> ref, 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 WorldSpawnData worldSpawnData = store.getExternalData().getWorld().getEntityStore().getStore().getResource(this.worldSpawnDataResourceType);
            final WorldChunk worldChunk = store.getComponent(ref, this.worldChunkComponentType);
            if (ChunkSpawningSystems.processStartedChunk(ref, store, worldChunk, worldSpawnData, this.chunkSpawnDataComponentType, this.chunkSpawnedNPCDataComponentType, commandBuffer)) {
                TickingState.LOGGER.at(Level.FINE).log("Adding chunk [%s/%s]", worldChunk.getX(), worldChunk.getZ());
                ChunkSpawningSystems.updateChunkCount(1, worldSpawnData);
            }
        }
        
        static {
            LOGGER = HytaleLogger.forEnclosingClass();
        }
    }
}
