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

package com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions;

import java.util.function.Consumer;
import com.hypixel.hytale.math.vector.Vector3d;
import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction;
import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.distancefunctions.DistanceFunction;
import com.hypixel.hytale.builtin.hytalegenerator.density.nodes.positions.returntypes.ReturnType;
import javax.annotation.Nonnull;
import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider;
import com.hypixel.hytale.builtin.hytalegenerator.density.Density;

public class PositionsDensity extends Density
{
    @Nonnull
    private final PositionProvider positionProvider;
    private final double maxDistance;
    private final double maxDistanceRaw;
    @Nonnull
    private final ReturnType returnType;
    @Nonnull
    private final DistanceFunction distanceFunction;
    
    public PositionsDensity(@Nonnull final PositionProvider positionsField, @Nonnull final ReturnType returnType, @Nonnull final DistanceFunction distanceFunction, final double maxDistance) {
        if (maxDistance < 0.0) {
            throw new IllegalArgumentException("negative distance");
        }
        this.positionProvider = positionsField;
        this.maxDistance = maxDistance;
        this.maxDistanceRaw = maxDistance * maxDistance;
        this.returnType = returnType;
        this.distanceFunction = distanceFunction;
    }
    
    @Nonnull
    public static Double2DoubleFunction cellNoiseDistanceFunction(final double maxDistance) {
        return d -> d / maxDistance - 1.0;
    }
    
    @Override
    public double process(@Nonnull final Context context) {
        final Vector3d min = context.position.clone().subtract(this.maxDistance);
        final Vector3d max = context.position.clone().add(this.maxDistance);
        final double[] distance = { Double.MAX_VALUE, Double.MAX_VALUE };
        final boolean[] hasClosestPoint = new boolean[2];
        final Vector3d closestPoint = new Vector3d();
        final Vector3d previousClosestPoint = new Vector3d();
        final Vector3d localPoint = new Vector3d();
        final Consumer<Vector3d> positionsConsumer = providedPoint -> {
            localPoint.x = providedPoint.x - context.position.x;
            localPoint.y = providedPoint.y - context.position.y;
            localPoint.z = providedPoint.z - context.position.z;
            final double newDistance = this.distanceFunction.getDistance(localPoint);
            if (this.maxDistanceRaw < newDistance) {
                return;
            }
            else {
                distance[1] = Math.max(Math.min(distance[1], newDistance), distance[0]);
                if (newDistance < distance[0]) {
                    distance[0] = newDistance;
                    previousClosestPoint.assign(closestPoint);
                    closestPoint.assign(providedPoint);
                    hasClosestPoint[1] = hasClosestPoint[0];
                    hasClosestPoint[0] = true;
                }
                return;
            }
        };
        final PositionProvider.Context positionsContext = new PositionProvider.Context();
        positionsContext.minInclusive = min;
        positionsContext.maxExclusive = max;
        positionsContext.consumer = positionsConsumer;
        positionsContext.workerId = context.workerId;
        this.positionProvider.positionsIn(positionsContext);
        distance[0] = Math.sqrt(distance[0]);
        distance[1] = Math.sqrt(distance[1]);
        return this.returnType.get(distance[0], distance[1], context.position.clone(), hasClosestPoint[0] ? closestPoint : null, hasClosestPoint[1] ? previousClosestPoint : null, context);
    }
}
