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

package com.hypixel.hytale.server.core.modules.migrations;

import java.util.concurrent.CompletionStage;
import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.longs.LongSet;
import com.hypixel.hytale.server.core.universe.world.storage.IChunkLoader;
import com.hypixel.hytale.server.core.universe.world.storage.IChunkSaver;
import java.util.Iterator;
import java.util.List;
import joptsimple.OptionSet;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.common.util.CompletableFutureUtil;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
import com.hypixel.hytale.math.util.ChunkUtil;
import java.util.concurrent.CompletableFuture;
import com.hypixel.hytale.server.core.universe.world.storage.component.ChunkSavingSystems;
import java.util.logging.Level;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.universe.Universe;
import java.util.concurrent.atomic.AtomicInteger;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import com.hypixel.hytale.server.core.HytaleServer;
import joptsimple.OptionSpec;
import com.hypixel.hytale.server.core.Options;
import com.hypixel.hytale.server.core.event.events.BootEvent;
import it.unimi.dsi.fastutil.objects.Object2ObjectOpenHashMap;
import com.hypixel.hytale.server.core.plugin.JavaPluginInit;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.component.SystemType;
import javax.annotation.Nonnull;
import java.nio.file.Path;
import java.util.function.Function;
import java.util.Map;
import com.hypixel.hytale.common.plugin.PluginManifest;
import com.hypixel.hytale.server.core.plugin.JavaPlugin;

public class MigrationModule extends JavaPlugin
{
    public static final PluginManifest MANIFEST;
    protected static MigrationModule instance;
    @Nonnull
    private final Map<String, Function<Path, Migration>> migrationCtors;
    private SystemType<ChunkStore, ChunkColumnMigrationSystem> chunkColumnMigrationSystem;
    private SystemType<ChunkStore, ChunkSectionMigrationSystem> chunkSectionMigrationSystem;
    
    public MigrationModule(@Nonnull final JavaPluginInit init) {
        super(init);
        this.migrationCtors = new Object2ObjectOpenHashMap<String, Function<Path, Migration>>();
        MigrationModule.instance = this;
    }
    
    public static MigrationModule get() {
        return MigrationModule.instance;
    }
    
    @Override
    protected void setup() {
        this.getEventRegistry().register(BootEvent.class, event -> {
            if (!Options.getOptionSet().has(Options.MIGRATIONS)) {
                return;
            }
            else {
                this.runMigrations();
                HytaleServer.get().shutdownServer();
                return;
            }
        });
        this.chunkColumnMigrationSystem = this.getChunkStoreRegistry().registerSystemType((Class<? super ChunkColumnMigrationSystem>)ChunkColumnMigrationSystem.class);
        this.chunkSectionMigrationSystem = this.getChunkStoreRegistry().registerSystemType((Class<? super ChunkSectionMigrationSystem>)ChunkSectionMigrationSystem.class);
    }
    
    public SystemType<ChunkStore, ChunkColumnMigrationSystem> getChunkColumnMigrationSystem() {
        return this.chunkColumnMigrationSystem;
    }
    
    public SystemType<ChunkStore, ChunkSectionMigrationSystem> getChunkSectionMigrationSystem() {
        return this.chunkSectionMigrationSystem;
    }
    
    public void register(final String id, final Function<Path, Migration> migration) {
        this.migrationCtors.put(id, migration);
    }
    
    public void runMigrations() {
        final OptionSet optionSet = Options.getOptionSet();
        final List<String> worldsToMigrate = optionSet.has(Options.MIGRATE_WORLDS) ? optionSet.valuesOf(Options.MIGRATE_WORLDS) : null;
        final Map<String, Path> migrationMap = Options.getOptionSet().valueOf(Options.MIGRATIONS);
        final List<Migration> migrations = new ObjectArrayList<Migration>();
        migrationMap.forEach((s, path) -> {
            final Function<Path, Migration> migrationCtor = this.migrationCtors.get(s);
            if (migrationCtor == null) {
                return;
            }
            else {
                migrations.add(migrationCtor.apply(path));
                return;
            }
        });
        if (migrations.isEmpty()) {
            return;
        }
        final AtomicInteger worldsCount = new AtomicInteger();
        for (final World world : Universe.get().getWorlds().values()) {
            final String worldName = world.getName();
            if (worldsToMigrate != null && !worldsToMigrate.contains(worldName)) {
                continue;
            }
            worldsCount.incrementAndGet();
            this.getLogger().at(Level.INFO).log("Starting to migrate world '%s'...", worldName);
            final ChunkStore chunkComponentStore = world.getChunkStore();
            final IChunkSaver saver = chunkComponentStore.getSaver();
            final IChunkLoader loader = chunkComponentStore.getLoader();
            world.execute(() -> {
                final ChunkSavingSystems.Data data = chunkComponentStore.getStore().getResource(ChunkStore.SAVE_RESOURCE);
                data.isSaving = false;
                data.waitForSavingChunks().whenComplete((aVoid, throwable) -> {
                    try {
                        final LongSet chunks = loader.getIndexes();
                        this.getLogger().at(Level.INFO).log("Found %d chunks in world '%s'. Starting iteration...", chunks.size(), worldName);
                        final List<CompletableFuture<?>> futures = new ObjectArrayList<CompletableFuture<?>>(chunks.size());
                        final LongIterator iterator2 = chunks.iterator();
                        while (iterator2.hasNext()) {
                            final long index = iterator2.nextLong();
                            final int chunkX = ChunkUtil.xOfChunkIndex(index);
                            final int chunkZ = ChunkUtil.zOfChunkIndex(index);
                            futures.add(loader.loadHolder(chunkX, chunkZ).thenCompose(holder -> {
                                if (holder == null) {
                                    return (CompletionStage<?>)(CompletionStage<?>)CompletableFuture.completedFuture((Object)null);
                                }
                                else {
                                    final WorldChunk chunk = holder.getComponent(WorldChunk.getComponentType());
                                    migrations.forEach(migration -> migration.run(chunk));
                                    if (chunk.getNeedsSaving()) {
                                        return (CompletionStage<?>)(CompletionStage<?>)saver.saveHolder(chunkX, chunkZ, holder);
                                    }
                                    else {
                                        return (CompletionStage<?>)(CompletionStage<?>)CompletableFuture.completedFuture((Object)null);
                                    }
                                }
                            }));
                        }
                        try {
                            CompletableFutureUtil.joinWithProgress(futures, (value, completed, max) -> this.getLogger().at(Level.INFO).log("Scanning + Migrating world '%s': %.2d% (%d of %d)", worldName, value, completed, max), 250, 750);
                        }
                        catch (final InterruptedException e) {
                            this.getLogger().at(Level.SEVERE).withCause(e).log("Interrupted while loading chunks:");
                            Thread.currentThread().interrupt();
                        }
                    }
                    catch (final Throwable t2) {
                        this.getLogger().at(Level.SEVERE).withCause(t2).log("Failed to migrate chunks!");
                    }
                    finally {
                        data.isSaving = true;
                        this.getLogger().at(Level.INFO).log("%d world(s) left to migrate.", worldsCount.decrementAndGet());
                    }
                });
            });
        }
    }
    
    static {
        MANIFEST = PluginManifest.corePlugin(MigrationModule.class).build();
    }
}
