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

package com.hypixel.hytale.builtin.instances.command;

import java.io.IOException;
import com.hypixel.hytale.server.core.universe.world.World;
import com.hypixel.hytale.server.core.universe.world.storage.IChunkSaver;
import com.hypixel.hytale.server.core.universe.world.storage.IChunkLoader;
import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.longs.LongSet;
import java.util.BitSet;
import com.hypixel.hytale.component.SystemType;
import com.hypixel.hytale.component.Store;
import com.hypixel.hytale.component.ComponentRegistry;
import java.util.concurrent.CompletionStage;
import java.util.function.Function;
import com.hypixel.hytale.sneakythrow.SneakyThrow;
import com.hypixel.hytale.server.core.modules.migrations.ChunkSectionMigrationSystem;
import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn;
import com.hypixel.hytale.server.core.modules.entity.EntityModule;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.server.core.universe.world.chunk.EntityChunk;
import com.hypixel.hytale.component.RemoveReason;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.component.AddReason;
import com.hypixel.hytale.server.core.modules.migrations.ChunkColumnMigrationSystem;
import com.hypixel.hytale.server.core.modules.migrations.MigrationModule;
import com.hypixel.hytale.math.util.ChunkUtil;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import java.util.concurrent.Executor;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.server.core.universe.world.storage.component.ChunkSavingSystems;
import com.hypixel.hytale.builtin.instances.removal.RemovalCondition;
import com.hypixel.hytale.builtin.instances.config.InstanceWorldConfig;
import com.hypixel.hytale.protocol.GameMode;
import java.util.UUID;
import com.hypixel.hytale.server.core.universe.Universe;
import java.nio.file.Path;
import java.util.List;
import com.hypixel.hytale.server.core.Message;
import com.hypixel.hytale.common.util.CompletableFutureUtil;
import com.hypixel.hytale.server.core.universe.world.WorldConfig;
import java.util.concurrent.atomic.AtomicLong;
import com.hypixel.hytale.builtin.instances.InstancesPlugin;
import java.util.concurrent.CompletableFuture;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.core.command.system.CommandContext;
import com.hypixel.hytale.server.core.command.system.basecommands.AbstractAsyncCommand;

public class InstanceMigrateCommand extends AbstractAsyncCommand
{
    private static final long CHUNK_UPDATE_INTERVAL = 100L;
    
    public InstanceMigrateCommand() {
        super("migrate", "");
    }
    
    @Nonnull
    @Override
    protected CompletableFuture<Void> executeAsync(@Nonnull final CommandContext context) {
        final InstancesPlugin instancePlugin = InstancesPlugin.get();
        final List<String> instancesToMigrate = instancePlugin.getInstanceAssets();
        final CompletableFuture[] futures = new CompletableFuture[instancesToMigrate.size()];
        final AtomicLong chunkCount = new AtomicLong();
        final AtomicLong chunksMigrated = new AtomicLong();
        for (int i = 0; i < instancesToMigrate.size(); ++i) {
            final String asset = instancesToMigrate.get(i);
            final Path instancePath = InstancesPlugin.getInstanceAssetPath(asset);
            final CompletableFuture<WorldConfig> configFuture = WorldConfig.load(instancePath.resolve("instance.bson"));
            (futures[i] = CompletableFutureUtil._catch(configFuture.thenCompose(config -> migrateInstance(context, asset, config, chunkCount, chunksMigrated)))).join();
        }
        return CompletableFuture.allOf((CompletableFuture<?>[])futures).whenComplete((result, throwable) -> {
            if (throwable != null) {
                context.sendMessage(Message.translation("server.commands.instances.migrate.failed").param("error", throwable.getMessage()));
            }
            else {
                context.sendMessage(Message.translation("server.commands.instances.migrate.complete").param("worlds", futures.length).param("chunks", chunkCount.get()));
            }
        });
    }
    
    @Nonnull
    private static CompletableFuture<Void> migrateInstance(@Nonnull final CommandContext context, @Nonnull final String asset, @Nonnull final WorldConfig config, @Nonnull final AtomicLong chunkCount, @Nonnull final AtomicLong chunksMigrated) {
        final Path instancePath = InstancesPlugin.getInstanceAssetPath(asset);
        final Universe universe = Universe.get();
        config.setUuid(UUID.randomUUID());
        config.setSavingPlayers(false);
        config.setIsAllNPCFrozen(true);
        config.setSavingConfig(false);
        config.setTicking(false);
        config.setGameMode(GameMode.Creative);
        config.setDeleteOnRemove(false);
        config.setCompassUpdating(false);
        InstanceWorldConfig.ensureAndGet(config).setRemovalConditions(RemovalCondition.EMPTY);
        config.markChanged();
        final String worldName = "instance-migrate-" + InstancesPlugin.safeName(asset);
        return universe.makeWorld(worldName, instancePath, config, true).thenCompose(world -> {
            final IChunkLoader loader = world.getChunkStore().getLoader();
            final IChunkSaver saver = world.getChunkStore().getSaver();
            return CompletableFuture.supplyAsync(() -> {
                final ChunkStore chunkStore = world.getChunkStore();
                final ChunkSavingSystems.Data data = chunkStore.getStore().getResource(ChunkStore.SAVE_RESOURCE);
                data.isSaving = false;
                return data.waitForSavingChunks();
            }, world).thenCompose(val -> val).thenComposeAsync((Function<? super Object, ? extends CompletionStage<Object>>)SneakyThrow.sneakyFunction(_void -> {
                final LongSet chunks = loader.getIndexes();
                final ObjectArrayList futures = new ObjectArrayList(chunks.size());
                final LongIterator iterator = chunks.iterator();
                while (iterator.hasNext()) {
                    final long chunkIndex = iterator.nextLong();
                    chunkCount.incrementAndGet();
                    final int chunkX = ChunkUtil.xOfChunkIndex(chunkIndex);
                    final int chunkZ = ChunkUtil.zOfChunkIndex(chunkIndex);
                    futures.add(CompletableFutureUtil._catch((CompletableFuture<Void>)loader.loadHolder(chunkX, chunkZ).thenComposeAsync(holder -> {
                        final ComponentRegistry.Data<ChunkStore> data2 = ChunkStore.REGISTRY.getData();
                        final ChunkStore chunkStore2 = world.getChunkStore();
                        final Store<ChunkStore> store = chunkStore2.getStore();
                        boolean shouldSave = 0 != 0;
                        final SystemType<ChunkStore, ChunkColumnMigrationSystem> systemType = MigrationModule.get().getChunkColumnMigrationSystem();
                        final BitSet systemIndexes = data2.getSystemIndexesForType(systemType);
                        int systemIndex = -1;
                        while (true) {
                            systemIndex = systemIndexes.nextSetBit(systemIndex + 1);
                            final Object o2;
                            if (o2 >= 0) {
                                final ChunkColumnMigrationSystem system = data2.getSystem(systemIndex, systemType);
                                if (system.test(ChunkStore.REGISTRY, holder.getArchetype())) {
                                    system.onEntityAdd(holder, AddReason.LOAD, store);
                                    shouldSave = (1 != 0);
                                }
                                else {
                                    continue;
                                }
                            }
                            else {
                                break;
                            }
                        }
                        int systemIndex2 = -1;
                        while (true) {
                            systemIndex2 = systemIndexes.nextSetBit(systemIndex2 + 1);
                            final Object o3;
                            if (o3 >= 0) {
                                final ChunkColumnMigrationSystem system2 = data2.getSystem(systemIndex2, systemType);
                                if (system2.test(ChunkStore.REGISTRY, holder.getArchetype())) {
                                    system2.onEntityRemoved(holder, RemoveReason.REMOVE, store);
                                }
                                else {
                                    continue;
                                }
                            }
                            else {
                                break;
                            }
                        }
                        final EntityChunk entityChunk = holder.getComponent(EntityChunk.getComponentType());
                        if (entityChunk != null && !entityChunk.getEntityHolders().isEmpty()) {
                            final Store<EntityStore> entityStore = world.getEntityStore().getStore();
                            final ComponentRegistry.Data<EntityStore> entityData = EntityStore.REGISTRY.getData();
                            final List<Holder<EntityStore>> entities = entityChunk.getEntityHolders();
                            final SystemType<EntityStore, EntityModule.MigrationSystem> systemType2 = EntityModule.get().getMigrationSystemType();
                            final BitSet systemIndexes2 = entityData.getSystemIndexesForType(systemType2);
                            int systemIndex3 = -1;
                            while (true) {
                                systemIndex3 = systemIndexes2.nextSetBit(systemIndex3 + 1);
                                final Object o4;
                                if (o4 >= 0) {
                                    final EntityModule.MigrationSystem system3 = entityData.getSystem(systemIndex3, systemType2);
                                    for (int i = 0; i < entities.size(); ++i) {
                                        final Holder<EntityStore> section = entities.get(i);
                                        if (system3.test(EntityStore.REGISTRY, section.getArchetype())) {
                                            system3.onEntityAdd(section, AddReason.LOAD, entityStore);
                                            shouldSave = (1 != 0);
                                        }
                                    }
                                }
                                else {
                                    break;
                                }
                            }
                            int systemIndex4 = -1;
                            while (true) {
                                systemIndex4 = systemIndexes2.nextSetBit(systemIndex4 + 1);
                                final Object o5;
                                if (o5 >= 0) {
                                    final EntityModule.MigrationSystem system4 = entityData.getSystem(systemIndex4, systemType2);
                                    for (int j = 0; j < entities.size(); ++j) {
                                        final Holder<EntityStore> section2 = entities.get(j);
                                        if (system4.test(EntityStore.REGISTRY, section2.getArchetype())) {
                                            system4.onEntityRemoved(section2, RemoveReason.REMOVE, entityStore);
                                        }
                                    }
                                }
                                else {
                                    break;
                                }
                            }
                        }
                        final ChunkColumn chunkColumn = holder.getComponent(ChunkColumn.getComponentType());
                        if (chunkColumn != null && chunkColumn.getSectionHolders() != null) {
                            final Holder<ChunkStore>[] sections = chunkColumn.getSectionHolders();
                            final SystemType<ChunkStore, ChunkSectionMigrationSystem> systemType3 = MigrationModule.get().getChunkSectionMigrationSystem();
                            final BitSet systemIndexes3 = data2.getSystemIndexesForType(systemType3);
                            int systemIndex5 = -1;
                            while (true) {
                                systemIndex5 = systemIndexes3.nextSetBit(systemIndex5 + 1);
                                final Object o6;
                                if (o6 >= 0) {
                                    final ChunkSectionMigrationSystem system5 = data2.getSystem(systemIndex5, systemType3);
                                    for (final Holder<ChunkStore> section3 : sections) {
                                        if (system5.test(ChunkStore.REGISTRY, section3.getArchetype())) {
                                            system5.onEntityAdd(section3, AddReason.LOAD, store);
                                            shouldSave = (1 != 0);
                                        }
                                    }
                                }
                                else {
                                    break;
                                }
                            }
                            int systemIndex6 = -1;
                            while (true) {
                                systemIndex6 = systemIndexes3.nextSetBit(systemIndex6 + 1);
                                final Object o7;
                                if (o7 >= 0) {
                                    final ChunkSectionMigrationSystem system6 = data2.getSystem(systemIndex6, systemType3);
                                    for (final Holder<ChunkStore> section4 : sections) {
                                        if (system6.test(ChunkStore.REGISTRY, section4.getArchetype())) {
                                            system6.onEntityRemoved(section4, RemoveReason.REMOVE, store);
                                        }
                                    }
                                }
                                else {
                                    break;
                                }
                            }
                        }
                        if (shouldSave) {
                            return saver.saveHolder(chunkX, chunkZ, holder);
                        }
                        else {
                            return CompletableFuture.completedFuture((Void)null);
                        }
                    }, (Executor)world).whenComplete((v, throwable) -> {
                        final long migratedChunks = chunksMigrated.incrementAndGet();
                        final long max = chunkCount.get();
                        if (migratedChunks % 100L == 0L || migratedChunks == max) {
                            context.sendMessage(Message.translation("server.commands.instances.migrate.update").param("chunks", migratedChunks).param("max", max));
                        }
                        return;
                    })));
                }
                return CompletableFuture.allOf((CompletableFuture<?>[])futures.toArray(CompletableFuture[]::new)).whenCompleteAsync((result, throwable) -> {
                    context.sendMessage(Message.translation("server.commands.instances.migrate.worldDone").param("asset", asset));
                    Universe.get().removeWorld(worldName);
                });
            }));
        });
    }
}
