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

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

import javax.annotation.Nullable;
import java.util.Collection;
import com.hypixel.hytale.math.random.RandomExtra;
import com.hypixel.hytale.server.core.modules.time.WorldTimeResource;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.server.spawning.SpawnRejection;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import com.hypixel.hytale.server.spawning.assets.spawns.config.RoleSpawnParameters;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.spawning.world.manager.WorldSpawnWrapper;
import java.util.Iterator;
import com.hypixel.hytale.server.spawning.world.component.ChunkSpawnData;
import com.hypixel.hytale.component.Store;
import java.util.function.BiConsumer;
import java.util.Objects;
import com.hypixel.hytale.function.consumer.IntObjectConsumer;
import com.hypixel.hytale.server.spawning.SpawningPlugin;
import java.util.HashSet;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.component.Ref;
import java.util.Set;
import javax.annotation.Nonnull;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;

public class WorldEnvironmentSpawnData
{
    public static final double K_COLUMNS = 1024.0;
    private final int environmentIndex;
    private double expectedNPCs;
    private int actualNPCs;
    private int segmentCount;
    private double density;
    private double sumOfWeights;
    private boolean unspawnable;
    private boolean fullyPopulated;
    @Nonnull
    private final Int2ObjectMap<WorldNPCSpawnStat> npcStatMap;
    @Nonnull
    private final Set<Ref<ChunkStore>> chunkRefSet;
    
    public WorldEnvironmentSpawnData(final int environmentIndex, final double density) {
        this.environmentIndex = environmentIndex;
        this.npcStatMap = new Int2ObjectOpenHashMap<WorldNPCSpawnStat>();
        this.chunkRefSet = new HashSet<Ref<ChunkStore>>();
        this.density = density;
        this.fullyPopulated = true;
    }
    
    public WorldEnvironmentSpawnData(final int index) {
        this(index, SpawningPlugin.get().getEnvironmentDensity(index));
    }
    
    public int getEnvironmentIndex() {
        return this.environmentIndex;
    }
    
    public int getSegmentCount() {
        return this.segmentCount;
    }
    
    public boolean isUnspawnable() {
        return this.unspawnable;
    }
    
    public void setUnspawnable(final boolean unspawnable) {
        this.unspawnable = unspawnable;
    }
    
    public double getExpectedNPCs() {
        return this.expectedNPCs;
    }
    
    public int getActualNPCs() {
        return this.actualNPCs;
    }
    
    public boolean isEmpty() {
        return this.getSegmentCount() == 0;
    }
    
    public boolean hasNPCs() {
        return !this.npcStatMap.isEmpty();
    }
    
    @Nonnull
    public Int2ObjectMap<WorldNPCSpawnStat> getNpcStatMap() {
        return this.npcStatMap;
    }
    
    public boolean isFullyPopulated() {
        return this.fullyPopulated;
    }
    
    public void setFullyPopulated(final boolean fullyPopulated) {
        this.fullyPopulated = fullyPopulated;
    }
    
    @Nonnull
    public Set<Ref<ChunkStore>> getChunkRefSet() {
        return this.chunkRefSet;
    }
    
    public void adjustSegmentCount(final int delta) {
        this.segmentCount += delta;
        this.expectedNPCs = this.segmentCount * this.density / 1024.0;
    }
    
    public void forEachNpcStat(@Nonnull final IntObjectConsumer<WorldNPCSpawnStat> consumer) {
        final Int2ObjectMap<WorldNPCSpawnStat> npcStatMap = this.npcStatMap;
        Objects.requireNonNull(consumer);
        npcStatMap.forEach(consumer::accept);
    }
    
    public void setDensity(final double density, @Nonnull final Store<ChunkStore> store) {
        this.density = density;
        this.expectedNPCs = this.segmentCount * density / 1024.0;
        for (final Ref<ChunkStore> chunkRef : this.chunkRefSet) {
            store.getComponent(chunkRef, ChunkSpawnData.getComponentType()).getEnvironmentSpawnData(this.environmentIndex).updateDensity(density);
        }
    }
    
    public void updateNPCs(final WorldSpawnWrapper spawnWrapper, final World world) {
        final Int2ObjectMap<RoleSpawnParameters> npcs = spawnWrapper.getRoles();
        if (!npcs.isEmpty()) {
            for (final Int2ObjectMap.Entry<RoleSpawnParameters> entry : npcs.int2ObjectEntrySet()) {
                if (!this.npcStatMap.containsKey(entry.getIntKey())) {
                    this.npcStatMap.put(entry.getIntKey(), new WorldNPCSpawnStat(entry.getIntKey(), spawnWrapper, entry.getValue(), world));
                }
            }
        }
    }
    
    public void clearNPCs() {
        this.npcStatMap.clear();
        this.sumOfWeights = 0.0;
        this.actualNPCs = 0;
        this.unspawnable = true;
    }
    
    public void updateSpawnStats(final int roleIndex, final int spansTried, final int spansSuccess, final int budgetUsed, @Nonnull final Object2IntMap<SpawnRejection> rejections, final boolean success) {
        final WorldNPCSpawnStat stat = this.npcStatMap.get(roleIndex);
        if (stat == null) {
            return;
        }
        stat.updateSpawnStats(spansTried, spansSuccess, budgetUsed, rejections, success);
    }
    
    public void removeNPC(final int roleIndex, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final WorldTimeResource worldTimeResource = componentAccessor.getResource(WorldTimeResource.getResourceType());
        this.npcStatMap.remove(roleIndex);
        this.recalculateWeight(worldTimeResource.getMoonPhase());
    }
    
    public void addNPC(final int roleIndex, @Nonnull final WorldSpawnWrapper spawnWrapper, @Nonnull final RoleSpawnParameters spawnParams, @Nonnull final World world, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final WorldTimeResource worldTimeResource = componentAccessor.getResource(WorldTimeResource.getResourceType());
        this.npcStatMap.computeIfAbsent(roleIndex, index -> new WorldNPCSpawnStat(index, spawnWrapper, spawnParams, world));
        this.recalculateWeight(worldTimeResource.getMoonPhase());
        this.resetUnspawnable();
    }
    
    public double spawnWeight() {
        return Math.max(0.0, this.getExpectedNPCs() - this.getActualNPCs());
    }
    
    @Nullable
    public WorldNPCSpawnStat pickRandomSpawnNPCStat(@Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        return RandomExtra.randomWeightedElement(this.npcStatMap.values(), worldNPCSpawnStat -> worldNPCSpawnStat.getMissingCount(componentAccessor));
    }
    
    public void resetUnspawnable() {
        if (this.npcStatMap.isEmpty()) {
            this.unspawnable = true;
            return;
        }
        this.unspawnable = false;
        for (final WorldNPCSpawnStat stat : this.npcStatMap.values()) {
            stat.resetUnspawnable();
        }
    }
    
    public void trackSpawn(final int roleNameIndex, final int npcCount) {
        WorldNPCSpawnStat stat = this.npcStatMap.get(roleNameIndex);
        if (stat == null) {
            stat = new WorldNPCSpawnStat.CountOnly(roleNameIndex);
            this.npcStatMap.put(roleNameIndex, stat);
        }
        stat.adjustActual(npcCount);
        this.actualNPCs += npcCount;
    }
    
    public void trackDespawn(final int roleNameIndex, final int npcCount) {
        final WorldNPCSpawnStat stat = this.npcStatMap.get(roleNameIndex);
        if (stat != null && stat.getActual() > 0) {
            stat.adjustActual(-npcCount);
            this.actualNPCs -= npcCount;
        }
    }
    
    public void removeChunk(@Nonnull final Ref<ChunkStore> ref, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final WorldTimeResource worldTimeResource = componentAccessor.getResource(WorldTimeResource.getResourceType());
        this.chunkRefSet.remove(ref);
        this.updateExpectedNPCs(worldTimeResource.getMoonPhase());
    }
    
    public void addChunk(@Nonnull final Ref<ChunkStore> ref, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final WorldTimeResource worldTimeResource = componentAccessor.getResource(WorldTimeResource.getResourceType());
        this.chunkRefSet.add(ref);
        this.fullyPopulated = false;
        this.updateExpectedNPCs(worldTimeResource.getMoonPhase());
        this.resetUnspawnable();
    }
    
    public void recalculateWeight(final int moonPhase) {
        this.sumOfWeights = 0.0;
        for (final WorldNPCSpawnStat stat : this.npcStatMap.values()) {
            this.sumOfWeights += stat.getWeight(moonPhase);
        }
        this.updateExpectedNPCs(moonPhase);
    }
    
    public void updateExpectedNPCs(final int moonPhase) {
        final double segmentsPerWeightUnit = (this.sumOfWeights == 0.0) ? 0.0 : (this.expectedNPCs / this.sumOfWeights);
        this.actualNPCs = 0;
        for (final WorldNPCSpawnStat stat : this.npcStatMap.values()) {
            stat.setExpected(stat.getWeight(moonPhase) * segmentsPerWeightUnit);
            this.actualNPCs += stat.getActual();
        }
    }
}
