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

package com.hypixel.hytale.server.worldgen.prefab;

import com.hypixel.hytale.procedurallib.condition.DefaultCoordinateRndCondition;
import com.hypixel.hytale.procedurallib.condition.DefaultCoordinateCondition;
import com.hypixel.hytale.math.util.FastRandom;
import com.hypixel.hytale.procedurallib.condition.ICoordinateRndCondition;
import com.hypixel.hytale.procedurallib.condition.ICoordinateCondition;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.worldgen.chunk.ChunkGeneratorExecution;
import com.hypixel.hytale.server.core.prefab.selection.buffer.PrefabBufferCall;
import com.hypixel.hytale.server.core.universe.world.storage.ChunkStore;
import com.hypixel.hytale.math.util.HashUtil;
import java.util.Random;
import com.hypixel.hytale.server.core.prefab.PrefabWeights;
import com.hypixel.hytale.server.core.universe.world.storage.EntityStore;
import com.hypixel.hytale.math.vector.Vector3i;
import com.hypixel.hytale.component.Holder;
import com.hypixel.hytale.server.worldgen.util.condition.BlockMaskCondition;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.server.worldgen.loader.WorldGenPrefabSupplier;
import com.hypixel.hytale.server.core.prefab.PrefabRotation;
import javax.annotation.Nonnull;

public class PrefabPasteUtil
{
    public static final int MAX_RECURSION_DEPTH = 10;
    
    public static void generate(@Nonnull final PrefabPasteBuffer buffer, final PrefabRotation rotation, @Nonnull final WorldGenPrefabSupplier supplier, final int x, final int y, final int z, final int cx, final int cz) {
        buffer.supplier = supplier;
        buffer.posWorld.assign(x, y, z);
        buffer.posChunk.assign(cx, y, cz);
        buffer.rotation = rotation;
        generate0(buffer, supplier);
        buffer.reset();
    }
    
    private static void generate0(@Nonnull final PrefabPasteBuffer _buffer, @Nonnull final WorldGenPrefabSupplier supplier) {
        if (_buffer.fitHeightmap) {
            _buffer.originHeight = _buffer.execution.getChunkGenerator().getHeight(_buffer.seed, _buffer.posWorld.x, _buffer.posWorld.z);
            _buffer.posChunk.y = _buffer.originHeight;
            _buffer.posWorld.y = _buffer.originHeight;
        }
        supplier.get().forEach((cx, cz, blocks, buffer) -> {
            final int bx = cx + buffer.posChunk.x;
            final int bz = cz + buffer.posChunk.z;
            if (!ChunkUtil.isWithinLocalChunk(bx, bz)) {
                return false;
            }
            else {
                if (buffer.fitHeightmap) {
                    buffer.yOffset = buffer.execution.getChunkGenerator().getHeight(buffer.seed, buffer.posWorld.x + cx, buffer.posWorld.z + cz) - buffer.originHeight;
                }
                else {
                    buffer.yOffset = 0;
                }
                return true;
            }
        }, (cx, cy, cz, block, holder, supportValue, rotation, filler, buffer, fluidId, fluidLevel) -> {
            if (buffer.blockMask != BlockMaskCondition.DEFAULT_FALSE) {
                final int bx2 = cx + buffer.posChunk.x;
                final int by = cy + buffer.posChunk.y + buffer.yOffset;
                final int bz2 = cz + buffer.posChunk.z;
                if (by >= 0 && by < 320) {
                    if (buffer.blockMask != BlockMaskCondition.DEFAULT_TRUE) {
                        final int currentBlock = buffer.execution.getBlock(bx2, by, bz2);
                        final int currentFluid = buffer.execution.getFluid(bx2, by, bz2);
                        if (!buffer.blockMask.eval(currentBlock, currentFluid, block, fluidId)) {
                            return;
                        }
                    }
                    buffer.execution.setBlock(bx2, by, bz2, buffer.priority, block, (holder != null) ? holder.clone() : null, supportValue, rotation, filler);
                    buffer.execution.setFluid(bx2, by, bz2, buffer.priority, fluidId, buffer.environmentId);
                    buffer.execution.setEnvironment(bx2, by, bz2, buffer.environmentId);
                }
            }
        }, (cx, cz, entityWrappers, buffer) -> {
            final Holder<EntityStore>[] clone = new Holder[entityWrappers.length];
            for (int i = 0; i < entityWrappers.length; ++i) {
                clone[i] = entityWrappers[i].clone();
            }
            final Vector3i offset = new Vector3i(buffer.posWorld.x, buffer.posWorld.y + buffer.yOffset, buffer.posWorld.z);
            buffer.execution.getEntityChunk().addEntities(offset, buffer.rotation, clone, buffer.specificSeed);
        }, (cx, cy, cz, path, fitHeightmap, inheritSeed, inheritHeightCondition, weights, rotation, buffer) -> {
            if (buffer.depth < 10) {
                ++buffer.depth;
                final int _localX = buffer.posChunk.x;
                final int _localY = buffer.posChunk.y;
                final int _localZ = buffer.posChunk.z;
                final int _worldX = buffer.posWorld.x;
                final int _worldY = buffer.posWorld.y;
                final int _worldZ = buffer.posWorld.z;
                final int _yOffset = buffer.yOffset;
                final int _originHeight = buffer.originHeight;
                final int _specificSeed = buffer.specificSeed;
                final PrefabRotation _rotation = buffer.rotation;
                final boolean _fitHeightmap = buffer.fitHeightmap;
                generateChild(cx, cy, cz, path, fitHeightmap, inheritSeed, inheritHeightCondition, weights, rotation, buffer, buffer.childRandom);
                buffer.posChunk.assign(_localX, _localY, _localZ);
                buffer.posWorld.assign(_worldX, _worldY, _worldZ);
                buffer.yOffset = _yOffset;
                buffer.originHeight = _originHeight;
                buffer.rotation = _rotation;
                buffer.specificSeed = _specificSeed;
                buffer.fitHeightmap = _fitHeightmap;
                --buffer.depth;
            }
        }, _buffer);
    }
    
    private static void generateChild(final int cx, final int cy, final int cz, final String path, final boolean fitHeightmap, final boolean inheritSeed, final boolean inheritHeightCondition, @Nonnull final PrefabWeights weights, @Nonnull final PrefabRotation rotation, @Nonnull final PrefabPasteBuffer buffer, final Random random) {
        final int parentSpecificSeed = buffer.specificSeed;
        final boolean parentFitHeightmap = buffer.fitHeightmap;
        buffer.posChunk.add(cx, cy, cz);
        buffer.posWorld.add(cx, cy, cz);
        buffer.fitHeightmap = fitHeightmap;
        buffer.rotation = buffer.rotation.add(rotation);
        if (!inheritSeed) {
            buffer.specificSeed = parentSpecificSeed;
        }
        else {
            buffer.specificSeed = (int)HashUtil.hash(parentSpecificSeed, cx, cy, cz);
            if (buffer.specificSeed == parentSpecificSeed) {
                ++buffer.specificSeed;
            }
        }
        if (parentFitHeightmap) {
            final int yOffset = buffer.execution.getChunkGenerator().getHeight(buffer.seed, buffer.posWorld.x, buffer.posWorld.z) - buffer.originHeight;
            final Vector3i posChunk = buffer.posChunk;
            posChunk.y += yOffset;
            final Vector3i posWorld = buffer.posWorld;
            posWorld.y += yOffset;
        }
        if (buffer.posChunk.y >= 0 && buffer.posChunk.y < 320 && ChunkUtil.isWithinLocalChunk(buffer.posChunk.x, buffer.posChunk.z)) {
            buffer.execution.setBlock(buffer.posChunk.x, buffer.posChunk.y, buffer.posChunk.z, buffer.priority, 0, null);
        }
        if (inheritHeightCondition && !buffer.spawnCondition.eval(buffer.seed, buffer.posWorld.x, buffer.posWorld.z, buffer.posWorld.y, random)) {
            return;
        }
        final WorldGenPrefabSupplier[] prefabSuppliers = buffer.supplier.getLoader().get(path);
        if (prefabSuppliers == null || prefabSuppliers.length == 0) {
            return;
        }
        final WorldGenPrefabSupplier prefabSupplier = nextPrefab(buffer.childRandom, prefabSuppliers, weights);
        generate0(buffer, prefabSupplier);
    }
    
    @Nonnull
    private static WorldGenPrefabSupplier nextPrefab(@Nonnull final Random random, @Nonnull final WorldGenPrefabSupplier[] prefabSuppliers, @Nonnull final PrefabWeights weights) {
        if (prefabSuppliers.length == 1) {
            return prefabSuppliers[0];
        }
        WorldGenPrefabSupplier prefab = null;
        if (weights.size() > 0) {
            prefab = weights.get(prefabSuppliers, WorldGenPrefabSupplier::getPrefabName, random);
        }
        if (prefab == null) {
            return nextRandomPrefab(random, prefabSuppliers);
        }
        return prefab;
    }
    
    @Nonnull
    private static WorldGenPrefabSupplier nextRandomPrefab(@Nonnull final Random random, @Nonnull final WorldGenPrefabSupplier[] prefabSuppliers) {
        return prefabSuppliers[random.nextInt(prefabSuppliers.length)];
    }
    
    public static class PrefabPasteBuffer extends PrefabBufferCall
    {
        @Nullable
        public ChunkGeneratorExecution execution;
        public final Vector3i posWorld;
        public final Vector3i posChunk;
        public final Random childRandom;
        public int originHeight;
        public int yOffset;
        public int seed;
        public int specificSeed;
        public boolean fitHeightmap;
        public boolean deepSearch;
        public BlockMaskCondition blockMask;
        public int environmentId;
        public byte priority;
        public ICoordinateCondition heightCondition;
        public ICoordinateRndCondition spawnCondition;
        @Nullable
        public WorldGenPrefabSupplier supplier;
        private int depth;
        
        public PrefabPasteBuffer() {
            this.posWorld = new Vector3i();
            this.posChunk = new Vector3i();
            this.childRandom = new FastRandom(0L);
            this.random = new FastRandom(0L);
            this.reset();
        }
        
        public void setSeed(final int worldSeed, final long externalSeed) {
            this.seed = worldSeed;
            this.specificSeed = (int)externalSeed;
            this.random.setSeed(externalSeed);
            this.childRandom.setSeed(externalSeed);
        }
        
        void reset() {
            this.execution = null;
            this.fitHeightmap = false;
            this.deepSearch = false;
            this.blockMask = BlockMaskCondition.DEFAULT_TRUE;
            this.environmentId = Integer.MIN_VALUE;
            this.heightCondition = DefaultCoordinateCondition.DEFAULT_TRUE;
            this.spawnCondition = DefaultCoordinateRndCondition.DEFAULT_TRUE;
            this.supplier = null;
            this.depth = 0;
        }
    }
}
