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

package com.hypixel.hytale.builtin.hytalegenerator.plugin;

import java.util.HashMap;
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.Executors;
import java.util.Iterator;
import com.hypixel.hytale.builtin.hytalegenerator.PropField;
import java.util.Set;
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages.NStage;
import java.util.List;
import com.hypixel.hytale.builtin.hytalegenerator.material.SolidMaterial;
import com.hypixel.hytale.builtin.hytalegenerator.biomemap.BiomeMap;
import java.util.HashSet;
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages.NEnvironmentStage;
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages.NTintStage;
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages.NPropStage;
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NBuffer;
import java.util.function.Supplier;
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NBufferType;
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NEntityBuffer;
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NVoxelBuffer;
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages.NTerrainStage;
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NSimplePixelBuffer;
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages.NBiomeDistanceStage;
import com.hypixel.hytale.builtin.hytalegenerator.biome.BiomeType;
import com.hypixel.hytale.builtin.hytalegenerator.framework.interfaces.functions.BiCarta;
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.type.NParametrizedBufferType;
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.bufferbundle.buffers.NCountedPixelBuffer;
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.stages.NBiomeStage;
import java.util.Comparator;
import java.util.Collection;
import java.util.ArrayList;
import com.hypixel.hytale.builtin.hytalegenerator.material.MaterialCache;
import com.hypixel.hytale.builtin.hytalegenerator.seed.SeedBox;
import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer;
import com.hypixel.hytale.builtin.hytalegenerator.newsystem.NStagedChunkGenerator;
import com.hypixel.hytale.builtin.hytalegenerator.assets.SettingsAsset;
import com.hypixel.hytale.builtin.hytalegenerator.assets.worldstructures.WorldStructureAsset;
import com.hypixel.hytale.server.core.universe.world.worldgen.IWorldGen;
import com.hypixel.hytale.server.core.universe.world.events.RemoveWorldEvent;
import com.hypixel.hytale.server.core.command.system.AbstractCommand;
import com.hypixel.hytale.builtin.hytalegenerator.commands.ViewportCommand;
import com.hypixel.hytale.server.core.universe.world.worldgen.provider.IWorldGenProvider;
import java.util.function.Function;
import java.util.function.BiConsumer;
import com.hypixel.hytale.codec.KeyedCodec;
import com.hypixel.hytale.codec.Codec;
import com.hypixel.hytale.codec.builder.BuilderCodec;
import com.hypixel.hytale.builtin.hytalegenerator.chunkgenerator.FallbackGenerator;
import com.hypixel.hytale.builtin.hytalegenerator.LoggerUtil;
import java.util.concurrent.Executor;
import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedChunk;
import java.util.concurrent.CompletableFuture;
import javax.annotation.Nonnull;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Semaphore;
import com.hypixel.hytale.builtin.hytalegenerator.chunkgenerator.ChunkGenerator;
import com.hypixel.hytale.builtin.hytalegenerator.chunkgenerator.ChunkRequest;
import java.util.Map;
import com.hypixel.hytale.builtin.hytalegenerator.assets.AssetManager;
import com.hypixel.hytale.server.core.plugin.JavaPlugin;

public class HytaleGenerator extends JavaPlugin
{
    private AssetManager assetManager;
    private Runnable assetReloadListener;
    private final Map<ChunkRequest.GeneratorProfile, ChunkGenerator> generators;
    private final Semaphore chunkGenerationSemaphore;
    private int concurrency;
    private ExecutorService mainExecutor;
    private ThreadPoolExecutor concurrentExecutor;
    
    @Override
    protected void start() {
        super.start();
        if (this.mainExecutor == null) {
            this.loadExecutors(this.assetManager.getSettingsAsset());
        }
        if (this.assetReloadListener == null) {
            this.assetReloadListener = (() -> this.reloadGenerators());
            this.assetManager.registerReloadListener(this.assetReloadListener);
        }
    }
    
    @Nonnull
    public CompletableFuture<GeneratedChunk> submitChunkRequest(@Nonnull final ChunkRequest request) {
        return CompletableFuture.supplyAsync(() -> {
            try {
                this.chunkGenerationSemaphore.acquireUninterruptibly();
                final ChunkGenerator generator = this.getGenerator(request.generatorProfile());
                return generator.generate(request.arguments());
            }
            finally {
                this.chunkGenerationSemaphore.release();
            }
        }, this.mainExecutor).handle((r, e) -> {
            if (e == null) {
                return r;
            }
            else {
                LoggerUtil.logException("generation of the chunk with request " + String.valueOf(request), e, LoggerUtil.getLogger());
                return FallbackGenerator.INSTANCE.generate(request.arguments());
            }
        });
    }
    
    @Override
    protected void setup() {
        this.assetManager = new AssetManager(this.getEventRegistry(), this.getLogger());
        final BuilderCodec<HandleProvider> generatorProvider = BuilderCodec.builder(HandleProvider.class, () -> new HandleProvider(this)).documentation("The standard generator for Hytale.").append(new KeyedCodec("WorldStructure", Codec.STRING), HandleProvider::setWorldStructureName, HandleProvider::getWorldStructureName).documentation("The world structure to be used for this world.").add().build();
        IWorldGenProvider.CODEC.register("HytaleGenerator", (Class<?>)HandleProvider.class, (C)generatorProvider);
        this.getCommandRegistry().registerCommand(new ViewportCommand(this.assetManager));
        this.getEventRegistry().registerGlobal(RemoveWorldEvent.class, event -> {
            final IWorldGen generator = event.getWorld().getChunkStore().getGenerator();
            if (generator instanceof final Handle handle) {
                this.generators.remove(handle.getProfile());
            }
        });
    }
    
    @Nonnull
    public NStagedChunkGenerator createStagedChunkGenerator(@Nonnull final ChunkRequest.GeneratorProfile generatorProfile, @Nonnull final WorldStructureAsset worldStructureAsset, @Nonnull final SettingsAsset settingsAsset) {
        final WorkerIndexer workerIndexer = new WorkerIndexer(this.concurrency);
        final SeedBox seed = new SeedBox(generatorProfile.seed());
        final MaterialCache materialCache = new MaterialCache();
        final BiomeMap<SolidMaterial> biomeMap = worldStructureAsset.buildBiomeMap(new WorldStructureAsset.Argument(materialCache, seed, workerIndexer));
        worldStructureAsset.cleanUp();
        final NStagedChunkGenerator.Builder generatorBuilder = new NStagedChunkGenerator.Builder();
        final List<BiomeType> allBiomes = biomeMap.allPossibleValues();
        final List<Integer> allRuntimes = new ArrayList<Integer>(getAllPossibleRuntimeIndices(allBiomes));
        allRuntimes.sort(Comparator.naturalOrder());
        int bufferTypeIndexCounter = 0;
        final NParametrizedBufferType biome_bufferType = new NParametrizedBufferType("Biome", bufferTypeIndexCounter++, (Class)NBiomeStage.bufferClass, (Class)NBiomeStage.biomeTypeClass, () -> new NCountedPixelBuffer((Class<Object>)NBiomeStage.biomeTypeClass));
        final NStage biomeStage = new NBiomeStage("BiomeStage", biome_bufferType, biomeMap);
        generatorBuilder.appendStage(biomeStage);
        final NParametrizedBufferType biomeDistance_bufferType = new NParametrizedBufferType("BiomeDistance", bufferTypeIndexCounter++, (Class)NBiomeDistanceStage.biomeDistanceBufferClass, (Class)NBiomeDistanceStage.biomeDistanceClass, () -> new NSimplePixelBuffer((Class<Object>)NBiomeDistanceStage.biomeDistanceClass));
        final int MAX_BIOME_DISTANCE_RADIUS = 512;
        final int interpolationRadius = Math.clamp(worldStructureAsset.getBiomeTransitionDistance() / 2, 0, 512);
        final int biomeEdgeRadius = Math.clamp(worldStructureAsset.getMaxBiomeEdgeDistance(), 0, 512);
        final int maxDistance = Math.max(interpolationRadius, biomeEdgeRadius);
        final NStage biomeDistanceStage = new NBiomeDistanceStage("BiomeDistanceStage", biome_bufferType, biomeDistance_bufferType, maxDistance);
        generatorBuilder.appendStage(biomeDistanceStage);
        int materialBufferIndexCounter = 0;
        NParametrizedBufferType material0_bufferType = generatorBuilder.MATERIAL_OUTPUT_BUFFER_TYPE;
        if (!allRuntimes.isEmpty()) {
            material0_bufferType = new NParametrizedBufferType("Material" + materialBufferIndexCounter, bufferTypeIndexCounter++, (Class)NTerrainStage.materialBufferClass, (Class)NTerrainStage.materialClass, () -> new NVoxelBuffer((Class<Object>)NTerrainStage.materialClass));
            ++materialBufferIndexCounter;
        }
        final NStage terrainStage = new NTerrainStage("TerrainStage", biome_bufferType, biomeDistance_bufferType, material0_bufferType, interpolationRadius, materialCache, workerIndexer);
        generatorBuilder.appendStage(terrainStage);
        NParametrizedBufferType materialInput_bufferType = material0_bufferType;
        NBufferType entityInput_bufferType = null;
        for (int i = 0; i < allRuntimes.size() - 1; ++i) {
            final int runtime = allRuntimes.get(i);
            final String runtimeString = Integer.toString(runtime);
            final NParametrizedBufferType materialOutput_bufferType = new NParametrizedBufferType("Material" + materialBufferIndexCounter, bufferTypeIndexCounter++, (Class)NTerrainStage.materialBufferClass, (Class)NTerrainStage.materialClass, () -> new NVoxelBuffer((Class<Object>)NTerrainStage.materialClass));
            final NBufferType entityOutput_bufferType = new NBufferType("Entity" + materialBufferIndexCounter, bufferTypeIndexCounter++, (Class)NEntityBuffer.class, (Supplier<NBuffer>)NEntityBuffer::new);
            final NStage propStage = new NPropStage("PropStage" + runtimeString, biome_bufferType, biomeDistance_bufferType, materialInput_bufferType, entityInput_bufferType, materialOutput_bufferType, entityOutput_bufferType, materialCache, allBiomes, runtime);
            generatorBuilder.appendStage(propStage);
            materialInput_bufferType = materialOutput_bufferType;
            entityInput_bufferType = entityOutput_bufferType;
            ++materialBufferIndexCounter;
        }
        if (!allRuntimes.isEmpty()) {
            final int runtime2 = allRuntimes.getLast();
            final String runtimeString2 = Integer.toString(runtime2);
            final NStage propStage2 = new NPropStage("PropStage" + runtimeString2, biome_bufferType, biomeDistance_bufferType, materialInput_bufferType, entityInput_bufferType, generatorBuilder.MATERIAL_OUTPUT_BUFFER_TYPE, generatorBuilder.ENTITY_OUTPUT_BUFFER_TYPE, materialCache, allBiomes, runtime2);
            generatorBuilder.appendStage(propStage2);
        }
        final NStage tintStage = new NTintStage("TintStage", biome_bufferType, generatorBuilder.TINT_OUTPUT_BUFFER_TYPE);
        generatorBuilder.appendStage(tintStage);
        final NStage environmentStage = new NEnvironmentStage("EnvironmentStage", biome_bufferType, generatorBuilder.ENVIRONMENT_OUTPUT_BUFFER_TYPE);
        generatorBuilder.appendStage(environmentStage);
        final double bufferCapacityFactor = Math.max(0.0, settingsAsset.getBufferCapacityFactor());
        final double targetViewDistance = Math.max(0.0, settingsAsset.getTargetViewDistance());
        final double targetPlayerCount = Math.max(0.0, settingsAsset.getTargetPlayerCount());
        final Set<Integer> statsCheckpoints = new HashSet<Integer>(settingsAsset.getStatsCheckpoints());
        final NStagedChunkGenerator generator = generatorBuilder.withStats("WorldStructure Name: " + generatorProfile.worldStructureName(), statsCheckpoints).withMaterialCache(materialCache).withConcurrentExecutor(this.concurrentExecutor, workerIndexer).withBufferCapacity(bufferCapacityFactor, targetViewDistance, targetPlayerCount).build();
        return generator;
    }
    
    @Nonnull
    private static Set<Integer> getAllPossibleRuntimeIndices(@Nonnull final List<BiomeType> biomes) {
        final Set<Integer> allRuntimes = new HashSet<Integer>();
        for (final BiomeType biome : biomes) {
            for (final PropField propField : biome.getPropFields()) {
                allRuntimes.add(propField.getRuntime());
            }
        }
        return allRuntimes;
    }
    
    @Nonnull
    private ChunkGenerator getGenerator(@Nonnull final ChunkRequest.GeneratorProfile profile) {
        ChunkGenerator generator = this.generators.get(profile);
        if (generator == null) {
            if (profile.worldStructureName() == null) {
                LoggerUtil.getLogger().warning("World Structure asset not loaded.");
                return FallbackGenerator.INSTANCE;
            }
            final WorldStructureAsset worldStructureAsset = this.assetManager.getWorldStructureAsset(profile.worldStructureName());
            if (worldStructureAsset == null) {
                LoggerUtil.getLogger().warning("World Structure asset not found: " + profile.worldStructureName());
                return FallbackGenerator.INSTANCE;
            }
            final SettingsAsset settingsAsset = this.assetManager.getSettingsAsset();
            if (settingsAsset == null) {
                LoggerUtil.getLogger().warning("Settings asset not found.");
                return FallbackGenerator.INSTANCE;
            }
            generator = this.createStagedChunkGenerator(profile, worldStructureAsset, settingsAsset);
            this.generators.put(profile, generator);
        }
        return generator;
    }
    
    private void loadExecutors(@Nonnull final SettingsAsset settingsAsset) {
        final int newConcurrency = getConcurrency(settingsAsset);
        if (newConcurrency == this.concurrency && this.mainExecutor != null && this.concurrentExecutor != null) {
            return;
        }
        this.concurrency = newConcurrency;
        if (this.mainExecutor == null) {
            this.mainExecutor = Executors.newSingleThreadExecutor();
        }
        if (this.concurrentExecutor != null && !this.concurrentExecutor.isShutdown()) {
            try {
                this.concurrentExecutor.shutdown();
                if (!this.concurrentExecutor.awaitTermination(1L, TimeUnit.MINUTES)) {}
            }
            catch (final InterruptedException e) {
                throw new RuntimeException(e);
            }
        }
        this.concurrentExecutor = new ThreadPoolExecutor(this.concurrency, this.concurrency, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>(), r -> {
            final Thread t = new Thread(r, "HytaleGenerator-Worker");
            t.setPriority(1);
            t.setDaemon(true);
            return t;
        });
        if (this.mainExecutor == null || this.mainExecutor.isShutdown()) {
            this.mainExecutor = Executors.newSingleThreadExecutor();
        }
    }
    
    private static int getConcurrency(@Nonnull final SettingsAsset settingsAsset) {
        final int concurrencySetting = settingsAsset.getCustomConcurrency();
        final int availableProcessors = Runtime.getRuntime().availableProcessors();
        int value = 1;
        if (concurrencySetting < 1) {
            value = Math.max(availableProcessors, 1);
        }
        else {
            if (concurrencySetting > availableProcessors) {
                LoggerUtil.getLogger().warning("Concurrency setting " + concurrencySetting + " exceeds available processors " + availableProcessors);
            }
            value = concurrencySetting;
        }
        return value;
    }
    
    private void reloadGenerators() {
        try {
            this.chunkGenerationSemaphore.acquireUninterruptibly();
            this.loadExecutors(this.assetManager.getSettingsAsset());
            this.generators.clear();
        }
        finally {
            this.chunkGenerationSemaphore.release();
        }
        LoggerUtil.getLogger().info("Reloaded HytaleGenerator.");
    }
    
    public HytaleGenerator(@Nonnull final JavaPluginInit init) {
        super(init);
        this.generators = new HashMap<ChunkRequest.GeneratorProfile, ChunkGenerator>();
        this.chunkGenerationSemaphore = new Semaphore(1);
    }
}
