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

package com.hypixel.hytale.common.collection;

import java.util.function.IntFunction;
import it.unimi.dsi.fastutil.objects.ObjectArrays;
import java.util.function.Predicate;
import it.unimi.dsi.fastutil.ints.IntArrayList;
import it.unimi.dsi.fastutil.ints.IntArrays;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import java.util.Comparator;

public class BucketList<E>
{
    public static final int INITIAL_BUCKET_ITEM_ARRAY_SIZE = 4;
    public static final Comparator<BucketItem<?>> CLOSER_TO_SELF;
    protected static final byte[] EMPTY_INDICES;
    protected BucketItemPool<E> bucketItemPool;
    @Nullable
    protected Bucket<E>[] buckets;
    protected byte[] bucketIndices;
    protected int bucketCount;
    protected int squaredMaxDistance;
    
    public BucketList(final BucketItemPool<E> bucketItemPool) {
        this.bucketIndices = BucketList.EMPTY_INDICES;
        this.bucketItemPool = bucketItemPool;
    }
    
    public void setBucketItemPool(@Nonnull final BucketItemPool<E> bucketItemPool) {
        this.clear();
        this.bucketItemPool = bucketItemPool;
    }
    
    public void clear() {
        if (this.buckets == null || this.bucketItemPool == null) {
            return;
        }
        for (final Bucket<E> bucket : this.buckets) {
            bucket.clear(this.bucketItemPool);
        }
    }
    
    public void reset() {
        this.clear();
        this.buckets = null;
        this.bucketCount = 0;
        this.bucketIndices = BucketList.EMPTY_INDICES;
    }
    
    public void configure(@Nonnull final int[] bucketRanges) {
        this.configure(bucketRanges, 4);
    }
    
    public void configure(@Nonnull final int[] bucketRanges, final int initialBucketItemArraySize) {
        if (bucketRanges == null) {
            throw new IllegalArgumentException("bucketRanges can't be null");
        }
        if (bucketRanges.length <= 0) {
            throw new IllegalArgumentException("bucketRanges can't beempty");
        }
        final int[] copyRanges = bucketRanges.clone();
        IntArrays.quickSort(copyRanges);
        if (copyRanges[0] <= 0) {
            throw new IllegalArgumentException("bucketRanges entries must be >0");
        }
        this.configureWithPreSortedArray(copyRanges, initialBucketItemArraySize);
    }
    
    public void configureWithPreSortedArray(@Nonnull final int[] bucketRanges) {
        this.configureWithPreSortedArray(bucketRanges, 4);
    }
    
    public void configureWithPreSortedArray(@Nonnull final int[] bucketRanges, final int initialBucketItemArraySize) {
        this.clear();
        this.bucketCount = bucketRanges.length;
        this.squaredMaxDistance = bucketRanges[this.bucketCount - 1];
        this.squaredMaxDistance *= this.squaredMaxDistance;
        this.buckets = new Bucket[this.bucketCount];
        this.bucketIndices = new byte[this.squaredMaxDistance + 1];
        int inner = 0;
        for (int i = 0; i < this.bucketCount; ++i) {
            final int outer = bucketRanges[i] * bucketRanges[i];
            this.buckets[i] = new Bucket<E>(initialBucketItemArraySize);
            for (int j = inner; j < outer; ++j) {
                this.bucketIndices[j] = (byte)i;
            }
            inner = outer;
        }
        this.bucketIndices[this.bucketIndices.length - 1] = -1;
    }
    
    public void configureWithPresortedArray(@Nonnull final IntArrayList bucketRanges, final int initialBucketItemArraySize) {
        this.configureWithPreSortedArray(bucketRanges.toIntArray(), initialBucketItemArraySize);
    }
    
    public boolean add(@Nonnull final E item, final double squaredDistance) {
        final int bucketIndex = this.getFirstBucketIndex((int)squaredDistance);
        if (bucketIndex < 0) {
            return false;
        }
        final BucketItem<E> bucketItem = this.bucketItemPool.allocate(item, squaredDistance);
        this.buckets[bucketIndex].add(bucketItem);
        return true;
    }
    
    public int getBucketCount() {
        return (this.buckets != null) ? this.buckets.length : 0;
    }
    
    @Nullable
    public Bucket<E> getBucket(final int index) {
        return (index < 0 || index >= this.getBucketCount()) ? null : this.buckets[index];
    }
    
    public int getFirstBucketIndex(int distanceSquared) {
        if (distanceSquared == 0) {
            return this.bucketIndices[0];
        }
        distanceSquared = Math.min(distanceSquared, this.squaredMaxDistance);
        if (distanceSquared <= 0) {
            return -1;
        }
        return this.bucketIndices[distanceSquared];
    }
    
    public int getLastBucketIndex(final int distanceSquared) {
        final int d = Math.min(distanceSquared, this.squaredMaxDistance) - 1;
        if (d < 0) {
            return -1;
        }
        return this.bucketIndices[d];
    }
    
    @Nullable
    public E getClosestInRange(final int minRange, final int maxRange, @Nonnull final Predicate<E> filter, @Nonnull final SortBufferProvider sortBufferProvider) {
        final int minRangeSquared = minRange * minRange;
        final int startBucket = this.getFirstBucketIndex(minRangeSquared);
        if (startBucket < 0) {
            return null;
        }
        final int maxRangeSquared = maxRange * maxRange;
        for (int endBucket = this.getLastBucketIndex(maxRangeSquared), i = startBucket; i <= endBucket; ++i) {
            final Bucket<E> bucket = this.buckets[i];
            if (!bucket.isEmpty) {
                if (bucket.isUnsorted) {
                    bucket.sort(sortBufferProvider);
                }
                final BucketItem<E>[] entityHolders = bucket.bucketItems;
                for (int i2 = 0, entityHoldersSize = bucket.size; i2 < entityHoldersSize; ++i2) {
                    final BucketItem<E> holder = entityHolders[i2];
                    final double squaredDistance = holder.squaredDistance;
                    if (squaredDistance >= minRangeSquared) {
                        if (squaredDistance >= maxRangeSquared) {
                            return null;
                        }
                        final E item = holder.item;
                        if (item != null && filter.test(item)) {
                            return item;
                        }
                    }
                }
            }
        }
        return null;
    }
    
    public static void addBucketDistance(@Nonnull final IntArrayList bucketRanges, final int maxBucketCount, final int distance) {
        addBucketDistance(bucketRanges, maxBucketCount, distance, -1);
    }
    
    public static void addBucketDistance(@Nonnull final IntArrayList bucketRanges, final int maxBucketCount, final int distance, final int keepDistance) {
        if (distance < 1) {
            return;
        }
        int i;
        int length;
        for (i = 0, length = bucketRanges.size(); i < length; ++i) {
            final int v = bucketRanges.getInt(i);
            if (v == distance) {
                return;
            }
            if (v > distance) {
                break;
            }
        }
        bucketRanges.add(i, distance);
        if (++length <= maxBucketCount) {
            return;
        }
        int middle = bucketRanges.getInt(0);
        int innerArea = area(0, middle);
        int area = Integer.MAX_VALUE;
        int pos = -1;
        for (i = 1; i < length; ++i) {
            final int outer = bucketRanges.getInt(i);
            final int outerArea = area(middle, outer);
            final int sumAreas = innerArea + outerArea;
            if (sumAreas <= area && middle != keepDistance) {
                pos = i - 1;
                area = sumAreas;
            }
            middle = outer;
            innerArea = outerArea;
        }
        bucketRanges.removeInt(pos);
    }
    
    protected static int area(final int inner, final int outer) {
        return outer * outer - inner * inner;
    }
    
    static {
        CLOSER_TO_SELF = Comparator.comparingDouble(bucketItem -> bucketItem.squaredDistance);
        EMPTY_INDICES = new byte[] { -1 };
    }
    
    public static class Bucket<E>
    {
        protected BucketItem<E>[] bucketItems;
        protected int size;
        protected boolean isUnsorted;
        protected boolean isEmpty;
        
        public Bucket(final int initialBucketArraySize) {
            this.bucketItems = new BucketItem[initialBucketArraySize];
            this.size = 0;
            this.isUnsorted = false;
            this.isEmpty = true;
        }
        
        public BucketItem<E>[] getItems() {
            return this.bucketItems;
        }
        
        public int size() {
            return this.size;
        }
        
        public boolean isUnsorted() {
            return this.isUnsorted;
        }
        
        public boolean isEmpty() {
            return this.isEmpty;
        }
        
        public void clear(@Nonnull final BucketItemPool<E> pool) {
            if (this.isEmpty) {
                return;
            }
            pool.deallocate(this.bucketItems, this.size);
            for (int i = 0; i < this.size; ++i) {
                this.bucketItems[i] = null;
            }
            this.size = 0;
            this.isUnsorted = false;
            this.isEmpty = true;
        }
        
        public void add(@Nonnull final BucketItem<E> item) {
            this.isEmpty = false;
            if (this.size == this.bucketItems.length) {
                this.bucketItems = ObjectArrays.grow(this.bucketItems, this.size + 1);
            }
            this.bucketItems[this.size++] = item;
            this.isUnsorted = true;
        }
        
        public void sort(@Nonnull final SortBufferProvider sortBufferProvider) {
            this.isUnsorted = false;
            if (this.size <= 1) {
                return;
            }
            final BucketItem[] sortBuffer = sortBufferProvider.apply(this.size);
            System.arraycopy(this.bucketItems, 0, sortBuffer, 0, this.size);
            ObjectArrays.mergeSort(this.bucketItems, 0, this.size, (Comparator<BucketItem<E>>)BucketList.CLOSER_TO_SELF, sortBuffer);
        }
    }
    
    public static class SortBufferProvider implements IntFunction<BucketItem[]>
    {
        protected BucketItem[] buffer;
        
        public SortBufferProvider() {
            this.buffer = new BucketItem[4];
        }
        
        @Override
        public BucketItem[] apply(final int size) {
            if (size <= this.buffer.length) {
                return this.buffer;
            }
            return this.buffer = ObjectArrays.grow(this.buffer, size);
        }
    }
}
