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

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

import javax.annotation.Nullable;
import com.hypixel.hytale.server.npc.NPCPlugin;
import java.util.function.Consumer;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import java.util.Iterator;
import com.hypixel.hytale.server.spawning.world.manager.EnvironmentSpawnParameters;
import com.hypixel.hytale.server.spawning.world.manager.WorldSpawnWrapper;
import java.util.logging.Level;
import com.hypixel.hytale.server.core.asset.type.environment.config.Environment;
import com.hypixel.hytale.server.core.modules.time.WorldTimeResource;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.server.core.universe.world.World;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.spawning.SpawningPlugin;
import com.hypixel.hytale.component.ResourceType;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import com.hypixel.hytale.math.iterator.SpiralIterator;
import java.util.ArrayDeque;
import com.hypixel.hytale.server.spawning.world.WorldEnvironmentSpawnData;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Resource;

public class WorldSpawnData implements Resource<EntityStore>
{
    private static final HytaleLogger LOGGER;
    private final Int2ObjectMap<WorldEnvironmentSpawnData> worldEnvironmentSpawnData;
    private final ArrayDeque<UnspawnableEntry> unspawnableProcessingQueue;
    private int actualNPCs;
    private double expectedNPCs;
    private double expectedNPCsInEmptyEnvironments;
    private boolean unspawnable;
    private int chunkCount;
    private int segmentCount;
    private int activeSpawnJobs;
    private int trackedCountFromJobs;
    private int totalSpawnJobBudgetUsed;
    private int totalSpawnJobsCompleted;
    private final SpiralIterator spiralIterator;
    
    public WorldSpawnData() {
        this.worldEnvironmentSpawnData = new Int2ObjectOpenHashMap<WorldEnvironmentSpawnData>();
        this.unspawnableProcessingQueue = new ArrayDeque<UnspawnableEntry>();
        this.spiralIterator = new SpiralIterator();
    }
    
    public static ResourceType<EntityStore, WorldSpawnData> getResourceType() {
        return SpawningPlugin.get().getWorldSpawnDataResourceType();
    }
    
    public int getActualNPCs() {
        return this.actualNPCs;
    }
    
    public double getExpectedNPCs() {
        return this.expectedNPCs;
    }
    
    public double getExpectedNPCsInEmptyEnvironments() {
        return this.expectedNPCsInEmptyEnvironments;
    }
    
    public boolean isUnspawnable() {
        return this.unspawnable;
    }
    
    public void setUnspawnable(final boolean unspawnable) {
        this.unspawnable = unspawnable;
    }
    
    public int getChunkCount() {
        return this.chunkCount;
    }
    
    public void adjustChunkCount(final int amount) {
        this.chunkCount += amount;
    }
    
    public void adjustSegmentCount(final int amount) {
        this.segmentCount += amount;
    }
    
    @Nonnull
    public SpiralIterator getSpiralIterator() {
        return this.spiralIterator;
    }
    
    public double averageSegmentCount() {
        return (this.chunkCount == 0) ? 0.0 : (this.segmentCount / (double)this.chunkCount);
    }
    
    public int getActiveSpawnJobs() {
        return this.activeSpawnJobs;
    }
    
    public void adjustActiveSpawnJobs(final int amount, final int trackedCount) {
        this.activeSpawnJobs += amount;
        this.trackedCountFromJobs += trackedCount;
    }
    
    public int getTrackedCountFromJobs() {
        return this.trackedCountFromJobs;
    }
    
    public int getTotalSpawnJobBudgetUsed() {
        return this.totalSpawnJobBudgetUsed;
    }
    
    public int getTotalSpawnJobsCompleted() {
        return this.totalSpawnJobsCompleted;
    }
    
    public void addCompletedSpawnJob(final int budgetUsed) {
        this.totalSpawnJobBudgetUsed += budgetUsed;
        ++this.totalSpawnJobsCompleted;
    }
    
    public WorldEnvironmentSpawnData getWorldEnvironmentSpawnData(final int environmentIndex) {
        return this.worldEnvironmentSpawnData.get(environmentIndex);
    }
    
    @Nonnull
    public WorldEnvironmentSpawnData getOrCreateWorldEnvironmentSpawnData(final int environmentIndex, @Nonnull final World world, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final WorldTimeResource worldTimeResource = componentAccessor.getResource(WorldTimeResource.getResourceType());
        return this.worldEnvironmentSpawnData.computeIfAbsent(environmentIndex, envIndex -> {
            final WorldEnvironmentSpawnData newWorldEnvironmentSpawnData = new WorldEnvironmentSpawnData(envIndex);
            final EnvironmentSpawnParameters envSpawnParameters = SpawningPlugin.get().getWorldEnvironmentSpawnParameters(envIndex);
            if (envSpawnParameters == null) {
                final Environment env = Environment.getAssetMap().getAsset(envIndex);
                WorldSpawnData.LOGGER.at(Level.WARNING).log("No environment data found for '%s' [%s] but used in chunk", (env == null) ? null : env.getId(), envIndex);
                return newWorldEnvironmentSpawnData;
            }
            else {
                for (final WorldSpawnWrapper config : envSpawnParameters.getSpawnWrappers()) {
                    newWorldEnvironmentSpawnData.updateNPCs(config, world);
                }
                final int moonPhase = worldTimeResource.getMoonPhase();
                newWorldEnvironmentSpawnData.recalculateWeight(moonPhase);
                newWorldEnvironmentSpawnData.resetUnspawnable();
                return newWorldEnvironmentSpawnData;
            }
        });
    }
    
    public int[] getWorldEnvironmentSpawnDataIndexes() {
        return this.worldEnvironmentSpawnData.keySet().toIntArray();
    }
    
    public void updateSpawnability() {
        this.unspawnable = true;
        for (final WorldEnvironmentSpawnData stats : this.worldEnvironmentSpawnData.values()) {
            if (!stats.isUnspawnable()) {
                this.unspawnable = false;
                break;
            }
        }
    }
    
    public void forEachEnvironmentSpawnData(final Consumer<WorldEnvironmentSpawnData> consumer) {
        this.worldEnvironmentSpawnData.values().forEach(consumer);
    }
    
    public boolean trackNPC(final int environmentIndex, final int roleIndex, final int npcCount, @Nonnull final World world, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        if (roleIndex < 0 || environmentIndex < 0) {
            return false;
        }
        final WorldEnvironmentSpawnData stats = this.getOrCreateWorldEnvironmentSpawnData(environmentIndex, world, componentAccessor);
        stats.trackSpawn(roleIndex, npcCount);
        this.actualNPCs += npcCount;
        return true;
    }
    
    public boolean untrackNPC(final int environmentIndex, final int roleIndex, final int npcCount) {
        if (environmentIndex < 0 || roleIndex < 0) {
            return false;
        }
        final WorldEnvironmentSpawnData stats = this.worldEnvironmentSpawnData.get(environmentIndex);
        if (stats == null) {
            WorldSpawnData.LOGGER.at(Level.WARNING).log("Removing NPC %s from environment %s which is not contained in the world environment spawn data", NPCPlugin.get().getName(roleIndex), Environment.getAssetMap().getAsset(environmentIndex).getId());
            return false;
        }
        stats.trackDespawn(roleIndex, npcCount);
        this.actualNPCs -= npcCount;
        return true;
    }
    
    public void recalculateWorldCount() {
        this.actualNPCs = 0;
        this.expectedNPCs = 0.0;
        this.expectedNPCsInEmptyEnvironments = 0.0;
        for (final WorldEnvironmentSpawnData stats : this.worldEnvironmentSpawnData.values()) {
            this.actualNPCs += stats.getActualNPCs();
            if (stats.hasNPCs()) {
                this.expectedNPCs += stats.getExpectedNPCs();
            }
            else {
                this.expectedNPCsInEmptyEnvironments += stats.getExpectedNPCs();
            }
        }
    }
    
    public void queueUnspawnableChunk(final int environmentIndex, final long chunkIndex) {
        this.unspawnableProcessingQueue.add(new UnspawnableEntry(environmentIndex, chunkIndex));
    }
    
    public boolean hasUnprocessedUnspawnableChunks() {
        return !this.unspawnableProcessingQueue.isEmpty();
    }
    
    @Nullable
    public UnspawnableEntry nextUnspawnableChunk() {
        if (this.unspawnableProcessingQueue.isEmpty()) {
            return null;
        }
        return this.unspawnableProcessingQueue.poll();
    }
    
    @Override
    public Resource<EntityStore> clone() {
        throw new UnsupportedOperationException("Not implemented!");
    }
    
    static {
        LOGGER = HytaleLogger.forEnclosingClass();
    }
    
    public static class UnspawnableEntry
    {
        private final int environmentIndex;
        private final long chunkIndex;
        
        public UnspawnableEntry(final int environmentIndex, final long chunkIndex) {
            this.environmentIndex = environmentIndex;
            this.chunkIndex = chunkIndex;
        }
        
        public int getEnvironmentIndex() {
            return this.environmentIndex;
        }
        
        public long getChunkIndex() {
            return this.chunkIndex;
        }
    }
}
