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

package com.hypixel.hytale.math.random;

import com.hypixel.hytale.function.function.TriFunction;
import java.util.function.ToDoubleBiFunction;
import java.util.function.BiPredicate;
import java.util.function.Predicate;
import java.util.function.ToIntFunction;
import javax.annotation.Nullable;
import java.util.Iterator;
import java.util.function.ToDoubleFunction;
import java.util.Collection;
import com.hypixel.hytale.math.vector.Vector3d;
import java.util.List;
import java.time.Duration;
import javax.annotation.Nonnull;
import java.util.concurrent.ThreadLocalRandom;

public final class RandomExtra
{
    private RandomExtra() {
    }
    
    public static double randomBinomial() {
        final ThreadLocalRandom random = ThreadLocalRandom.current();
        final double a = random.nextDouble();
        final double b = random.nextDouble();
        return a - b;
    }
    
    public static double randomRange(@Nonnull final double[] range) {
        return randomRange(range[0], range[1]);
    }
    
    public static double randomRange(final double from, final double to) {
        return from + ThreadLocalRandom.current().nextDouble() * (to - from);
    }
    
    public static float randomRange(@Nonnull final float[] range) {
        return randomRange(range[0], range[1]);
    }
    
    public static float randomRange(final float from, final float to) {
        return from + ThreadLocalRandom.current().nextFloat() * (to - from);
    }
    
    public static int randomRange(final int bound) {
        return ThreadLocalRandom.current().nextInt(bound);
    }
    
    public static int randomRange(@Nonnull final int[] range) {
        return randomRange(range[0], range[1]);
    }
    
    public static int randomRange(final int from, final int to) {
        return ThreadLocalRandom.current().nextInt(to - from + 1) + from;
    }
    
    public static long randomRange(final long from, final long to) {
        return ThreadLocalRandom.current().nextLong(to - from + 1L) + from;
    }
    
    public static Duration randomDuration(@Nonnull final Duration from, @Nonnull final Duration to) {
        return Duration.ofNanos(randomRange(from.toNanos(), to.toNanos()));
    }
    
    public static boolean randomBoolean() {
        return ThreadLocalRandom.current().nextBoolean();
    }
    
    public static <T> T randomElement(@Nonnull final List<T> collection) {
        return collection.get(randomRange(collection.size()));
    }
    
    @Nonnull
    public static Vector3d jitter(@Nonnull final Vector3d vec, final double maxRange) {
        final ThreadLocalRandom current = ThreadLocalRandom.current();
        vec.x += current.nextDouble() * maxRange;
        vec.y += current.nextDouble() * maxRange;
        vec.z += current.nextDouble() * maxRange;
        return vec;
    }
    
    @Nullable
    public static <T> T randomWeightedElement(@Nonnull final Collection<? extends T> elements, @Nonnull final ToDoubleFunction<T> weight) {
        final Iterator<? extends T> i = elements.iterator();
        double sumWeights = 0.0;
        while (i.hasNext()) {
            sumWeights += weight.applyAsDouble((T)i.next());
        }
        return randomWeightedElement(elements, weight, sumWeights);
    }
    
    @Nullable
    public static <T> T randomWeightedElement(@Nonnull final Collection<? extends T> elements, @Nonnull final ToDoubleFunction<T> weight, double sumWeights) {
        if (sumWeights == 0.0) {
            return null;
        }
        final Iterator<? extends T> i = elements.iterator();
        T result = null;
        sumWeights *= ThreadLocalRandom.current().nextDouble();
        while (i.hasNext()) {
            result = (T)i.next();
            sumWeights -= weight.applyAsDouble(result);
            if (sumWeights < 0.0) {
                break;
            }
        }
        return result;
    }
    
    @Nullable
    public static <T> T randomIntWeightedElement(@Nonnull final Collection<? extends T> elements, @Nonnull final ToIntFunction<T> weight) {
        final Iterator<? extends T> i = elements.iterator();
        int sumWeights = 0;
        while (i.hasNext()) {
            sumWeights += weight.applyAsInt((T)i.next());
        }
        return randomIntWeightedElement(elements, weight, sumWeights);
    }
    
    @Nullable
    public static <T> T randomIntWeightedElement(@Nonnull final Collection<? extends T> elements, @Nonnull final ToIntFunction<T> weight, int sumWeights) {
        if (sumWeights == 0) {
            return null;
        }
        final Iterator<? extends T> i = elements.iterator();
        T result = null;
        sumWeights = randomRange(sumWeights);
        while (i.hasNext()) {
            result = (T)i.next();
            sumWeights -= weight.applyAsInt(result);
            if (sumWeights < 0) {
                break;
            }
        }
        return result;
    }
    
    @Nullable
    public static <T> T randomWeightedElementFiltered(@Nonnull final Collection<? extends T> elements, @Nonnull final Predicate<T> filter, @Nonnull final ToIntFunction<T> weight) {
        final Iterator<? extends T> i = elements.iterator();
        int sumWeights = 0;
        while (i.hasNext()) {
            final T t = (T)i.next();
            if (filter.test(t)) {
                sumWeights += weight.applyAsInt(t);
            }
        }
        return randomWeightedElementFiltered(elements, filter, weight, sumWeights);
    }
    
    @Nullable
    public static <T> T randomWeightedElementFiltered(@Nonnull final Collection<? extends T> elements, @Nonnull final Predicate<T> filter, @Nonnull final ToIntFunction<T> weight, int sumWeights) {
        if (sumWeights == 0) {
            return null;
        }
        final Iterator<? extends T> i = elements.iterator();
        T result = null;
        sumWeights = randomRange(sumWeights);
        while (i.hasNext()) {
            result = (T)i.next();
            if (filter.test(result)) {
                sumWeights -= weight.applyAsInt(result);
                if (sumWeights < 0) {
                    break;
                }
                continue;
            }
        }
        return result;
    }
    
    @Nullable
    public static <T> T randomWeightedElement(@Nonnull final Collection<? extends T> elements, @Nonnull final Predicate<T> filter, @Nonnull final ToDoubleFunction<T> weight) {
        final Iterator<? extends T> i = elements.iterator();
        double sumWeights = 0.0;
        while (i.hasNext()) {
            final T t = (T)i.next();
            if (filter.test(t)) {
                sumWeights += weight.applyAsDouble(t);
            }
        }
        return randomWeightedElement(elements, filter, weight, sumWeights);
    }
    
    @Nullable
    public static <T> T randomWeightedElement(@Nonnull final Collection<? extends T> elements, @Nonnull final Predicate<T> filter, @Nonnull final ToDoubleFunction<T> weight, double sumWeights) {
        if (sumWeights == 0.0) {
            return null;
        }
        final Iterator<? extends T> i = elements.iterator();
        T result = null;
        sumWeights *= ThreadLocalRandom.current().nextDouble();
        while (i.hasNext()) {
            result = (T)i.next();
            if (filter.test(result)) {
                sumWeights -= weight.applyAsDouble(result);
                if (sumWeights < 0.0) {
                    break;
                }
                continue;
            }
        }
        return result;
    }
    
    @Nullable
    public static <T, U> T randomWeightedElement(@Nonnull final Collection<? extends T> elements, @Nonnull final BiPredicate<T, U> filter, @Nonnull final ToDoubleBiFunction<T, U> weight, double sumWeights, final U meta) {
        if (sumWeights == 0.0) {
            return null;
        }
        final Iterator<? extends T> i = elements.iterator();
        T result = null;
        sumWeights *= ThreadLocalRandom.current().nextDouble();
        while (i.hasNext()) {
            result = (T)i.next();
            if (filter.test(result, meta)) {
                sumWeights -= weight.applyAsDouble(result, meta);
                if (sumWeights < 0.0) {
                    break;
                }
                continue;
            }
        }
        return result;
    }
    
    public static <T> void reservoirSample(@Nonnull final List<T> input, @Nonnull final Predicate<T> matcher, final int count, @Nonnull final List<T> picked) {
        int selected = 0;
        for (int i = 0; i < input.size(); ++i) {
            final T element = input.get(i);
            if (matcher.test(element)) {
                if (selected < count) {
                    picked.add(element);
                }
                else {
                    final int j = randomRange(selected + 1);
                    if (j < count) {
                        picked.set(j, element);
                    }
                }
                ++selected;
            }
        }
    }
    
    public static <E, S extends List<E>, F, T extends List<F>, G, H> void reservoirSample(@Nonnull final S input, @Nonnull final TriFunction<E, G, H, F> filter, final int count, @Nonnull final T picked, final G g, final H h) {
        int selected = 0;
        for (int i = 0; i < input.size(); ++i) {
            final F f = filter.apply(input.get(i), g, h);
            if (f != null) {
                if (selected < count) {
                    picked.add(f);
                }
                else {
                    final int j = randomRange(selected + 1);
                    if (j < count) {
                        picked.set(j, f);
                    }
                }
                ++selected;
            }
        }
    }
    
    public static <E, T extends List<E>> void reservoirSample(final E element, final int count, @Nonnull final T picked) {
        if (picked.size() < count) {
            picked.add(element);
        }
        else {
            final int i = randomRange(count + 1);
            if (i < count) {
                picked.set(i, element);
            }
        }
    }
    
    public static int pickWeightedIndex(@Nonnull final double[] weights) {
        double sum = 0.0;
        for (final double weight : weights) {
            sum += weight;
        }
        double randomWeight = ThreadLocalRandom.current().nextDouble(sum);
        for (int i = 0; i < weights.length - 1; ++i) {
            randomWeight -= weights[i];
            if (randomWeight <= 0.0) {
                return i;
            }
        }
        return weights.length - 1;
    }
}
