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

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

import java.util.function.Consumer;
import com.hypixel.hytale.builtin.hytalegenerator.framework.math.Calculator;
import java.util.ArrayList;
import com.hypixel.hytale.math.vector.Vector3d;
import com.hypixel.hytale.builtin.hytalegenerator.threadindexer.WorkerIndexer;
import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction;
import com.hypixel.hytale.builtin.hytalegenerator.positionproviders.PositionProvider;
import javax.annotation.Nonnull;
import com.hypixel.hytale.builtin.hytalegenerator.density.Density;

public class PositionsHorizontalPinchDensity extends Density
{
    @Nonnull
    private Density input;
    @Nonnull
    private final PositionProvider positions;
    @Nonnull
    private final Double2DoubleFunction pinchCurve;
    @Nonnull
    private final WorkerIndexer.Data<Cache> threadData;
    private final double maxDistance;
    private final boolean distanceNormalized;
    private final double positionsMinY;
    private final double positionsMaxY;
    
    public PositionsHorizontalPinchDensity(@Nonnull final Density input, @Nonnull final PositionProvider positions, @Nonnull final Double2DoubleFunction pinchCurve, final double maxDistance, final boolean distanceNormalized, double positionsMinY, final double positionsMaxY, final int threadCount) {
        if (maxDistance < 0.0) {
            throw new IllegalArgumentException();
        }
        if (positionsMinY > positionsMaxY) {
            positionsMinY = positionsMaxY;
        }
        this.input = input;
        this.positions = positions;
        this.pinchCurve = pinchCurve;
        this.maxDistance = maxDistance;
        this.distanceNormalized = distanceNormalized;
        this.positionsMinY = positionsMinY;
        this.positionsMaxY = positionsMaxY;
        this.threadData = new WorkerIndexer.Data<Cache>(threadCount, Cache::new);
    }
    
    @Override
    public double process(@Nonnull final Context context) {
        if (this.input == null) {
            return 0.0;
        }
        if (this.positions == null) {
            return this.input.process(context);
        }
        final Cache cache = this.threadData.get(context.workerId);
        Vector3d warpVector;
        if (cache.x == context.position.x && cache.z == context.position.z && !cache.hasValue) {
            warpVector = cache.warpVector;
        }
        else {
            warpVector = this.calculateWarpVector(context);
            cache.warpVector = warpVector;
        }
        final Vector3d position = new Vector3d(warpVector.x + context.position.x, warpVector.y + context.position.y, warpVector.z + context.position.z);
        final Context childContext = new Context(context);
        childContext.position = position;
        return this.input.process(childContext);
    }
    
    @Override
    public void setInputs(@Nonnull final Density[] inputs) {
        if (inputs.length == 0) {
            this.input = new ConstantValueDensity(0.0);
        }
        this.input = inputs[0];
    }
    
    public Vector3d calculateWarpVector(@Nonnull final Context context) {
        final Vector3d position = context.position;
        final Vector3d min = new Vector3d(position.x - this.maxDistance, this.positionsMinY, position.z - this.maxDistance);
        final Vector3d max = new Vector3d(position.x + this.maxDistance, this.positionsMaxY, position.z + this.maxDistance);
        final Vector3d samplePoint = position.clone();
        final ArrayList<Vector3d> warpVectors = new ArrayList<Vector3d>(10);
        final ArrayList<Double> warpDistances = new ArrayList<Double>(10);
        final Consumer<Vector3d> consumer = iteratedPosition -> {
            final double distance2 = Calculator.distance(iteratedPosition.x, iteratedPosition.z, samplePoint.x, samplePoint.z);
            if (distance2 > this.maxDistance) {
                return;
            }
            else {
                final double normalizedDistance = distance2 / this.maxDistance;
                final Vector3d warpVector2 = iteratedPosition.clone().addScaled(samplePoint, -1.0);
                warpVector2.setY(0.0);
                double radialDistance2;
                if (this.distanceNormalized) {
                    final double radialDistance = this.pinchCurve.applyAsDouble(normalizedDistance);
                    radialDistance2 = radialDistance * this.maxDistance;
                }
                else {
                    radialDistance2 = this.pinchCurve.applyAsDouble(distance2);
                }
                if (Math.abs(warpVector2.length()) >= 1.0E-9) {
                    warpVector2.setLength(radialDistance2);
                }
                warpVectors.add(warpVector2);
                warpDistances.add(normalizedDistance);
                return;
            }
        };
        final PositionProvider.Context positionsContext = new PositionProvider.Context();
        positionsContext.minInclusive = min;
        positionsContext.maxExclusive = max;
        positionsContext.consumer = consumer;
        this.positions.positionsIn(positionsContext);
        if (warpVectors.isEmpty()) {
            return new Vector3d(0.0, 0.0, 0.0);
        }
        if (warpVectors.size() == 1) {
            return warpVectors.getFirst();
        }
        final int possiblePointsSize = warpVectors.size();
        final ArrayList<Double> weights = new ArrayList<Double>(warpDistances.size());
        double totalWeight = 0.0;
        for (int i = 0; i < possiblePointsSize; ++i) {
            final double distance = warpDistances.get(i);
            final double weight = 1.0 - distance;
            weights.add(weight);
            totalWeight += weight;
        }
        final Vector3d totalWarpVector = new Vector3d();
        for (int j = 0; j < possiblePointsSize; ++j) {
            final double weight2 = weights.get(j) / totalWeight;
            final Vector3d warpVector = warpVectors.get(j);
            warpVector.scale(weight2);
            totalWarpVector.add(warpVector);
        }
        return totalWarpVector;
    }
    
    private static class Cache
    {
        double x;
        double z;
        Vector3d warpVector;
        boolean hasValue;
    }
}
