// 
// 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.CaveNodeType;
import com.hypixel.hytale.server.worldgen.cave.element.CaveNode;
import java.util.Random;
import com.hypixel.hytale.procedurallib.supplier.IDoubleRange;
import com.hypixel.hytale.math.util.MathUtil;
import javax.annotation.Nonnull;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.server.worldgen.cave.CaveType;
import com.hypixel.hytale.server.worldgen.util.bounds.IWorldBounds;

public class CylinderCaveNodeShape extends AbstractCaveNodeShape implements IWorldBounds
{
    private final CaveType caveType;
    @Nonnull
    private final Vector3d o;
    @Nonnull
    private final Vector3d v;
    private final int lowBoundX;
    private final int lowBoundY;
    private final int lowBoundZ;
    private final int highBoundX;
    private final int highBoundY;
    private final int highBoundZ;
    private final double radius1;
    private final double radius2;
    private final double middleRadius;
    
    public CylinderCaveNodeShape(final CaveType caveType, @Nonnull final Vector3d o, @Nonnull final Vector3d v, final double radius1, final double radius2, final double middleRadius) {
        this.caveType = caveType;
        this.o = o;
        this.v = v;
        this.radius1 = radius1;
        this.radius2 = radius2;
        this.middleRadius = middleRadius;
        this.lowBoundX = MathUtil.floor(Math.min(o.x, o.x + v.x) - Math.max(radius1, radius2));
        this.lowBoundY = MathUtil.floor(Math.min(o.y, o.y + v.y) - Math.max(radius1, radius2));
        this.lowBoundZ = MathUtil.floor(Math.min(o.z, o.z + v.z) - Math.max(radius1, radius2));
        this.highBoundX = MathUtil.ceil(Math.max(o.x, o.x + v.x) + Math.max(radius1, radius2));
        this.highBoundY = MathUtil.ceil(Math.max(o.y, o.y + v.y) + Math.max(radius1, radius2));
        this.highBoundZ = MathUtil.ceil(Math.max(o.z, o.z + v.z) + Math.max(radius1, radius2));
    }
    
    @Nonnull
    @Override
    public Vector3d getStart() {
        return this.o.clone();
    }
    
    @Nonnull
    @Override
    public Vector3d getEnd() {
        final double x = this.o.x + this.v.x;
        final double y = this.o.y + this.v.y;
        final double z = this.o.z + this.v.z;
        return new Vector3d(x, y, z);
    }
    
    @Nonnull
    @Override
    public Vector3d getAnchor(@Nonnull final Vector3d vector, final double t, final double tv, final double th) {
        final double radius = this.getRadiusAt(t);
        return CaveNodeShapeUtils.getPipeAnchor(vector, this.o, this.v, radius, radius, radius, t, tv, th);
    }
    
    @Nonnull
    @Override
    public IWorldBounds getBounds() {
        return this;
    }
    
    @Override
    public int getLowBoundX() {
        return this.lowBoundX;
    }
    
    @Override
    public int getLowBoundZ() {
        return this.lowBoundZ;
    }
    
    @Override
    public int getHighBoundX() {
        return this.highBoundX;
    }
    
    @Override
    public int getHighBoundZ() {
        return this.highBoundZ;
    }
    
    @Override
    public int getLowBoundY() {
        return this.lowBoundY;
    }
    
    @Override
    public int getHighBoundY() {
        return this.highBoundY;
    }
    
    public double getRadius1() {
        return this.radius1;
    }
    
    public double getRadius2() {
        return this.radius2;
    }
    
    @Override
    public boolean shouldReplace(final int seed, final double x, final double z, final int y) {
        final double t = this.projectPointOnNode(x, y, z);
        if (t < 0.0 || t > 1.0) {
            return false;
        }
        double dx = this.o.x + this.v.x * t;
        double dy = this.o.y + this.v.y * t;
        double dz = this.o.z + this.v.z * t;
        final double r = this.getRadiusAt(t) * this.caveType.getHeightRadiusFactor(seed, x, z, MathUtil.floor(dy));
        dx -= x;
        dy -= y;
        dz -= z;
        return dx * dx + dy * dy + dz * dz < r * r;
    }
    
    private double projectPointOnNode(final double px, final double py, final double pz) {
        double t = (px - this.o.x) * this.v.x + (py - this.o.y) * this.v.y + (pz - this.o.z) * this.v.z;
        t /= this.v.x * this.v.x + this.v.y * this.v.y + this.v.z * this.v.z;
        return t;
    }
    
    private double getRadiusAt(final double t) {
        if (t < 0.5) {
            return (this.middleRadius - this.radius1) * 2.0 * t + this.radius1;
        }
        return (this.radius2 - this.middleRadius) * 2.0 * (t - 0.5) + this.middleRadius;
    }
    
    @Override
    public double getFloorPosition(final int seed, final double x, final double z) {
        for (int y = this.getLowBoundY(); y < this.getHighBoundY(); ++y) {
            if (this.shouldReplace(seed, x, z, y)) {
                return y - 1;
            }
        }
        return -1.0;
    }
    
    @Override
    public double getCeilingPosition(final int seed, final double x, final double z) {
        for (int y = this.getHighBoundY(); y > this.getLowBoundY(); --y) {
            if (this.shouldReplace(seed, x, z, y)) {
                return y + 1;
            }
        }
        return -1.0;
    }
    
    @Nonnull
    @Override
    public String toString() {
        return "CylinderCaveNodeShape{caveType=" + String.valueOf(this.caveType) + ", o=" + String.valueOf(this.o) + ", v=" + String.valueOf(this.v) + ", lowBoundX=" + this.lowBoundX + ", lowBoundY=" + this.lowBoundY + ", lowBoundZ=" + this.lowBoundZ + ", highBoundX=" + this.highBoundX + ", highBoundY=" + this.highBoundY + ", highBoundZ=" + this.highBoundZ + ", radius1=" + this.radius1 + ", radius2=" + this.radius2 + ", middleRadius=" + this.middleRadius;
    }
    
    public static class CylinderCaveNodeShapeGenerator implements CaveNodeShapeEnum.CaveNodeShapeGenerator
    {
        private final IDoubleRange radius;
        private final IDoubleRange middleRadius;
        private final IDoubleRange length;
        private final boolean inheritParentRadius;
        
        public CylinderCaveNodeShapeGenerator(final IDoubleRange radius, final IDoubleRange middleRadius, final IDoubleRange length, final boolean inheritParentRadius) {
            this.radius = radius;
            this.middleRadius = middleRadius;
            this.length = length;
            this.inheritParentRadius = inheritParentRadius;
        }
        
        @Nonnull
        @Override
        public CaveNodeShape generateCaveNodeShape(@Nonnull final Random random, final CaveType caveType, final CaveNode parentNode, @Nonnull final CaveNodeType.CaveNodeChildEntry childEntry, @Nonnull final Vector3d origin, final float yaw, final float pitch) {
            final double l = this.length.getValue(random.nextDouble());
            final Vector3d direction = new Vector3d(TrigMathUtil.sin(pitch) * TrigMathUtil.cos(yaw), TrigMathUtil.cos(pitch), TrigMathUtil.sin(pitch) * TrigMathUtil.sin(yaw)).scale(l);
            double radius1;
            if (this.inheritParentRadius) {
                radius1 = CaveNodeShapeUtils.getEndRadius(parentNode, this.radius, random);
            }
            else {
                radius1 = this.radius.getValue(random.nextDouble());
            }
            final double radius2 = this.radius.getValue(random.nextDouble());
            double radius3 = (radius2 - radius1) * 0.5 + radius1;
            if (this.middleRadius != null) {
                radius3 = this.middleRadius.getValue(random.nextDouble());
            }
            final Vector3d offset = CaveNodeShapeUtils.getOffset(parentNode, childEntry);
            origin.add(offset);
            return new CylinderCaveNodeShape(caveType, origin, direction, radius1, radius2, radius3);
        }
    }
}
