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

package com.hypixel.hytale.builtin.hytalegenerator.framework.math;

import com.hypixel.hytale.builtin.hytalegenerator.ArrayUtil;
import java.util.Iterator;
import java.util.ArrayList;
import com.hypixel.hytale.builtin.hytalegenerator.delimiters.RangeDouble;
import javax.annotation.Nonnull;
import java.util.List;
import it.unimi.dsi.fastutil.doubles.Double2DoubleFunction;
import java.util.function.Function;

public class NodeFunction implements Function<Double, Double>, Double2DoubleFunction
{
    private static final double FALLBACK_VALUE = 0.0;
    @Nonnull
    private final List<double[]> points;
    @Nonnull
    private final List<RangeDouble> ranges;
    
    public NodeFunction() {
        this.points = new ArrayList<double[]>(2);
        this.ranges = new ArrayList<RangeDouble>(2);
    }
    
    @Override
    public Double apply(@Nonnull final Double input) {
        return this.get((Object)input);
    }
    
    @Override
    public double get(final double input) {
        if (Double.isNaN(input)) {
            return 0.0;
        }
        if (this.points.isEmpty()) {
            return 0.0;
        }
        if (this.points.size() == 1 || input <= this.points.getFirst()[0]) {
            return this.points.getFirst()[1];
        }
        if (input >= this.points.getLast()[0]) {
            return this.points.getLast()[1];
        }
        final int indexBefore = this.indexBefore(input);
        final double[] before = this.points.get(indexBefore);
        final double[] after = this.points.get(indexBefore + 1);
        final double differenceY = after[1] - before[1];
        final double ratio = (input - before[0]) / (after[0] - before[0]);
        return before[1] + differenceY * ratio;
    }
    
    @Nonnull
    public NodeFunction addPoint(final double in, final double out) {
        for (final double[] point : this.points) {
            if (point[0] == in) {
                return this;
            }
        }
        this.points.add(new double[] { in, out });
        this.points.sort((a, b) -> {
            if (a[0] < b[0]) {
                return -1;
            }
            else if (a[0] == b[0]) {
                return 0;
            }
            else {
                return 1;
            }
        });
        this.initializeRanges();
        return this;
    }
    
    public boolean contains(final double x) {
        return this.points.parallelStream().anyMatch(point -> point[0] == x);
    }
    
    private void initializeRanges() {
        this.ranges.clear();
        for (int i = 0; i < this.points.size() - 1; ++i) {
            this.ranges.add(new RangeDouble(this.points.get(i)[0], this.points.get(i + 1)[0]));
        }
    }
    
    private int indexBefore(final double input) {
        return ArrayUtil.sortedSearch(this.ranges, Double.valueOf(input), (gauge, range) -> {
            if (gauge < range.min()) {
                return -1;
            }
            else if (gauge >= range.max()) {
                return 1;
            }
            else {
                return 0;
            }
        });
    }
}
