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

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

import com.hypixel.hytale.component.CommandBuffer;
import com.hypixel.hytale.component.ArchetypeChunk;
import com.hypixel.hytale.server.spawning.wrappers.SpawnWrapper;
import it.unimi.dsi.fastutil.ints.IntOpenHashSet;
import com.hypixel.hytale.server.spawning.world.WorldEnvironmentSpawnData;
import com.hypixel.hytale.server.spawning.world.component.SpawnJobData;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.server.core.modules.time.WorldTimeResource;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.component.query.Query;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.component.ComponentAccessor;
import com.hypixel.hytale.server.npc.entities.NPCEntity;
import com.hypixel.hytale.server.spawning.world.component.WorldSpawnData;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import com.hypixel.hytale.assetstore.map.IndexedLookupTableAssetMap;
import com.hypixel.hytale.server.spawning.assets.spawns.config.RoleSpawnParameters;
import com.hypixel.hytale.server.npc.NPCPlugin;
import java.util.logging.Level;
import com.hypixel.hytale.server.spawning.SpawningPlugin;
import com.hypixel.hytale.server.core.asset.type.environment.config.Environment;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Iterator;
import it.unimi.dsi.fastutil.ints.IntIterator;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.universe.Universe;
import it.unimi.dsi.fastutil.ints.Int2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.Long2IntOpenHashMap;
import it.unimi.dsi.fastutil.ints.IntSet;
import it.unimi.dsi.fastutil.ints.Int2ObjectMap;
import it.unimi.dsi.fastutil.longs.Long2IntMap;
import com.hypixel.fastutil.ints.Int2ObjectConcurrentHashMap;
import com.hypixel.hytale.server.spawning.assets.spawns.config.WorldNPCSpawn;
import com.hypixel.hytale.server.spawning.managers.SpawnManager;

public class WorldSpawnManager extends SpawnManager<WorldSpawnWrapper, WorldNPCSpawn>
{
    protected final Int2ObjectConcurrentHashMap<EnvironmentSpawnParameters> environmentSpawnParametersMap;
    protected final Long2IntMap npcEnvCombinations;
    protected final Int2ObjectMap<IntSet> npcTypesPerEnvironment;
    static final /* synthetic */ boolean $assertionsDisabled;
    
    public WorldSpawnManager() {
        this.environmentSpawnParametersMap = new Int2ObjectConcurrentHashMap<EnvironmentSpawnParameters>();
        this.npcEnvCombinations = new Long2IntOpenHashMap();
        this.npcTypesPerEnvironment = new Int2ObjectOpenHashMap<IntSet>();
        this.npcEnvCombinations.defaultReturnValue(Integer.MIN_VALUE);
    }
    
    @Nullable
    @Override
    public WorldSpawnWrapper removeSpawnWrapper(final int spawnConfigurationIndex) {
        final WorldSpawnWrapper spawnWrapper = super.removeSpawnWrapper(spawnConfigurationIndex);
        if (spawnWrapper == null) {
            return null;
        }
        final IntSet environments = spawnWrapper.getSpawn().getEnvironmentIds();
        for (final int environmentIndex : environments) {
            final EnvironmentSpawnParameters envConfigs = this.environmentSpawnParametersMap.get(environmentIndex);
            if (envConfigs != null) {
                envConfigs.getSpawnWrappers().remove(spawnWrapper);
            }
        }
        for (final int npcIndex : spawnWrapper.getRoles().keySet()) {
            for (final World world : Universe.get().getWorlds().values()) {
                onRoleRemoved(world, npcIndex, environments);
            }
            for (final Integer environment : environments) {
                this.removeCombination(npcIndex, environment);
            }
        }
        return spawnWrapper;
    }
    
    @Override
    public boolean addSpawnWrapper(@Nonnull final WorldSpawnWrapper spawnWrapper) {
        final WorldNPCSpawn spawn = spawnWrapper.getSpawn();
        final IndexedLookupTableAssetMap<String, Environment> assetMap = Environment.getAssetMap();
        final IntSet environments = spawnWrapper.getSpawn().getEnvironmentIds();
        final Int2ObjectMap<RoleSpawnParameters> npcs = spawnWrapper.getRoles();
        final int spawnConfigIndex = spawnWrapper.getSpawnIndex();
        for (final Integer npcIndex : npcs.keySet()) {
            for (final Integer environmentIndex : environments) {
                if (this.haveCombination(npcIndex, environmentIndex)) {
                    SpawningPlugin.get().getLogger().at(Level.SEVERE).log("Spawning Configuration %s can't be utilised: Combination NPC %s with Environment %s defined before in configuration %s", spawn.getId(), NPCPlugin.get().getName(npcIndex), assetMap.getAsset(environmentIndex).getId(), this.getCombination(npcIndex, environmentIndex));
                    return false;
                }
            }
        }
        for (final Integer environmentIndex2 : environments) {
            EnvironmentSpawnParameters environmentSpawnParameters = this.environmentSpawnParametersMap.get(environmentIndex2);
            if (environmentSpawnParameters == null) {
                environmentSpawnParameters = this.createEnvironmentSpawnParameters(environmentIndex2, assetMap.getAsset(environmentIndex2));
            }
            environmentSpawnParameters.getSpawnWrappers().add(spawnWrapper);
        }
        for (final World world : Universe.get().getWorlds().values()) {
            for (final Int2ObjectMap.Entry<RoleSpawnParameters> roleSpawnParametersEntry : npcs.int2ObjectEntrySet()) {
                final int npcIndex2 = roleSpawnParametersEntry.getIntKey();
                final RoleSpawnParameters roleSpawnParameters = roleSpawnParametersEntry.getValue();
                onRoleAdded(world, npcIndex2, environments, spawnWrapper, roleSpawnParameters);
            }
        }
        for (final Integer npcIndex : npcs.keySet()) {
            for (final Integer environmentIndex : environments) {
                this.addCombination(npcIndex, environmentIndex, spawnConfigIndex);
            }
        }
        super.addSpawnWrapper(spawnWrapper);
        return true;
    }
    
    public IntSet getRolesForEnvironment(final int environment) {
        return this.npcTypesPerEnvironment.get(environment);
    }
    
    @Nonnull
    public EnvironmentSpawnParameters createEnvironmentSpawnParameters(final int environmentIndex, @Nullable final Environment environment) {
        final EnvironmentSpawnParameters environmentSpawnParameters = new EnvironmentSpawnParameters((environment != null) ? environment.getSpawnDensity() : 0.0);
        this.environmentSpawnParametersMap.put(environmentIndex, environmentSpawnParameters);
        return environmentSpawnParameters;
    }
    
    public EnvironmentSpawnParameters getEnvironmentSpawnParameters(final int environmentIndex) {
        return this.environmentSpawnParametersMap.get(environmentIndex);
    }
    
    public void updateSpawnParameters(final int environmentIndex, @Nullable final Environment environment) {
        final EnvironmentSpawnParameters spawnParameters = this.getEnvironmentSpawnParameters(environmentIndex);
        if (spawnParameters == null) {
            this.createEnvironmentSpawnParameters(environmentIndex, environment);
        }
        else {
            spawnParameters.setDensity((environment != null) ? environment.getSpawnDensity() : 0.0);
        }
    }
    
    public void rebuildConfigurations(@Nullable final IntSet changeSet) {
        if (changeSet == null || changeSet.isEmpty()) {
            return;
        }
        untrackNPCs(changeSet);
        int setupCount = 0;
        for (final Integer configIndex : changeSet) {
            this.removeSpawnWrapper(configIndex);
            final WorldNPCSpawn spawn = WorldNPCSpawn.getAssetMap().getAssetOrDefault(configIndex, null);
            if (spawn == null) {
                continue;
            }
            if (!this.addSpawnWrapper(new WorldSpawnWrapper(spawn))) {
                continue;
            }
            ++setupCount;
        }
        trackNPCs(changeSet);
        SpawningPlugin.get().getLogger().at(Level.INFO).log("Successfully rebuilt %s world spawn configurations", setupCount);
    }
    
    public static void trackNPCs(@Nonnull final IntSet spawnConfigs) {
        Universe.get().getWorlds().forEach((name, world) -> world.execute(() -> {
            final Store<EntityStore> store = world.getEntityStore().getStore();
            final WorldSpawnData worldSpawnData = store.getResource(WorldSpawnData.getResourceType());
            store.forEachChunk(NPCEntity.getComponentType(), (archetypeChunk, commandBuffer) -> {
                for (int index = 0; index < archetypeChunk.size(); ++index) {
                    final NPCEntity npc = archetypeChunk.getComponent(index, NPCEntity.getComponentType());
                    final int spawnConfiguration = npc.getSpawnConfiguration();
                    if (spawnConfiguration >= 0) {
                        if (!(!spawnConfigs.contains(spawnConfiguration))) {
                            final boolean isTracked = npc.updateSpawnTrackingState((boolean)(1 != 0));
                            if (!isTracked) {
                                worldSpawnData.trackNPC(npc.getEnvironment(), npc.getRoleIndex(), 1, world, commandBuffer);
                            }
                        }
                    }
                }
            });
        }));
    }
    
    public void untrackNPCs(final int spawnConfig) {
        if (spawnConfig < 0) {
            return;
        }
        Universe.get().getWorlds().forEach((name, world) -> world.execute(() -> world.getEntityStore().getStore().forEachChunk(NPCEntity.getComponentType(), (archetypeChunk, commandBuffer) -> {
            for (int index = 0; index < archetypeChunk.size(); ++index) {
                final NPCEntity npc = archetypeChunk.getComponent(index, NPCEntity.getComponentType());
                if (npc.getSpawnConfiguration() == spawnConfig) {
                    untrackNPC(world, npc);
                }
            }
        })));
    }
    
    public static void untrackNPCs(@Nonnull final IntSet spawnConfigs) {
        Universe.get().getWorlds().forEach((name, world) -> world.execute(() -> world.getEntityStore().getStore().forEachChunk(NPCEntity.getComponentType(), (archetypeChunk, commandBuffer) -> {
            for (int index = 0; index < archetypeChunk.size(); ++index) {
                final NPCEntity npc = archetypeChunk.getComponent(index, NPCEntity.getComponentType());
                final int spawnConfiguration = npc.getSpawnConfiguration();
                if (spawnConfiguration >= 0) {
                    if (!(!spawnConfigs.contains(spawnConfiguration))) {
                        untrackNPC(world, npc);
                    }
                }
            }
        })));
    }
    
    public static void onEnvironmentChanged() {
        Universe.get().getWorlds().forEach((name, world) -> world.execute(() -> onEnvironmentChanged(world)));
    }
    
    private static void untrackNPC(@Nonnull final World world, @Nonnull final NPCEntity npc) {
        final boolean isTracked = npc.updateSpawnTrackingState(false);
        if (!isTracked) {
            return;
        }
        final WorldSpawnData worldSpawnData = world.getEntityStore().getStore().getResource(WorldSpawnData.getResourceType());
        worldSpawnData.untrackNPC(npc.getEnvironment(), npc.getRoleIndex(), 1);
    }
    
    private static void onEnvironmentChanged(@Nonnull final World world) {
        final Store<EntityStore> store = world.getEntityStore().getStore();
        final Store<ChunkStore> chunkStore = world.getChunkStore().getStore();
        final WorldTimeResource worldTimeResource = store.getResource(WorldTimeResource.getResourceType());
        final WorldSpawnData worldSpawnData = store.getResource(WorldSpawnData.getResourceType());
        worldSpawnData.forEachEnvironmentSpawnData(worldEnvironmentSpawnData -> {
            final EnvironmentSpawnParameters environmentSpawnParameters = SpawningPlugin.get().getWorldEnvironmentSpawnParameters(worldEnvironmentSpawnData.getEnvironmentIndex());
            if (environmentSpawnParameters != null) {
                worldEnvironmentSpawnData.setDensity(environmentSpawnParameters.getSpawnDensity(), chunkStore);
                for (final WorldSpawnWrapper config : environmentSpawnParameters.getSpawnWrappers()) {
                    worldEnvironmentSpawnData.updateNPCs(config, world);
                }
                final int moonPhase = worldTimeResource.getMoonPhase();
                worldEnvironmentSpawnData.recalculateWeight(moonPhase);
                worldEnvironmentSpawnData.resetUnspawnable();
            }
            else {
                worldEnvironmentSpawnData.setDensity(0.0, chunkStore);
                worldEnvironmentSpawnData.clearNPCs();
            }
            return;
        });
        worldSpawnData.recalculateWorldCount();
    }
    
    private static void onRoleRemoved(@Nonnull final World world, final int roleIndex, @Nonnull final IntSet environments) {
        world.execute(() -> {
            world.getChunkStore().getStore().forEachEntityParallel(SpawnJobData.getComponentType(), (index, chunk, commandBuffer) -> {
                final SpawnJobData spawnJobData = chunk.getComponent(index, SpawnJobData.getComponentType());
                if (!WorldSpawnManager.$assertionsDisabled && spawnJobData == null) {
                    throw new AssertionError();
                }
                else {
                    if (spawnJobData.getRoleIndex() == roleIndex) {
                        spawnJobData.terminate();
                    }
                    return;
                }
            });
            final Store<EntityStore> store = world.getEntityStore().getStore();
            final WorldSpawnData worldSpawnData = store.getResource(WorldSpawnData.getResourceType());
            for (final int environmentIndex : environments) {
                final WorldEnvironmentSpawnData worldEnvironmentSpawnStats = worldSpawnData.getWorldEnvironmentSpawnData(environmentIndex);
                if (worldEnvironmentSpawnStats != null) {
                    worldEnvironmentSpawnStats.removeNPC(roleIndex, store);
                }
            }
        });
    }
    
    private static void onRoleAdded(@Nonnull final World world, final int roleIndex, @Nonnull final IntSet environments, final WorldSpawnWrapper spawnWrapper, @Nonnull final RoleSpawnParameters spawnParams) {
        world.execute(() -> {
            final Store<EntityStore> store = world.getEntityStore().getStore();
            final WorldSpawnData worldSpawnData = store.getResource(WorldSpawnData.getResourceType());
            for (final int environmentIndex : environments) {
                worldSpawnData.getOrCreateWorldEnvironmentSpawnData(environmentIndex, world, store).addNPC(roleIndex, spawnWrapper, spawnParams, world, store);
            }
            onEnvironmentChanged();
        });
    }
    
    private static long combinedIndex(final int npc, final int environment) {
        return ((long)npc << 32) + environment;
    }
    
    private boolean haveCombination(final int npc, final int environment) {
        return this.npcEnvCombinations.containsKey(combinedIndex(npc, environment));
    }
    
    private void addCombination(final int npc, final int environment, final int config) {
        this.npcEnvCombinations.put(combinedIndex(npc, environment), config);
        this.npcTypesPerEnvironment.computeIfAbsent(environment, i -> new IntOpenHashSet()).add(npc);
    }
    
    private void removeCombination(final int npc, final int environment) {
        this.npcEnvCombinations.remove(combinedIndex(npc, environment));
        this.npcTypesPerEnvironment.get(environment).remove(npc);
    }
    
    private String getCombination(final int npc, final int environment) {
        return WorldNPCSpawn.getAssetMap().getAsset(this.npcEnvCombinations.get(combinedIndex(npc, environment))).getId();
    }
}
