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

package com.hypixel.hytale.common.map;

import java.util.function.ToDoubleFunction;
import com.hypixel.hytale.common.util.ArrayUtil;
import java.util.function.IntFunction;
import java.util.function.Function;
import java.util.Arrays;
import java.util.function.ObjDoubleConsumer;
import java.util.function.Consumer;
import com.hypixel.hytale.function.function.BiDoubleToDoubleFunction;
import com.hypixel.hytale.function.function.BiLongToDoubleFunction;
import com.hypixel.hytale.function.function.BiIntToDoubleFunction;
import java.util.Random;
import java.util.function.DoubleSupplier;
import javax.annotation.Nullable;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import javax.annotation.Nonnull;
import java.util.Set;

public class WeightedMap<T> implements IWeightedMap<T>
{
    public static final double EPSILON = 0.99999;
    public static final double ONE_MINUS_EPSILON = 9.99999999995449E-6;
    @Nonnull
    private final Set<T> keySet;
    @Nonnull
    private final T[] keys;
    private final double[] values;
    private final double sum;
    
    @Nonnull
    public static <T> Builder<T> builder(final T[] emptyKeys) {
        return new Builder<T>(emptyKeys);
    }
    
    private WeightedMap(@Nonnull final T[] keys, final double[] values, final double sum) {
        Collections.addAll(this.keySet = new HashSet<T>(), keys);
        this.keys = keys;
        this.values = values;
        this.sum = sum;
    }
    
    @Nullable
    @Override
    public T get(final double value) {
        double weightPercentSum = Math.min(value, 0.99999) * this.sum;
        for (int i = 0; i < this.keys.length; ++i) {
            if ((weightPercentSum -= this.values[i]) <= 9.99999999995449E-6) {
                return this.keys[i];
            }
        }
        return null;
    }
    
    @Nullable
    @Override
    public T get(@Nonnull final DoubleSupplier supplier) {
        return this.get(supplier.getAsDouble());
    }
    
    @Nullable
    @Override
    public T get(@Nonnull final Random random) {
        return this.get(random.nextDouble());
    }
    
    @Nullable
    @Override
    public T get(final int x, final int z, @Nonnull final BiIntToDoubleFunction supplier) {
        return this.get(supplier.apply(x, z));
    }
    
    @Nullable
    @Override
    public T get(final long x, final long z, @Nonnull final BiLongToDoubleFunction supplier) {
        return this.get(supplier.apply(x, z));
    }
    
    @Nullable
    @Override
    public T get(final double x, final double z, @Nonnull final BiDoubleToDoubleFunction supplier) {
        return this.get(supplier.apply(x, z));
    }
    
    @Nullable
    @Override
    public <K> T get(final int seed, final int x, final int z, @Nonnull final SeedCoordinateFunction<K> supplier, final K k) {
        return this.get(supplier.apply(seed, x, z, k));
    }
    
    @Override
    public int size() {
        return this.keys.length;
    }
    
    @Override
    public boolean contains(final T obj) {
        return this.keySet.contains(obj);
    }
    
    @Override
    public void forEach(@Nonnull final Consumer<T> consumer) {
        for (final T o : this.keys) {
            consumer.accept(o);
        }
    }
    
    @Override
    public void forEachEntry(@Nonnull final ObjDoubleConsumer<T> consumer) {
        for (int i = 0; i < this.keys.length; ++i) {
            consumer.accept(this.keys[i], this.values[i]);
        }
    }
    
    @Nonnull
    @Override
    public T[] internalKeys() {
        return this.keys;
    }
    
    @Nonnull
    @Override
    public T[] toArray() {
        return Arrays.copyOf(this.keys, this.keys.length);
    }
    
    @Nonnull
    @Override
    public <K> IWeightedMap<K> resolveKeys(@Nonnull final Function<T, K> mapper, @Nonnull final IntFunction<K[]> arraySupplier) {
        final K[] array = arraySupplier.apply(this.keys.length);
        for (int i = 0; i < this.keys.length; ++i) {
            array[i] = mapper.apply(this.keys[i]);
        }
        return new WeightedMap<K>(array, this.values, this.sum);
    }
    
    @Nonnull
    @Override
    public String toString() {
        return "WeightedMap{keySet=" + String.valueOf(this.keySet) + ", sum=" + this.sum + ", keys=" + Arrays.toString(this.keys) + ", values=" + Arrays.toString(this.values);
    }
    
    private static class SingletonWeightedMap<T> implements IWeightedMap<T>
    {
        @Nonnull
        protected final T[] keys;
        protected final T key;
        
        private SingletonWeightedMap(@Nonnull final T[] keys) {
            this.keys = keys;
            this.key = ((keys.length > 0) ? keys[0] : null);
        }
        
        @Override
        public T get(final double value) {
            return this.key;
        }
        
        @Override
        public T get(final DoubleSupplier supplier) {
            return this.key;
        }
        
        @Override
        public T get(final Random random) {
            return this.key;
        }
        
        @Override
        public T get(final int x, final int z, final BiIntToDoubleFunction supplier) {
            return this.key;
        }
        
        @Override
        public T get(final long x, final long z, final BiLongToDoubleFunction supplier) {
            return this.key;
        }
        
        @Override
        public T get(final double x, final double z, final BiDoubleToDoubleFunction supplier) {
            return this.key;
        }
        
        @Override
        public <K> T get(final int seed, final int x, final int z, final SeedCoordinateFunction<K> supplier, final K k) {
            return this.key;
        }
        
        @Override
        public int size() {
            return this.keys.length;
        }
        
        @Override
        public boolean contains(@Nullable final T obj) {
            return obj != null && (obj == this.key || obj.equals(this.key));
        }
        
        @Override
        public void forEach(@Nonnull final Consumer<T> consumer) {
            if (this.key != null) {
                consumer.accept(this.key);
            }
        }
        
        @Override
        public void forEachEntry(@Nonnull final ObjDoubleConsumer<T> consumer) {
            if (this.key != null) {
                consumer.accept(this.key, 1.0);
            }
        }
        
        @Nonnull
        @Override
        public T[] internalKeys() {
            return this.keys;
        }
        
        @Nonnull
        @Override
        public T[] toArray() {
            return Arrays.copyOf(this.keys, this.keys.length);
        }
        
        @Nonnull
        @Override
        public <K> IWeightedMap<K> resolveKeys(@Nonnull final Function<T, K> mapper, @Nonnull final IntFunction<K[]> arraySupplier) {
            final K[] array = arraySupplier.apply(1);
            array[0] = mapper.apply(this.key);
            return new SingletonWeightedMap<K>(array);
        }
        
        @Nonnull
        @Override
        public String toString() {
            return "SingletonWeightedMap{key=" + String.valueOf(this.key);
        }
    }
    
    public static class Builder<T>
    {
        private final T[] emptyKeys;
        private T[] keys;
        private double[] values;
        private int size;
        
        private Builder(final T[] emptyKeys) {
            this.emptyKeys = emptyKeys;
            this.keys = emptyKeys;
            this.values = ArrayUtil.EMPTY_DOUBLE_ARRAY;
        }
        
        @Nonnull
        public Builder<T> putAll(@Nullable final IWeightedMap<T> map) {
            if (map != null) {
                this.ensureCapacity(map.size());
                map.forEachEntry(this::insert);
            }
            return this;
        }
        
        @Nonnull
        public Builder<T> putAll(@Nullable final T[] arr, @Nonnull final ToDoubleFunction<T> weight) {
            if (arr == null || arr.length == 0) {
                return this;
            }
            this.ensureCapacity(arr.length);
            for (final T t : arr) {
                this.insert(t, weight.applyAsDouble(t));
            }
            return this;
        }
        
        @Nonnull
        public Builder<T> put(final T obj, final double weight) {
            this.ensureCapacity(1);
            this.insert(obj, weight);
            return this;
        }
        
        public void ensureCapacity(final int toAdd) {
            final int minCapacity = this.size + toAdd;
            final int allocated = this.allocated();
            if (minCapacity > allocated) {
                final int newLength = Math.max(allocated + (allocated >> 1), minCapacity);
                this.resize(newLength);
            }
        }
        
        private void resize(final int newLength) {
            this.keys = Arrays.copyOf(this.keys, newLength);
            this.values = Arrays.copyOf(this.values, newLength);
        }
        
        private void insert(final T key, final double value) {
            this.keys[this.size] = key;
            this.values[this.size] = value;
            ++this.size;
        }
        
        public int size() {
            return this.size;
        }
        
        private int allocated() {
            return this.keys.length;
        }
        
        public void clear() {
            this.keys = this.emptyKeys;
            this.values = ArrayUtil.EMPTY_DOUBLE_ARRAY;
            this.size = 0;
        }
        
        @Nonnull
        public IWeightedMap<T> build() {
            if (this.size < this.allocated()) {
                this.resize(this.size);
            }
            if (this.keys.length == 0 || this.keys.length == 1) {
                return new SingletonWeightedMap<T>(this.keys);
            }
            double sum = 0.0;
            for (final double value : this.values) {
                sum += value;
            }
            return new WeightedMap<T>(this.keys, this.values, sum);
        }
    }
}
