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

package com.hypixel.hytale.builtin.buildertools.prefabeditor.saving;

import com.hypixel.hytale.server.core.prefab.PrefabSaveException;
import com.hypixel.hytale.logger.HytaleLogger;
import com.hypixel.hytale.server.core.prefab.PrefabStore;
import java.nio.file.FileSystems;
import it.unimi.dsi.fastutil.longs.LongIterator;
import it.unimi.dsi.fastutil.longs.LongSet;
import java.util.ArrayList;
import it.unimi.dsi.fastutil.longs.Long2ObjectOpenHashMap;
import it.unimi.dsi.fastutil.longs.LongOpenHashSet;
import javax.annotation.Nullable;
import com.hypixel.hytale.math.vector.Vector3d;
import java.util.Iterator;
import java.util.List;
import it.unimi.dsi.fastutil.objects.ObjectIterator;
import com.hypixel.hytale.component.ComponentRegistry;
import java.util.Set;
import com.hypixel.hytale.component.Store;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import com.hypixel.hytale.math.vector.VectorBoxUtil;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.server.core.universe.world.chunk.EntityChunk;
import com.hypixel.hytale.builtin.buildertools.BuilderToolsPlugin;
import com.hypixel.hytale.server.core.entity.UUIDComponent;
import com.hypixel.hytale.component.ComponentType;
import com.hypixel.hytale.server.core.modules.entity.component.TransformComponent;
import com.hypixel.hytale.server.core.prefab.PrefabCopyableComponent;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import java.util.UUID;
import java.util.HashSet;
import com.hypixel.hytale.server.core.entity.entities.BlockEntity;
import com.hypixel.hytale.server.core.universe.world.chunk.section.FluidSection;
import com.hypixel.hytale.server.core.universe.world.meta.BlockState;
import com.hypixel.hytale.server.core.blocktype.component.BlockPhysics;
import com.hypixel.hytale.server.core.universe.world.chunk.section.BlockSection;
import com.hypixel.hytale.server.core.universe.world.chunk.ChunkColumn;
import com.hypixel.hytale.server.core.universe.world.chunk.WorldChunk;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.component.Ref;
import it.unimi.dsi.fastutil.longs.Long2ObjectMap;
import com.hypixel.hytale.server.core.Message;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection;
import java.util.concurrent.Executor;
import java.util.concurrent.CompletableFuture;
import com.hypixel.hytale.math.vector.Vector3i;
import java.nio.file.Path;
import com.hypixel.hytale.server.core.universe.world.World;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.core.command.system.CommandSender;

public class PrefabSaver
{
    protected static final String EDITOR_BLOCK = "Editor_Block";
    protected static final String EDITOR_BLOCK_PREFAB_AIR = "Editor_Empty";
    protected static final String EDITOR_BLOCK_PREFAB_ANCHOR = "Editor_Anchor";
    
    @Nonnull
    public static CompletableFuture<Boolean> savePrefab(@Nonnull final CommandSender sender, @Nonnull final World world, @Nonnull final Path pathToSave, @Nonnull final Vector3i anchorPoint, @Nonnull final Vector3i minPoint, @Nonnull final Vector3i maxPoint, @Nonnull final Vector3i pastePosition, @Nonnull final Vector3i originalFileAnchor, @Nonnull final PrefabSaverSettings settings) {
        return copyBlocksAsync(sender, world, anchorPoint, minPoint, maxPoint, pastePosition, originalFileAnchor, settings).thenApplyAsync(blockSelection -> {
            if (blockSelection == null) {
                return Boolean.valueOf(false);
            }
            else {
                return Boolean.valueOf(save(sender, blockSelection, pathToSave, settings));
            }
        }, (Executor)world);
    }
    
    @Nonnull
    private static CompletableFuture<BlockSelection> copyBlocksAsync(@Nonnull final CommandSender sender, @Nonnull final World world, @Nonnull final Vector3i anchorPoint, @Nonnull final Vector3i minPoint, @Nonnull final Vector3i maxPoint, @Nonnull final Vector3i pastePosition, @Nonnull final Vector3i originalFileAnchor, @Nonnull final PrefabSaverSettings settings) {
        final ChunkStore chunkStore = world.getChunkStore();
        final BlockTypeAssetMap<String, BlockType> assetMap = BlockType.getAssetMap();
        final int editorBlock = assetMap.getIndex("Editor_Block");
        if (editorBlock == Integer.MIN_VALUE) {
            sender.sendMessage(Message.translation("server.commands.editprefab.save.error.unknownBlockIdKey").param("key", "Editor_Block".toString()));
            return CompletableFuture.completedFuture((BlockSelection)null);
        }
        final int editorBlockPrefabAir = assetMap.getIndex("Editor_Empty");
        if (editorBlockPrefabAir == Integer.MIN_VALUE) {
            sender.sendMessage(Message.translation("server.commands.editprefab.save.error.unknownBlockIdKey").param("key", "Editor_Empty".toString()));
            return CompletableFuture.completedFuture((BlockSelection)null);
        }
        final int editorBlockPrefabAnchor = assetMap.getIndex("Editor_Anchor");
        if (editorBlockPrefabAnchor == Integer.MIN_VALUE) {
            sender.sendMessage(Message.translation("server.commands.editprefab.save.error.unknownBlockIdKey").param("key", "Editor_Anchor".toString()));
            return CompletableFuture.completedFuture((BlockSelection)null);
        }
        return preloadChunksInSelectionAsync(chunkStore, minPoint, maxPoint).thenApplyAsync(loadedChunks -> copyBlocksWithLoadedChunks(sender, world, anchorPoint, minPoint, maxPoint, pastePosition, originalFileAnchor, settings, loadedChunks, editorBlock, editorBlockPrefabAir, editorBlockPrefabAnchor), (Executor)world);
    }
    
    @Nullable
    private static BlockSelection copyBlocksWithLoadedChunks(@Nonnull final CommandSender sender, @Nonnull final World world, @Nonnull final Vector3i anchorPoint, @Nonnull final Vector3i minPoint, @Nonnull final Vector3i maxPoint, @Nonnull final Vector3i pastePosition, @Nonnull final Vector3i originalFileAnchor, @Nonnull final PrefabSaverSettings settings, @Nonnull final Long2ObjectMap<Ref<ChunkStore>> loadedChunks, final int editorBlock, final int editorBlockPrefabAir, final int editorBlockPrefabAnchor) {
        final ChunkStore chunkStore = world.getChunkStore();
        final long start = System.nanoTime();
        final int width = maxPoint.x - minPoint.x;
        final int height = maxPoint.y - minPoint.y;
        final int depth = maxPoint.z - minPoint.z;
        final int newAnchorX = anchorPoint.x - pastePosition.x;
        final int newAnchorY = anchorPoint.y - pastePosition.y;
        final int newAnchorZ = anchorPoint.z - pastePosition.z;
        final BlockSelection selection = new BlockSelection();
        selection.setPosition(pastePosition.x - originalFileAnchor.x, pastePosition.y - originalFileAnchor.y, pastePosition.z - originalFileAnchor.z);
        selection.setSelectionArea(minPoint, maxPoint);
        selection.setAnchor(newAnchorX, newAnchorY, newAnchorZ);
        int blockCount = 0;
        int fluidCount = 0;
        final int top = Math.max(minPoint.y, maxPoint.y);
        final int bottom = Math.min(minPoint.y, maxPoint.y);
        for (int x = minPoint.x; x <= maxPoint.x; ++x) {
            for (int z = minPoint.z; z <= maxPoint.z; ++z) {
                final long chunkIndex = ChunkUtil.indexChunkFromBlock(x, z);
                final Ref<ChunkStore> chunkRef = loadedChunks.get(chunkIndex);
                if (chunkRef != null) {
                    if (chunkRef.isValid()) {
                        final WorldChunk worldChunkComponent = chunkStore.getStore().getComponent(chunkRef, WorldChunk.getComponentType());
                        assert worldChunkComponent != null;
                        final ChunkColumn chunkColumnComponent = chunkStore.getStore().getComponent(chunkRef, ChunkColumn.getComponentType());
                        assert chunkColumnComponent != null;
                        for (int y = top; y >= bottom; --y) {
                            final int sectionIndex = ChunkUtil.indexSection(y);
                            final Ref<ChunkStore> sectionRef = chunkColumnComponent.getSection(sectionIndex);
                            if (sectionRef != null) {
                                if (sectionRef.isValid()) {
                                    final BlockSection sectionComponent = chunkStore.getStore().getComponent(sectionRef, BlockSection.getComponentType());
                                    assert sectionComponent != null;
                                    final BlockPhysics blockPhysicsComponent = chunkStore.getStore().getComponent(sectionRef, BlockPhysics.getComponentType());
                                    int block = sectionComponent.get(x, y, z);
                                    final int filler = sectionComponent.getFiller(x, y, z);
                                    if (settings.isBlocks() && (block != 0 || settings.isEmpty()) && block != editorBlock) {
                                        if (block == editorBlockPrefabAir) {
                                            block = 0;
                                        }
                                        Holder<ChunkStore> holder = worldChunkComponent.getBlockComponentHolder(x, y, z);
                                        if (holder != null) {
                                            holder = holder.clone();
                                            final BlockState blockState = BlockState.getBlockState(holder);
                                            if (blockState != null) {
                                                blockState.clearPositionForSerialization();
                                            }
                                        }
                                        selection.addBlockAtWorldPos(x, y, z, block, sectionComponent.getRotationIndex(x, y, z), filler, (blockPhysicsComponent != null) ? blockPhysicsComponent.get(x, y, z) : 0, holder);
                                        ++blockCount;
                                    }
                                    final FluidSection fluidSectionComponent = chunkStore.getStore().getComponent(sectionRef, FluidSection.getComponentType());
                                    assert fluidSectionComponent != null;
                                    final int fluid = fluidSectionComponent.getFluidId(x, y, z);
                                    if (settings.isBlocks() && (fluid != 0 || settings.isEmpty())) {
                                        final byte fluidLevel = fluidSectionComponent.getFluidLevel(x, y, z);
                                        selection.addFluidAtWorldPos(x, y, z, fluid, fluidLevel);
                                        ++fluidCount;
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
        if (settings.isEntities()) {
            final Store<EntityStore> store = world.getEntityStore().getStore();
            final ComponentType<EntityStore, BlockEntity> blockEntityType = BlockEntity.getComponentType();
            final Set<UUID> addedEntityUuids = new HashSet<UUID>();
            final ComponentRegistry.Data<EntityStore> data = EntityStore.REGISTRY.getData();
            final ComponentType<EntityStore, PrefabCopyableComponent> prefabCopyableType = PrefabCopyableComponent.getComponentType();
            final ComponentType<EntityStore, TransformComponent> transformType = TransformComponent.getComponentType();
            BuilderToolsPlugin.forEachCopyableInSelection(world, minPoint.x, minPoint.y, minPoint.z, width, height, depth, e -> {
                final BlockEntity blockEntity2 = store.getComponent(e, blockEntityType);
                if (blockEntity2 != null) {
                    final String key2 = blockEntity2.getBlockTypeKey();
                    if (key2 != null && (key2.equals("Editor_Block") || key2.equals("Editor_Empty") || key2.equals("Editor_Anchor"))) {
                        return;
                    }
                }
                final Holder<EntityStore> holder3 = store.copyEntity(e);
                final UUIDComponent uuidComp2 = holder3.getComponent(UUIDComponent.getComponentType());
                if (uuidComp2 != null) {
                    addedEntityUuids.add(uuidComp2.getUuid());
                }
                final TransformComponent transform2 = holder3.getComponent((ComponentType<EntityStore, TransformComponent>)transformType);
                if (transform2 != null && transform2.getPosition() != null) {
                    transform2.getPosition().subtract(selection.getX(), selection.getY(), selection.getZ());
                }
                selection.addEntityHolderRaw(holder3);
                return;
            });
            for (final Ref<ChunkStore> chunkRef2 : loadedChunks.values()) {
                final EntityChunk entityChunk = chunkStore.getStore().getComponent(chunkRef2, EntityChunk.getComponentType());
                if (entityChunk == null) {
                    continue;
                }
                final List<Holder<EntityStore>> holders = entityChunk.getEntityHolders();
                for (final Holder<EntityStore> holder2 : holders) {
                    final UUIDComponent uuidComp = holder2.getComponent(UUIDComponent.getComponentType());
                    final TransformComponent transform = holder2.getComponent(transformType);
                    final Vector3d position = (transform != null) ? transform.getPosition() : null;
                    final boolean hasPrefabCopyable = holder2.getArchetype().contains(prefabCopyableType);
                    final boolean hasSerializable = holder2.hasSerializableComponents(data);
                    if (!hasPrefabCopyable) {
                        continue;
                    }
                    if (!hasSerializable) {
                        continue;
                    }
                    final BlockEntity blockEntity = holder2.getComponent(blockEntityType);
                    if (blockEntity != null) {
                        final String key = blockEntity.getBlockTypeKey();
                        if (key != null) {
                            if (key.equals("Editor_Block") || key.equals("Editor_Empty")) {
                                continue;
                            }
                            if (key.equals("Editor_Anchor")) {
                                continue;
                            }
                        }
                    }
                    if (transform == null) {
                        continue;
                    }
                    if (position == null) {
                        continue;
                    }
                    if (!VectorBoxUtil.isInside(minPoint.x, minPoint.y, minPoint.z, 0.0, 0.0, 0.0, width + 1, height + 1, depth + 1, position)) {
                        continue;
                    }
                    if (uuidComp != null && addedEntityUuids.contains(uuidComp.getUuid())) {
                        continue;
                    }
                    if (uuidComp != null) {
                        addedEntityUuids.add(uuidComp.getUuid());
                    }
                    final Holder<EntityStore> clonedHolder = holder2.clone();
                    final TransformComponent clonedTransform = clonedHolder.getComponent(transformType);
                    if (clonedTransform != null && clonedTransform.getPosition() != null) {
                        clonedTransform.getPosition().subtract(selection.getX(), selection.getY(), selection.getZ());
                    }
                    selection.addEntityHolderRaw(clonedHolder);
                }
            }
            selection.sortEntitiesByPosition();
        }
        final long end = System.nanoTime();
        final long diff = end - start;
        BuilderToolsPlugin.get().getLogger().at(Level.FINE).log("Took: %dns (%dms) to execute copy of %d blocks, %d fluids", diff, TimeUnit.NANOSECONDS.toMillis(diff), blockCount, fluidCount);
        return selection;
    }
    
    @Nonnull
    private static CompletableFuture<Long2ObjectMap<Ref<ChunkStore>>> preloadChunksInSelectionAsync(@Nonnull final ChunkStore chunkStore, @Nonnull final Vector3i minPoint, @Nonnull final Vector3i maxPoint) {
        final LongSet chunkIndices = new LongOpenHashSet();
        final int minChunkX = minPoint.x >> 5;
        final int maxChunkX = maxPoint.x >> 5;
        final int minChunkZ = minPoint.z >> 5;
        final int maxChunkZ = maxPoint.z >> 5;
        for (int cx = minChunkX; cx <= maxChunkX; ++cx) {
            for (int cz = minChunkZ; cz <= maxChunkZ; ++cz) {
                chunkIndices.add(ChunkUtil.indexChunk(cx, cz));
            }
        }
        final Long2ObjectMap<Ref<ChunkStore>> loadedChunks = new Long2ObjectOpenHashMap<Ref<ChunkStore>>(chunkIndices.size());
        final List<CompletableFuture<Void>> chunkFutures = new ArrayList<CompletableFuture<Void>>(chunkIndices.size());
        for (final long chunkIndex : chunkIndices) {
            final CompletableFuture<Void> future = chunkStore.getChunkReferenceAsync(chunkIndex).thenAccept(reference -> {
                if (reference != null && reference.isValid()) {
                    synchronized (loadedChunks) {
                        loadedChunks.put(chunkIndex, reference);
                    }
                }
                return;
            });
            chunkFutures.add(future);
        }
        return CompletableFuture.allOf((CompletableFuture<?>[])chunkFutures.toArray(CompletableFuture[]::new)).thenApply(v -> loadedChunks);
    }
    
    private static boolean save(@Nonnull final CommandSender sender, @Nonnull final BlockSelection copiedSelection, @Nonnull final Path saveFilePath, @Nonnull final PrefabSaverSettings settings) {
        if (saveFilePath.getFileSystem() != FileSystems.getDefault()) {
            sender.sendMessage(Message.translation("server.builderTools.cannotSaveToReadOnlyPath").param("path", saveFilePath.toString()));
            return false;
        }
        try {
            final long start = System.nanoTime();
            final BlockSelection postClone = settings.isRelativize() ? copiedSelection.relativize() : copiedSelection.cloneSelection();
            PrefabStore.get().savePrefab(saveFilePath, postClone, settings.isOverwriteExisting());
            final long diff = System.nanoTime() - start;
            BuilderToolsPlugin.get().getLogger().at(Level.FINE).log("Took: %dns (%dms) to execute save of %d blocks", diff, TimeUnit.NANOSECONDS.toMillis(diff), copiedSelection.getBlockCount());
            return true;
        }
        catch (final PrefabSaveException e) {
            switch (e.getType()) {
                case ERROR: {
                    BuilderToolsPlugin.get().getLogger().at(Level.WARNING).withCause(e).log("Exception saving prefab %s", saveFilePath);
                    sender.sendMessage(Message.translation("server.builderTools.errorSavingPrefab").param("name", saveFilePath.toString()).param("message", e.getCause().getMessage()));
                    break;
                }
                case ALREADY_EXISTS: {
                    BuilderToolsPlugin.get().getLogger().at(Level.WARNING).log("Prefab already exists %s", saveFilePath.toString());
                    sender.sendMessage(Message.translation("server.builderTools.prefabAlreadyExists"));
                    break;
                }
            }
            return false;
        }
    }
}
