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

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

import com.hypixel.hytale.server.worldgen.chunk.ZoneBiomeResult;
import com.hypixel.hytale.procedurallib.condition.ConstantIntCondition;
import com.hypixel.hytale.procedurallib.condition.IIntCondition;
import com.hypixel.hytale.server.worldgen.loader.WorldGenPrefabSupplier;
import com.hypixel.hytale.server.worldgen.cave.element.CavePrefab;
import com.hypixel.hytale.server.worldgen.cave.prefab.CavePrefabContainer;
import com.hypixel.hytale.procedurallib.condition.DefaultCoordinateCondition;
import com.hypixel.hytale.procedurallib.condition.ICoordinateCondition;
import javax.annotation.Nullable;
import com.hypixel.hytale.server.worldgen.cave.shape.PrefabCaveNodeShape;
import com.hypixel.hytale.server.worldgen.util.ArrayUtli;
import com.hypixel.hytale.procedurallib.supplier.IDoubleRange;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.server.core.prefab.PrefabRotation;
import com.hypixel.hytale.server.worldgen.cave.shape.CaveNodeShape;
import com.hypixel.hytale.server.worldgen.util.condition.flag.Int2FlagsCondition;
import com.hypixel.hytale.server.worldgen.cave.element.CaveNode;
import java.util.Random;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.math.util.FastRandom;
import com.hypixel.hytale.math.util.HashUtil;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.worldgen.chunk.ChunkGenerator;

public class CaveGenerator
{
    private final CaveType[] caveTypes;
    
    public CaveGenerator(final CaveType[] caveTypes) {
        this.caveTypes = caveTypes;
    }
    
    public CaveType[] getCaveTypes() {
        return this.caveTypes;
    }
    
    @Nonnull
    public Cave generate(final int seed, @Nonnull final ChunkGenerator chunkGenerator, @Nonnull final CaveType caveType, final int x, final int y, final int z) {
        final int seedOffset = (int)HashUtil.rehash(seed, x, y, z);
        final Random random = new FastRandom(seedOffset);
        final Cave cave = this.newCave(caveType);
        final Vector3d origin = new Vector3d(x, y, z);
        origin.y = caveType.getModifiedStartHeight(seed + seedOffset, x, y, z, random);
        this.startCave(seed, chunkGenerator, cave, origin, random);
        cave.compile();
        return cave;
    }
    
    @Nonnull
    protected Cave newCave(final CaveType caveType) {
        return new Cave(caveType);
    }
    
    protected void startCave(final int seed, @Nonnull final ChunkGenerator chunkGenerator, @Nonnull final Cave cave, @Nonnull final Vector3d origin, @Nonnull final Random random) {
        final Int2FlagsCondition biomeMask = cave.getCaveType().getBiomeMask();
        final int startBiomeMaskResult = this.getBiomeMaskResult(seed, chunkGenerator, biomeMask, origin);
        if (!CaveBiomeMaskFlags.canGenerate(startBiomeMaskResult)) {
            return;
        }
        final CaveType caveType = cave.getCaveType();
        final int depth = caveType.getStartDepth(random);
        final CaveNodeType type = caveType.getEntryNode();
        final float yaw = caveType.getStartYaw(random);
        final float pitch = caveType.getStartPitch(random);
        final int seedOffset = random.nextInt();
        final CaveNodeShape shape = type.generateCaveNodeShape(random, caveType, null, null, origin, yaw, pitch);
        final int endBiomeMaskResult = this.getBiomeMaskResult(seed, chunkGenerator, biomeMask, shape.getEnd());
        if (!CaveBiomeMaskFlags.canGenerate(endBiomeMaskResult)) {
            return;
        }
        final CaveNode node = new CaveNode(seed + seedOffset, type, shape, yaw, pitch);
        if (shape.hasGeometry() && CaveBiomeMaskFlags.canPopulate(startBiomeMaskResult) && CaveBiomeMaskFlags.canPopulate(endBiomeMaskResult)) {
            cave.addNode(node);
        }
        this.continueNode(seed, chunkGenerator, cave, node, depth, random);
    }
    
    protected void continueNode(final int seed, @Nonnull final ChunkGenerator chunkGenerator, @Nonnull final Cave cave, @Nonnull final CaveNode parent, final int depth, @Nonnull final Random random) {
        if (depth <= 0) {
            return;
        }
        final Int2FlagsCondition biomeMask = cave.getCaveType().getBiomeMask();
        final CaveNodeType.CaveNodeChildEntry[] childEntries = this.getChildEntriesRandomized(parent.getCaveNodeType(), random);
        final int childrenCount = this.getChildrenCount(parent.getCaveNodeType(), random);
        int generatedChildren = 0;
        for (final CaveNodeType.CaveNodeChildEntry childEntry : childEntries) {
            for (int repeat = this.getRepeatCounter(childEntry, random), j = 0; j < repeat; ++j) {
                if (this.shouldGenerateChild(childEntry, random)) {
                    if (generatedChildren >= childrenCount) {
                        return;
                    }
                    final PrefabRotation parentRotation = this.getRotation(parent);
                    final Vector3d origin = this.getChildOrigin(parent, parentRotation, childEntry);
                    final CaveNodeType type = childEntry.getTypes().get(random);
                    if (this.isMatchingHeight(seed, origin, type.getHeightCondition())) {
                        final float yaw = this.getChildYaw(parent, parentRotation, childEntry, random);
                        final float pitch = childEntry.getPitchModifier().calc(parent.getPitch(), random);
                        final int hash = random.nextInt();
                        final CaveNodeShape shape = type.generateCaveNodeShape(random, cave.getCaveType(), parent, childEntry, origin, yaw, pitch);
                        if (this.isMatchingHeight(seed, shape.getEnd(), type.getHeightCondition())) {
                            final int biomeMaskResult = this.getBiomeMaskResult(seed, chunkGenerator, biomeMask, shape.getEnd());
                            if (!CaveBiomeMaskFlags.canGenerate(biomeMaskResult)) {
                                if (!CaveBiomeMaskFlags.canContinue(biomeMaskResult)) {
                                    break;
                                }
                            }
                            else {
                                final CaveNode node = new CaveNode(hash, type, shape, yaw, pitch);
                                if (shape.hasGeometry() && CaveBiomeMaskFlags.canPopulate(biomeMaskResult)) {
                                    this.generatePrefabs(seed, chunkGenerator, parent, node);
                                    cave.addNode(node);
                                }
                                final int nextDepth = this.getNextDepth(childEntry, depth, random);
                                this.continueNode(seed, chunkGenerator, cave, node, nextDepth, random);
                                ++generatedChildren;
                            }
                        }
                    }
                }
            }
        }
    }
    
    protected int getChildrenCount(@Nonnull final CaveNodeType type, final Random random) {
        final IDoubleRange countArray = type.getChildrenCountBounds();
        if (countArray == null) {
            return Integer.MAX_VALUE;
        }
        return MathUtil.floor(countArray.getValue(random));
    }
    
    @Nonnull
    protected CaveNodeType.CaveNodeChildEntry[] getChildEntriesRandomized(@Nonnull final CaveNodeType type, @Nonnull final Random random) {
        final CaveNodeType.CaveNodeChildEntry[] childEntries = type.getChildren();
        if (type.getChildrenCountBounds() == null || childEntries.length == 0) {
            return childEntries;
        }
        final CaveNodeType.CaveNodeChildEntry[] randomized = new CaveNodeType.CaveNodeChildEntry[childEntries.length];
        System.arraycopy(childEntries, 0, randomized, 0, randomized.length);
        ArrayUtli.shuffleArray(randomized, random);
        return randomized;
    }
    
    protected int getRepeatCounter(@Nonnull final CaveNodeType.CaveNodeChildEntry entry, final Random random) {
        return MathUtil.floor(entry.getRepeat().getValue(random));
    }
    
    @Nullable
    protected PrefabRotation getRotation(@Nonnull final CaveNode caveNode) {
        final CaveNodeShape shape = caveNode.getShape();
        if (shape instanceof final PrefabCaveNodeShape prefabCaveNodeShape) {
            return prefabCaveNodeShape.getPrefabRotation();
        }
        return null;
    }
    
    protected Vector3d getChildOrigin(@Nonnull final CaveNode parentNode, @Nullable final PrefabRotation parentRotation, @Nonnull final CaveNodeType.CaveNodeChildEntry childEntry) {
        final Vector3d vector = parentNode.getEnd();
        final Vector3d anchor = childEntry.getAnchor();
        if (anchor == Vector3d.ZERO) {
            return vector;
        }
        vector.assign(anchor);
        if (parentRotation != null && parentRotation != PrefabRotation.ROTATION_0) {
            vector.subtract(0.5, 0.5, 0.5);
            parentRotation.rotate(vector);
            vector.add(0.5, 0.5, 0.5);
        }
        return parentNode.getShape().getAnchor(vector, vector.x, vector.y, vector.z);
    }
    
    protected float getChildYaw(@Nonnull final CaveNode parentNode, @Nullable final PrefabRotation parentRotation, @Nonnull final CaveNodeType.CaveNodeChildEntry childEntry, final Random random) {
        final float yaw = childEntry.getYawMode().combine(parentNode.getYaw(), parentRotation);
        return childEntry.getYawModifier().calc(yaw, random);
    }
    
    protected boolean shouldGenerateChild(@Nonnull final CaveNodeType.CaveNodeChildEntry entry, @Nonnull final Random random) {
        return random.nextDouble() < entry.getChance();
    }
    
    protected boolean isMatchingHeight(final int seed, @Nonnull final Vector3d vec, @Nonnull final ICoordinateCondition condition) {
        if (condition == DefaultCoordinateCondition.DEFAULT_TRUE) {
            return true;
        }
        if (condition == DefaultCoordinateCondition.DEFAULT_FALSE) {
            return false;
        }
        final int x = MathUtil.floor(vec.x);
        final int y = MathUtil.floor(vec.y);
        final int z = MathUtil.floor(vec.z);
        return condition.eval(seed, x, y, z);
    }
    
    protected int getNextDepth(@Nonnull final CaveNodeType.CaveNodeChildEntry entry, final int depth, final Random random) {
        final int nextDepth = depth - 1;
        if (entry.getChildrenLimit() != null) {
            final int limit = MathUtil.floor(entry.getChildrenLimit().getValue(random));
            if (limit < nextDepth) {
                return limit;
            }
        }
        return nextDepth;
    }
    
    protected void generatePrefabs(final int seed, @Nonnull final ChunkGenerator chunkGenerator, final CaveNode parent, @Nonnull final CaveNode node) {
        final Random random = ChunkGenerator.getResource().getRandom();
        random.setSeed(seed + node.getSeedOffset());
        final CavePrefabContainer container = node.getCaveNodeType().getPrefabContainer();
        if (container == null) {
            return;
        }
        for (final CavePrefabContainer.CavePrefabEntry entry : container.getEntries()) {
            this.generatePrefab(seed, chunkGenerator, parent, node, entry, random);
        }
    }
    
    protected void generatePrefab(final int seed, @Nonnull final ChunkGenerator chunkGenerator, @Nullable final CaveNode parent, @Nonnull final CaveNode caveNode, @Nonnull final CavePrefabContainer.CavePrefabEntry entry, @Nonnull final Random random) {
        final CavePrefabContainer.CavePrefabEntry.CavePrefabConfig config = entry.getConfig();
        for (int iterations = config.getIterations(random.nextDouble()), i = 0; i < iterations; ++i) {
            final int x = caveNode.getBounds().randomX(random);
            final int z = caveNode.getBounds().randomZ(random);
            if (this.isMatchingBiome(seed, chunkGenerator, config.getBiomeMask(), x, z)) {
                if (config.isMatchingNoiseDensity(seed, x, z)) {
                    final int y = config.getHeight(seed, x, z, caveNode);
                    if (y != -1) {
                        if (config.isMatchingHeight(seed, x, y, z, random)) {
                            if (parent == null || !parent.getShape().shouldReplace(seed, x, z, y)) {
                                final WorldGenPrefabSupplier prefab = entry.getPrefab(random.nextDouble());
                                final PrefabRotation rotation = config.getRotation(random);
                                final CavePrefab entity = new CavePrefab(prefab, rotation, config.getBiomeMask(), config.getBlockMask(), x, y, z);
                                caveNode.addPrefab(entity);
                            }
                        }
                    }
                }
            }
        }
    }
    
    protected boolean isMatchingBiome(final int seed, @Nonnull final ChunkGenerator chunkGenerator, @Nonnull final IIntCondition condition, final int x, final int z) {
        if (condition == ConstantIntCondition.DEFAULT_TRUE) {
            return true;
        }
        if (condition == ConstantIntCondition.DEFAULT_FALSE) {
            return false;
        }
        final ZoneBiomeResult biomeResult = chunkGenerator.getZoneBiomeResultAt(seed, x, z);
        return condition.eval(biomeResult.getBiome().getId());
    }
    
    protected int getBiomeMaskResult(final int seed, @Nonnull final ChunkGenerator chunkGenerator, @Nonnull final Int2FlagsCondition mask, @Nonnull final Vector3d vec) {
        if (mask == CaveBiomeMaskFlags.DEFAULT_ALLOW) {
            return 7;
        }
        if (mask == CaveBiomeMaskFlags.DEFAULT_DENY) {
            return 0;
        }
        final int x = MathUtil.floor(vec.getX());
        final int z = MathUtil.floor(vec.getZ());
        final ZoneBiomeResult biomeResult = chunkGenerator.getZoneBiomeResultAt(seed, x, z);
        return mask.eval(biomeResult.getBiome().getId());
    }
}
