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

package com.hypixel.hytale.builtin.buildertools.utils;

import java.util.ArrayList;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.Rotation;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import it.unimi.dsi.fastutil.objects.ObjectArrayList;
import com.hypixel.hytale.server.core.prefab.selection.standard.BlockSelection;
import java.io.IOException;
import java.util.List;
import java.util.function.Consumer;
import com.hypixel.hytale.server.core.prefab.selection.buffer.PrefabLoader;
import com.hypixel.hytale.server.core.prefab.PrefabLoadException;
import com.hypixel.hytale.server.core.prefab.PrefabWeights;
import com.hypixel.hytale.server.core.prefab.PrefabRotation;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.core.universe.world.meta.BlockStateModule;
import java.util.HashSet;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.core.modules.prefabspawner.PrefabSpawnerState;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.component.ComponentType;
import java.util.Set;
import java.util.function.Function;
import java.nio.file.Path;
import java.util.Random;
import java.util.function.BiFunction;

public abstract class RecursivePrefabLoader<T> implements BiFunction<String, Random, T>
{
    private static final int MAX_RECURSION_DEPTH = 10;
    protected final Path rootPrefabsDir;
    protected final Function<String, T> prefabsLoader;
    protected final Set<Path> visitedFiles;
    @Nullable
    protected final ComponentType<ChunkStore, PrefabSpawnerState> prefabComponentType;
    private int depthTracker;
    
    public RecursivePrefabLoader(final Path rootPrefabsDir, final Function<String, T> prefabsLoader) {
        this.visitedFiles = new HashSet<Path>();
        this.prefabComponentType = BlockStateModule.get().getComponentType(PrefabSpawnerState.class);
        this.depthTracker = 0;
        this.rootPrefabsDir = rootPrefabsDir;
        this.prefabsLoader = prefabsLoader;
    }
    
    @Nonnull
    @Override
    public T apply(@Nonnull final String name, @Nonnull final Random random) {
        return this.load(name, random);
    }
    
    @Nonnull
    public T load(@Nonnull final String name, @Nonnull final Random random) {
        return this.load(0, 0, 0, name, PrefabRotation.ROTATION_0, PrefabWeights.NONE, random);
    }
    
    @Nonnull
    protected T load(final int x, final int y, final int z, @Nonnull final String name, final PrefabRotation rotation, @Nonnull final PrefabWeights weights, @Nonnull final Random random) {
        if (this.depthTracker >= 10) {
            throw new PrefabLoadException(PrefabLoadException.Type.NOT_FOUND, "Prefab nesting limit exceeded!");
        }
        try {
            ++this.depthTracker;
            final DistinctCollector<Path> prefabs = new DistinctCollector<Path>();
            PrefabLoader.resolvePrefabs(this.rootPrefabsDir, stripSuffix(name), prefabs);
            if (prefabs.isEmpty()) {
                throw new PrefabLoadException(PrefabLoadException.Type.NOT_FOUND, "Could not locate prefab: " + name);
            }
            if (prefabs.size() == 1) {
                return this.loadSinglePrefab(x, y, z, prefabs.getFirst(), rotation, random);
            }
            if (weights.size() > 0) {
                return this.loadWeightedPrefab(x, y, z, name, prefabs, rotation, weights, random);
            }
            return this.loadRandomPrefab(x, y, z, prefabs, rotation, random);
        }
        catch (final IOException e) {
            throw new PrefabLoadException(PrefabLoadException.Type.ERROR, e);
        }
        finally {
            --this.depthTracker;
        }
    }
    
    protected T loadSinglePrefab(final int x, final int y, final int z, @Nonnull final Path file, final PrefabRotation rotation, final Random random) {
        if (!this.visitedFiles.add(file)) {
            throw new PrefabLoadException(PrefabLoadException.Type.ERROR, "Cyclic prefab dependency detected: " + String.valueOf(file));
        }
        try {
            final String path = this.rootPrefabsDir.relativize(file).toString();
            return this.loadPrefab(x, y, z, appendSuffix(path), rotation, random);
        }
        finally {
            this.visitedFiles.remove(file);
        }
    }
    
    protected T loadWeightedPrefab(final int x, final int y, final int z, @Nonnull final String name, @Nonnull final List<Path> files, final PrefabRotation rotation, @Nonnull final PrefabWeights weights, @Nonnull final Random random) {
        final Path[] prefabs = files.toArray(Path[]::new);
        final Path prefab = weights.get(prefabs, path -> PrefabLoader.resolveRelativeJsonPath(name, path, this.rootPrefabsDir), random);
        if (prefab != null) {
            return this.loadSinglePrefab(x, y, z, prefab, rotation, random);
        }
        throw new PrefabLoadException(PrefabLoadException.Type.ERROR, String.format("Unable to pick weighted prefab! Files: %s, Weights: %s", files, weights));
    }
    
    protected T loadRandomPrefab(final int x, final int y, final int z, @Nonnull final List<Path> files, final PrefabRotation rotation, @Nonnull final Random random) {
        final Path file = files.get(random.nextInt(files.size()));
        return this.loadSinglePrefab(x, y, z, file, rotation, random);
    }
    
    protected abstract T loadPrefab(final int p0, final int p1, final int p2, final String p3, final PrefabRotation p4, final Random p5);
    
    @Nonnull
    private static String stripSuffix(@Nonnull final String path) {
        return path.replace(".prefab.json", "");
    }
    
    @Nonnull
    private static String appendSuffix(@Nonnull final String path) {
        if (path.endsWith(".prefab.json")) {
            return path;
        }
        return path + ".prefab.json";
    }
    
    public static class BlockSelectionLoader extends RecursivePrefabLoader<BlockSelection>
    {
        public BlockSelectionLoader(final Path rootPrefabsDir, @Nonnull final Function<String, BlockSelection> prefabsLoader) {
            super(rootPrefabsDir, prefabsLoader.andThen((Function<? super BlockSelection, ?>)BlockSelection::cloneSelection));
        }
        
        @Nonnull
        @Override
        protected BlockSelection loadPrefab(final int x, final int y, final int z, final String file, @Nonnull final PrefabRotation rotation, @Nonnull final Random random) {
            final BlockSelection prefab = (BlockSelection)this.prefabsLoader.apply(file);
            prefab.setPosition(x, y, z);
            final List<BlockSelection> children = new ObjectArrayList<BlockSelection>();
            prefab.forEachBlock((dx, dy, dz, block) -> {
                final Holder<ChunkStore> state = block.holder();
                if (state == null) {
                    return;
                }
                else {
                    final PrefabSpawnerState spawner = state.getComponent(this.prefabComponentType);
                    if (spawner == null) {
                        return;
                    }
                    else {
                        final BlockType blockType = BlockType.getAssetMap().getAsset(block.blockId());
                        final int childX = x + rotation.getX(dx, dz);
                        final int childY = y + dy;
                        final int childZ = z + rotation.getZ(dx, dz);
                        final String childPath = spawner.getPrefabPath();
                        final PrefabWeights childWeights = spawner.getPrefabWeights();
                        final PrefabRotation childRotation = rotation.add(getRotation(blockType));
                        final BlockSelection child = this.load(childX, childY, childZ, childPath, childRotation, childWeights, random);
                        children.add(child);
                        return;
                    }
                }
            });
            for (int i = 0; i < children.size(); ++i) {
                prefab.add(children.get(i));
            }
            return prefab;
        }
        
        @Nonnull
        private static PrefabRotation getRotation(@Nonnull final BlockType blockType) {
            final Rotation rotation = blockType.getRotationYawPlacementOffset();
            if (rotation == null) {
                return PrefabRotation.ROTATION_0;
            }
            return PrefabRotation.fromRotation(rotation);
        }
    }
    
    protected static class DistinctCollector<T> extends ArrayList<T> implements Consumer<T>
    {
        @Override
        public void accept(final T t) {
            if (this.contains(t)) {
                return;
            }
            this.add(t);
        }
    }
}
