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

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

import com.hypixel.hytale.math.util.TrigMathUtil;
import com.hypixel.hytale.server.worldgen.cave.shape.distorted.AbstractDistortedShape;
import javax.annotation.Nullable;
import com.hypixel.hytale.procedurallib.logic.GeneralNoise;
import com.hypixel.hytale.procedurallib.supplier.IDoubleRange;
import com.hypixel.hytale.math.util.MathUtil;
import com.hypixel.hytale.server.worldgen.util.BlockFluidEntry;
import com.hypixel.hytale.assetstore.map.BlockTypeAssetMap;
import com.hypixel.hytale.server.core.universe.world.worldgen.GeneratedBlockChunk;
import com.hypixel.hytale.server.worldgen.cave.CaveNodeType;
import com.hypixel.hytale.math.util.ChunkUtil;
import com.hypixel.hytale.server.core.asset.type.blocktype.config.BlockType;
import java.util.Random;
import com.hypixel.hytale.server.worldgen.cave.element.CaveNode;
import com.hypixel.hytale.server.worldgen.cave.Cave;
import javax.annotation.Nonnull;
import com.hypixel.hytale.server.worldgen.chunk.ChunkGeneratorExecution;
import com.hypixel.hytale.server.worldgen.util.bounds.IWorldBounds;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.server.worldgen.cave.shape.distorted.ShapeDistortion;
import com.hypixel.hytale.server.worldgen.cave.shape.distorted.DistortedShape;
import com.hypixel.hytale.server.worldgen.cave.CaveType;

public class DistortedCaveNodeShape implements CaveNodeShape
{
    private final CaveType caveType;
    private final DistortedShape shape;
    private final ShapeDistortion distortion;
    
    public DistortedCaveNodeShape(final CaveType caveType, final DistortedShape shape, final ShapeDistortion distortion) {
        this.caveType = caveType;
        this.shape = shape;
        this.distortion = distortion;
    }
    
    public DistortedShape getShape() {
        return this.shape;
    }
    
    @Override
    public Vector3d getStart() {
        return this.shape.getStart();
    }
    
    @Override
    public Vector3d getEnd() {
        return this.shape.getEnd();
    }
    
    @Override
    public Vector3d getAnchor(final Vector3d vector, final double tx, final double ty, final double tz) {
        return this.shape.getAnchor(vector, tx, ty, tz);
    }
    
    @Override
    public IWorldBounds getBounds() {
        return this.shape;
    }
    
    @Override
    public boolean hasGeometry() {
        return this.shape.hasGeometry();
    }
    
    @Override
    public boolean shouldReplace(final int seed, final double x, final double z, final int y) {
        final double t = this.shape.getProjection(x, z);
        if (this.shape.isValidProjection(t)) {
            final double centerY = this.shape.getYAt(t);
            final double shapeHeight = this.shape.getHeightAtProjection(seed, x, z, t, centerY, this.caveType, this.distortion);
            if (shapeHeight > 0.0) {
                final int minY = this.getBounds().getLowBoundY();
                final int floor = this.getFloor(seed, x, z, centerY, shapeHeight, minY);
                if (y < floor) {
                    return false;
                }
                final int maxY = this.getBounds().getHighBoundY();
                final int ceiling = this.getCeiling(seed, x, z, centerY, shapeHeight, maxY);
                return y <= ceiling;
            }
        }
        return false;
    }
    
    @Override
    public double getFloorPosition(final int seed, final double x, final double z) {
        final double t = this.shape.getProjection(x, z);
        if (this.shape.isValidProjection(t)) {
            final double centerY = this.shape.getYAt(t);
            final double shapeHeight = this.shape.getHeightAtProjection(seed, x, z, t, centerY, this.caveType, this.distortion);
            if (shapeHeight > 0.0) {
                final int minY = this.getBounds().getLowBoundY();
                return this.getFloor(seed, x, z, centerY, shapeHeight, minY) - 1;
            }
        }
        return -1.0;
    }
    
    @Override
    public double getCeilingPosition(final int seed, final double x, final double z) {
        final double t = this.shape.getProjection(x, z);
        if (this.shape.isValidProjection(t)) {
            final double centerY = this.shape.getYAt(t);
            final double shapeHeight = this.shape.getHeightAtProjection(seed, x, z, t, centerY, this.caveType, this.distortion);
            if (shapeHeight > 0.0) {
                final int maxY = this.getBounds().getHighBoundY();
                return this.getCeiling(seed, x, z, centerY, shapeHeight, maxY) + 1;
            }
        }
        return -1.0;
    }
    
    @Override
    public void populateChunk(final int seed, @Nonnull final ChunkGeneratorExecution execution, @Nonnull final Cave cave, @Nonnull final CaveNode node, @Nonnull final Random random) {
        final GeneratedBlockChunk chunk = execution.getChunk();
        final BlockTypeAssetMap<String, BlockType> blockTypeMap = BlockType.getAssetMap();
        final CaveType caveType = cave.getCaveType();
        final CaveNodeType caveNodeType = node.getCaveNodeType();
        final IWorldBounds shapeBounds = this.getBounds();
        final boolean surfaceLimited = cave.getCaveType().isSurfaceLimited();
        final int environment = node.getCaveNodeType().hasEnvironment() ? node.getCaveNodeType().getEnvironment() : caveType.getEnvironment();
        final int chunkLowX = ChunkUtil.minBlock(execution.getX());
        final int chunkLowZ = ChunkUtil.minBlock(execution.getZ());
        final int chunkHighX = ChunkUtil.maxBlock(execution.getX());
        final int chunkHighZ = ChunkUtil.maxBlock(execution.getZ());
        final int minX = Math.max(chunkLowX, shapeBounds.getLowBoundX());
        final int minY = shapeBounds.getLowBoundY();
        final int minZ = Math.max(chunkLowZ, shapeBounds.getLowBoundZ());
        final int maxX = Math.min(chunkHighX, shapeBounds.getHighBoundX());
        final int maxY = shapeBounds.getHighBoundY();
        final int maxZ = Math.min(chunkHighZ, shapeBounds.getHighBoundZ());
        for (int x = minX; x <= maxX; ++x) {
            final int cx = x - chunkLowX;
            for (int z = minZ; z <= maxZ; ++z) {
                final int cz = z - chunkLowZ;
                int maximumY = maxY;
                boolean heightLimited = false;
                if (surfaceLimited) {
                    final int chunkHeight = chunk.getHeight(cx, cz);
                    if (maximumY >= chunkHeight) {
                        maximumY = chunkHeight;
                        heightLimited = true;
                    }
                }
                int lowest = Integer.MAX_VALUE;
                int lowestPossible = Integer.MAX_VALUE;
                int highest = Integer.MIN_VALUE;
                int highestPossible = Integer.MIN_VALUE;
                final double t = this.shape.getProjection(x, z);
                if (this.shape.isValidProjection(t)) {
                    final double centerY = this.shape.getYAt(t);
                    final double shapeHeight = this.shape.getHeightAtProjection(seed, x, z, t, centerY, caveType, this.distortion);
                    if (shapeHeight > 0.0) {
                        final int floorY = this.getFloor(seed, x, z, centerY, shapeHeight, minY);
                        final int ceilingY = this.getCeiling(seed, x, z, centerY, shapeHeight, maximumY);
                        if (floorY < lowestPossible) {
                            lowestPossible = floorY;
                        }
                        if (ceilingY > highestPossible) {
                            highestPossible = ceilingY;
                        }
                        for (int y = floorY; y <= ceilingY; ++y) {
                            final int current = execution.getBlock(cx, y, cz);
                            final int currentFluid = execution.getFluid(cx, y, cz);
                            final boolean isCandidateBlock = !surfaceLimited || current != 0;
                            if (isCandidateBlock) {
                                final BlockFluidEntry blockEntry = CaveNodeShapeUtils.getFillingBlock(caveType, caveNodeType, y, random);
                                if (caveType.getBlockMask().eval(current, currentFluid, blockEntry.blockId(), blockEntry.fluidId())) {
                                    if (execution.setBlock(cx, y, cz, (byte)6, blockEntry, environment)) {
                                        if (y < lowest) {
                                            lowest = y;
                                        }
                                        if (y > highest) {
                                            highest = y;
                                        }
                                    }
                                    if (execution.setFluid(cx, y, cz, (byte)6, blockEntry.fluidId(), environment)) {
                                        if (y < lowest) {
                                            lowest = y;
                                        }
                                        if (y > highest) {
                                            highest = y;
                                        }
                                    }
                                }
                            }
                        }
                        final CaveNodeType.CaveNodeCoverEntry[] covers2;
                        final CaveNodeType.CaveNodeCoverEntry[] covers = covers2 = caveNodeType.getCovers();
                        for (final CaveNodeType.CaveNodeCoverEntry cover : covers2) {
                            final CaveNodeType.CaveNodeCoverEntry.Entry entry = cover.get(random);
                            final int y2 = CaveNodeShapeUtils.getCoverHeight(lowest, lowestPossible, highest, highestPossible, heightLimited, cover, entry);
                            if (y2 >= 0 && cover.getDensityCondition().eval(seed + node.getSeedOffset(), x, z) && cover.getHeightCondition().eval(seed, x, z, y2, random) && cover.getMapCondition().eval(seed, x, z) && CaveNodeShapeUtils.isCoverMatchingParent(cx, cz, y2, execution, cover)) {
                                execution.setBlock(cx, y2, cz, (byte)5, entry.getEntry(), environment);
                                execution.setFluid(cx, y2, cz, (byte)5, entry.getEntry().fluidId(), environment);
                            }
                        }
                        if (CaveNodeShapeUtils.invalidateCover(cx, lowest - 1, cz, CaveNodeType.CaveNodeCoverType.CEILING, execution, blockTypeMap)) {
                            final BlockFluidEntry blockEntry2 = CaveNodeShapeUtils.getFillingBlock(caveType, caveNodeType, lowest - 1, random);
                            execution.overrideBlock(cx, lowest - 1, cz, (byte)6, blockEntry2);
                            execution.overrideFluid(cx, lowest - 1, cz, (byte)6, blockEntry2.fluidId());
                        }
                        if (CaveNodeShapeUtils.invalidateCover(cx, highest + 1, cz, CaveNodeType.CaveNodeCoverType.FLOOR, execution, blockTypeMap)) {
                            final BlockFluidEntry blockEntry2 = CaveNodeShapeUtils.getFillingBlock(caveType, caveNodeType, highest + 1, random);
                            execution.overrideBlock(cx, highest + 1, cz, (byte)6, blockEntry2);
                            execution.overrideFluid(cx, highest + 1, cz, (byte)6, blockEntry2.fluidId());
                        }
                    }
                }
            }
        }
    }
    
    private int getFloor(final int seed, final double x, final double z, final double centerY, double height, final int minY) {
        height *= this.distortion.getFloorFactor(seed, x, z);
        final double floorY = this.shape.getFloor(x, z, centerY, height);
        return Math.max(MathUtil.floor(floorY), minY);
    }
    
    private int getCeiling(final int seed, final double x, final double z, final double centerY, double height, final int maxY) {
        height *= this.distortion.getCeilingFactor(seed, x, z);
        final double ceilingY = this.shape.getCeiling(x, z, centerY, height);
        return Math.min(MathUtil.ceil(ceilingY), maxY);
    }
    
    public static class DistortedCaveNodeShapeGenerator implements CaveNodeShapeEnum.CaveNodeShapeGenerator
    {
        private final DistortedShape.Factory shapeFactory;
        private final IDoubleRange widthRange;
        private final IDoubleRange midWidthRange;
        private final IDoubleRange heightRange;
        private final IDoubleRange midHeightRange;
        private final IDoubleRange lengthRange;
        private final ShapeDistortion distortion;
        private final boolean inheritParentRadius;
        private final GeneralNoise.InterpolationFunction interpolation;
        
        public DistortedCaveNodeShapeGenerator(final DistortedShape.Factory shapeFactory, final IDoubleRange widthRange, final IDoubleRange heightRange, @Nullable final IDoubleRange midWidthRange, @Nullable final IDoubleRange midHeightRange, @Nullable final IDoubleRange lengthRange, final boolean inheritParentRadius, final ShapeDistortion distortion, @Nullable final GeneralNoise.InterpolationFunction interpolation) {
            this.shapeFactory = shapeFactory;
            this.widthRange = widthRange;
            this.heightRange = heightRange;
            this.midWidthRange = midWidthRange;
            this.midHeightRange = midHeightRange;
            this.lengthRange = lengthRange;
            this.distortion = distortion;
            this.inheritParentRadius = inheritParentRadius;
            this.interpolation = interpolation;
        }
        
        @Nonnull
        @Override
        public CaveNodeShape generateCaveNodeShape(final Random random, final CaveType caveType, @Nullable final CaveNode parentNode, @Nonnull final CaveNodeType.CaveNodeChildEntry childEntry, @Nonnull final Vector3d position, final float yaw, final float pitch) {
            final double length = getLength(this.lengthRange, random);
            final Vector3d origin = getOrigin(position, parentNode, childEntry);
            final Vector3d direction = getDirection(yaw, pitch, length);
            final double startWidth = getStartWidth(this.inheritParentRadius, parentNode, this.widthRange, random);
            final double startHeight = getStartHeight(this.inheritParentRadius, parentNode, this.heightRange, random);
            final double endWidth = this.widthRange.getValue(random);
            final double endHeight = this.heightRange.getValue(random);
            final double midWidth = getMiddleRadius(startWidth, endWidth, this.midWidthRange, random);
            final double midHeight = getMiddleRadius(startHeight, endHeight, this.midHeightRange, random);
            final DistortedShape shape = this.shapeFactory.create(origin, direction, length, startWidth, startHeight, midWidth, midHeight, endWidth, endHeight, this.interpolation);
            return new DistortedCaveNodeShape(caveType, shape, this.distortion);
        }
        
        @Nonnull
        private static Vector3d getOrigin(@Nonnull final Vector3d origin, @Nullable final CaveNode parentNode, @Nonnull final CaveNodeType.CaveNodeChildEntry childEntry) {
            if (parentNode == null) {
                return origin;
            }
            final Vector3d offset = CaveNodeShapeUtils.getOffset(parentNode, childEntry);
            origin.add(offset);
            return origin.add(offset);
        }
        
        private static double getLength(@Nullable final IDoubleRange lengthRange, final Random random) {
            if (lengthRange == null) {
                return 0.0;
            }
            return lengthRange.getValue(random);
        }
        
        @Nonnull
        private static Vector3d getDirection(final double yaw, double pitch, final double length) {
            if (length == 0.0) {
                return Vector3d.ZERO;
            }
            pitch = AbstractDistortedShape.clampPitch(pitch);
            return new Vector3d(TrigMathUtil.sin(pitch) * TrigMathUtil.cos(yaw), TrigMathUtil.cos(pitch), TrigMathUtil.sin(pitch) * TrigMathUtil.sin(yaw)).scale(length);
        }
        
        private static double getStartWidth(final boolean inheritParentRadius, @Nullable final CaveNode parentNode, @Nonnull final IDoubleRange fallback, final Random random) {
            if (inheritParentRadius) {
                return CaveNodeShapeUtils.getEndWidth(parentNode, fallback, random);
            }
            return fallback.getValue(random);
        }
        
        private static double getStartHeight(final boolean inheritParentRadius, @Nullable final CaveNode parentNode, @Nonnull final IDoubleRange fallback, final Random random) {
            if (inheritParentRadius) {
                return CaveNodeShapeUtils.getEndHeight(parentNode, fallback, random);
            }
            return fallback.getValue(random);
        }
        
        private static double getMiddleRadius(final double start, final double end, @Nullable final IDoubleRange range, final Random random) {
            return (range == null) ? ((start - end) * 0.5 + start) : range.getValue(random);
        }
    }
}
