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

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

import com.hypixel.hytale.server.spawning.jobs.SpawnJob;
import com.hypixel.hytale.server.core.entity.UUIDComponent;
import java.util.logging.Level;
import com.hypixel.hytale.server.spawning.assets.spawns.config.BeaconNPCSpawn;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.spawning.assets.spawns.config.RoleSpawnParameters;
import com.hypixel.hytale.server.spawning.wrappers.BeaconSpawnWrapper;
import com.hypixel.hytale.server.npc.NPCPlugin;
import java.util.Random;
import java.util.concurrent.ThreadLocalRandom;
import com.hypixel.hytale.server.spawning.beacons.LegacySpawnBeaconEntity;
import com.hypixel.hytale.component.ComponentAccessor;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import it.unimi.dsi.fastutil.objects.Object2DoubleOpenHashMap;
import it.unimi.dsi.fastutil.objects.Object2IntOpenHashMap;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import com.hypixel.hytale.server.core.universe.world.World;
import java.time.Duration;
import java.util.Comparator;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.objects.Object2DoubleMap;
import java.util.UUID;
import it.unimi.dsi.fastutil.objects.Object2IntMap;
import com.hypixel.hytale.server.core.universe.PlayerRef;
import java.util.List;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.Ref;
import javax.annotation.Nonnull;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.server.spawning.jobs.NPCBeaconSpawnJob;

public class BeaconSpawnController extends SpawnController<NPCBeaconSpawnJob>
{
    @Nonnull
    private static final HytaleLogger LOGGER;
    public static final int MAX_ATTEMPTS_PER_TICK = 5;
    public static final double ROUNDING_BREAK_POINT = 0.25;
    @Nonnull
    private final Ref<EntityStore> ownerRef;
    private final List<Ref<EntityStore>> spawnedEntities;
    private final List<PlayerRef> playersInRegion;
    private int nextPlayerIndex;
    private final Object2IntMap<UUID> entitiesPerPlayer;
    private final Object2DoubleMap<Ref<EntityStore>> entityTimeoutCounter;
    private final IntSet unspawnableRoles;
    private final Comparator<PlayerRef> threatComparator;
    private int baseMaxTotalSpawns;
    private int currentScaledMaxTotalSpawns;
    private int[] baseMaxConcurrentSpawns;
    private int currentScaledMaxConcurrentSpawns;
    private int spawnsThisRound;
    private int remainingSpawns;
    private boolean roundStart;
    private double beaconRadiusSquared;
    private double spawnRadiusSquared;
    private double despawnNPCAfterTimeout;
    private Duration despawnBeaconAfterTimeout;
    private boolean despawnNPCsIfIdle;
    
    public BeaconSpawnController(@Nonnull final World world, @Nonnull final Ref<EntityStore> ownerRef) {
        super(world);
        this.spawnedEntities = new ObjectArrayList<Ref<EntityStore>>();
        this.playersInRegion = new ObjectArrayList<PlayerRef>();
        this.nextPlayerIndex = 0;
        this.entitiesPerPlayer = new Object2IntOpenHashMap<UUID>();
        this.entityTimeoutCounter = new Object2DoubleOpenHashMap<Ref<EntityStore>>();
        this.unspawnableRoles = new IntOpenHashSet();
        this.threatComparator = Comparator.comparingInt(playerRef -> this.entitiesPerPlayer.getOrDefault(playerRef.getUuid(), 0));
        this.roundStart = true;
        this.ownerRef = ownerRef;
    }
    
    @Override
    public int getMaxActiveJobs() {
        return Math.min(this.remainingSpawns, this.baseMaxActiveJobs);
    }
    
    @Nullable
    @Override
    public NPCBeaconSpawnJob createRandomSpawnJob(@Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        final LegacySpawnBeaconEntity legacySpawnBeaconComponent = componentAccessor.getComponent(this.ownerRef, LegacySpawnBeaconEntity.getComponentType());
        assert legacySpawnBeaconComponent != null;
        final BeaconSpawnWrapper wrapper = legacySpawnBeaconComponent.getSpawnWrapper();
        final RoleSpawnParameters spawn = wrapper.pickRole(ThreadLocalRandom.current());
        if (spawn == null) {
            return null;
        }
        final String spawnId = spawn.getId();
        final int roleIndex = NPCPlugin.get().getIndex(spawnId);
        if (roleIndex < 0 || this.unspawnableRoles.contains(roleIndex)) {
            return null;
        }
        NPCBeaconSpawnJob job = null;
        final int predictedTotal = this.spawnedEntities.size() + this.activeJobs.size();
        if (this.activeJobs.size() < this.getMaxActiveJobs() && this.nextPlayerIndex < this.playersInRegion.size() && predictedTotal < this.currentScaledMaxTotalSpawns) {
            job = (NPCBeaconSpawnJob)(this.idleJobs.isEmpty() ? new NPCBeaconSpawnJob() : this.idleJobs.pop());
            job.beginProbing(this.playersInRegion.get(this.nextPlayerIndex++), this.currentScaledMaxConcurrentSpawns, roleIndex, spawn.getFlockDefinition());
            this.activeJobs.add((T)job);
            if (this.nextPlayerIndex >= this.playersInRegion.size()) {
                this.nextPlayerIndex = 0;
            }
        }
        return job;
    }
    
    public void initialise(@Nonnull final BeaconSpawnWrapper spawnWrapper) {
        final BeaconNPCSpawn spawn = spawnWrapper.getSpawn();
        this.baseMaxTotalSpawns = spawn.getMaxSpawnedNpcs();
        this.baseMaxConcurrentSpawns = spawn.getConcurrentSpawnsRange();
        final double beaconRadius = spawn.getBeaconRadius();
        this.beaconRadiusSquared = beaconRadius * beaconRadius;
        final double spawnRadius = spawn.getSpawnRadius();
        this.spawnRadiusSquared = spawnRadius * spawnRadius;
        this.despawnNPCAfterTimeout = spawn.getNpcIdleDespawnTimeSeconds();
        this.despawnBeaconAfterTimeout = spawn.getBeaconVacantDespawnTime();
        this.despawnNPCsIfIdle = (spawn.getNpcSpawnState() != null);
    }
    
    public int getSpawnsThisRound() {
        return this.spawnsThisRound;
    }
    
    public void setRemainingSpawns(final int remainingSpawns) {
        this.remainingSpawns = remainingSpawns;
    }
    
    public void addRoundSpawn() {
        ++this.spawnsThisRound;
        --this.remainingSpawns;
    }
    
    public boolean isRoundStart() {
        return this.roundStart;
    }
    
    public void setRoundStart(final boolean roundStart) {
        this.roundStart = roundStart;
    }
    
    public Ref<EntityStore> getOwnerRef() {
        return this.ownerRef;
    }
    
    public int[] getBaseMaxConcurrentSpawns() {
        return this.baseMaxConcurrentSpawns;
    }
    
    public List<PlayerRef> getPlayersInRegion() {
        return this.playersInRegion;
    }
    
    public int getCurrentScaledMaxConcurrentSpawns() {
        return this.currentScaledMaxConcurrentSpawns;
    }
    
    public void setCurrentScaledMaxConcurrentSpawns(final int currentScaledMaxConcurrentSpawns) {
        this.currentScaledMaxConcurrentSpawns = currentScaledMaxConcurrentSpawns;
    }
    
    public Duration getDespawnBeaconAfterTimeout() {
        return this.despawnBeaconAfterTimeout;
    }
    
    public double getSpawnRadiusSquared() {
        return this.spawnRadiusSquared;
    }
    
    public double getBeaconRadiusSquared() {
        return this.beaconRadiusSquared;
    }
    
    public int getBaseMaxTotalSpawns() {
        return this.baseMaxTotalSpawns;
    }
    
    public void setCurrentScaledMaxTotalSpawns(final int currentScaledMaxTotalSpawns) {
        this.currentScaledMaxTotalSpawns = currentScaledMaxTotalSpawns;
    }
    
    public List<Ref<EntityStore>> getSpawnedEntities() {
        return this.spawnedEntities;
    }
    
    public void setNextPlayerIndex(final int nextPlayerIndex) {
        this.nextPlayerIndex = nextPlayerIndex;
    }
    
    public Object2DoubleMap<Ref<EntityStore>> getEntityTimeoutCounter() {
        return this.entityTimeoutCounter;
    }
    
    public Object2IntMap<UUID> getEntitiesPerPlayer() {
        return this.entitiesPerPlayer;
    }
    
    public boolean isDespawnNPCsIfIdle() {
        return this.despawnNPCsIfIdle;
    }
    
    public double getDespawnNPCAfterTimeout() {
        return this.despawnNPCAfterTimeout;
    }
    
    public Comparator<PlayerRef> getThreatComparator() {
        return this.threatComparator;
    }
    
    public void notifySpawnedEntityExists(@Nonnull final Ref<EntityStore> ref, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        this.spawnedEntities.add(ref);
        final HytaleLogger.Api context = BeaconSpawnController.LOGGER.at(Level.FINE);
        if (context.isEnabled()) {
            final UUIDComponent ownerUuidComponent = componentAccessor.getComponent(this.ownerRef, UUIDComponent.getComponentType());
            assert ownerUuidComponent != null;
            context.log("Registering NPC with reference %s with Spawn Beacon %s", ref, ownerUuidComponent.getUuid());
        }
    }
    
    public void onJobFinished(@Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        if (++this.spawnsThisRound >= this.currentScaledMaxConcurrentSpawns) {
            this.onAllConcurrentSpawned(componentAccessor);
        }
    }
    
    public void notifyNPCRemoval(@Nonnull final Ref<EntityStore> ref, @Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        this.spawnedEntities.remove(ref);
        this.entityTimeoutCounter.removeDouble(ref);
        if (this.spawnedEntities.size() == this.currentScaledMaxTotalSpawns - 1) {
            LegacySpawnBeaconEntity.prepareNextSpawnTimer(this.ownerRef, componentAccessor);
        }
        final HytaleLogger.Api context = BeaconSpawnController.LOGGER.at(Level.FINE);
        if (context.isEnabled()) {
            final UUIDComponent ownerUuidComponent = componentAccessor.getComponent(this.ownerRef, UUIDComponent.getComponentType());
            assert ownerUuidComponent != null;
            context.log("Removing NPC with reference %s from Spawn Beacon %s", ref, ownerUuidComponent.getUuid());
        }
    }
    
    public boolean hasSlots() {
        return this.spawnedEntities.size() < this.currentScaledMaxTotalSpawns;
    }
    
    public void markNPCUnspawnable(final int roleIndex) {
        this.unspawnableRoles.add(roleIndex);
    }
    
    public void clearUnspawnableNPCs() {
        this.unspawnableRoles.clear();
    }
    
    public void onAllConcurrentSpawned(@Nonnull final ComponentAccessor<EntityStore> componentAccessor) {
        this.spawnsThisRound = 0;
        this.remainingSpawns = 0;
        LegacySpawnBeaconEntity.prepareNextSpawnTimer(this.ownerRef, componentAccessor);
        this.roundStart = true;
    }
    
    static {
        LOGGER = HytaleLogger.forEnclosingClass();
    }
}
