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

package it.unimi.dsi.fastutil.objects;

import java.util.NoSuchElementException;
import java.util.function.Supplier;
import java.util.Spliterator;
import java.io.ObjectInputStream;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.util.function.Consumer;
import it.unimi.dsi.fastutil.BigArrays;
import java.util.Iterator;
import java.util.Collection;
import it.unimi.dsi.fastutil.HashCommon;
import java.util.stream.Collector;
import it.unimi.dsi.fastutil.Size64;
import it.unimi.dsi.fastutil.Hash;
import java.io.Serializable;

public class ObjectOpenHashBigSet<K> extends AbstractObjectSet<K> implements Serializable, Cloneable, Hash, Size64
{
    private static final long serialVersionUID = 0L;
    private static final boolean ASSERTS = false;
    protected transient K[][] key;
    protected transient long mask;
    protected transient int segmentMask;
    protected transient int baseMask;
    protected transient boolean containsNull;
    protected transient long n;
    protected transient long maxFill;
    protected final transient long minN;
    protected final float f;
    protected long size;
    private static final Collector<Object, ?, ObjectOpenHashBigSet<Object>> TO_SET_COLLECTOR;
    
    private void initMasks() {
        this.mask = this.n - 1L;
        this.segmentMask = this.key[0].length - 1;
        this.baseMask = this.key.length - 1;
    }
    
    public ObjectOpenHashBigSet(final long expected, final float f) {
        if (f <= 0.0f || f > 1.0f) {
            throw new IllegalArgumentException("Load factor must be greater than 0 and smaller than or equal to 1");
        }
        if (this.n < 0L) {
            throw new IllegalArgumentException("The expected number of elements must be nonnegative");
        }
        this.f = f;
        final long bigArraySize = HashCommon.bigArraySize(expected, f);
        this.n = bigArraySize;
        this.minN = bigArraySize;
        this.maxFill = HashCommon.maxFill(this.n, f);
        this.key = (K[][])ObjectBigArrays.newBigArray(this.n);
        this.initMasks();
    }
    
    public ObjectOpenHashBigSet(final long expected) {
        this(expected, 0.75f);
    }
    
    public ObjectOpenHashBigSet() {
        this(16L, 0.75f);
    }
    
    public ObjectOpenHashBigSet(final Collection<? extends K> c, final float f) {
        this(Size64.sizeOf(c), f);
        this.addAll(c);
    }
    
    public ObjectOpenHashBigSet(final Collection<? extends K> c) {
        this(c, 0.75f);
    }
    
    public ObjectOpenHashBigSet(final ObjectCollection<? extends K> c, final float f) {
        this(Size64.sizeOf(c), f);
        this.addAll(c);
    }
    
    public ObjectOpenHashBigSet(final ObjectCollection<? extends K> c) {
        this(c, 0.75f);
    }
    
    public ObjectOpenHashBigSet(final Iterator<? extends K> i, final float f) {
        this(16L, f);
        while (i.hasNext()) {
            this.add(i.next());
        }
    }
    
    public ObjectOpenHashBigSet(final Iterator<? extends K> i) {
        this(i, 0.75f);
    }
    
    public ObjectOpenHashBigSet(final K[] a, final int offset, final int length, final float f) {
        this((length < 0) ? 0L : length, f);
        ObjectArrays.ensureOffsetLength(a, offset, length);
        for (int i = 0; i < length; ++i) {
            this.add(a[offset + i]);
        }
    }
    
    public ObjectOpenHashBigSet(final K[] a, final int offset, final int length) {
        this(a, offset, length, 0.75f);
    }
    
    public ObjectOpenHashBigSet(final K[] a, final float f) {
        this(a, 0, a.length, f);
    }
    
    public ObjectOpenHashBigSet(final K[] a) {
        this(a, 0.75f);
    }
    
    private ObjectOpenHashBigSet<K> combine(final ObjectOpenHashBigSet<? extends K> toAddFrom) {
        this.addAll(toAddFrom);
        return this;
    }
    
    public static <K> Collector<K, ?, ObjectOpenHashBigSet<K>> toBigSet() {
        return (Collector<K, ?, ObjectOpenHashBigSet<K>>)ObjectOpenHashBigSet.TO_SET_COLLECTOR;
    }
    
    public static <K> Collector<K, ?, ObjectOpenHashBigSet<K>> toBigSetWithExpectedSize(final long expectedSize) {
        return (Collector<K, ?, ObjectOpenHashBigSet<K>>)Collector.of(() -> new ObjectOpenHashBigSet(expectedSize), ObjectOpenHashBigSet::add, ObjectOpenHashBigSet::combine, new Collector.Characteristics[0]);
    }
    
    private long realSize() {
        return this.containsNull ? (this.size - 1L) : this.size;
    }
    
    public void ensureCapacity(final long capacity) {
        final long needed = HashCommon.bigArraySize(capacity, this.f);
        if (needed > this.n) {
            this.rehash(needed);
        }
    }
    
    @Override
    public boolean addAll(final Collection<? extends K> c) {
        final long size = Size64.sizeOf(c);
        if (this.f <= 0.5) {
            this.ensureCapacity(size);
        }
        else {
            this.ensureCapacity(this.size64() + size);
        }
        return super.addAll(c);
    }
    
    @Override
    public boolean add(final K k) {
        if (k == null) {
            if (this.containsNull) {
                return false;
            }
            this.containsNull = true;
        }
        else {
            final K[][] key = this.key;
            final long h = HashCommon.mix((long)k.hashCode());
            int base;
            int displ;
            K curr;
            if ((curr = key[base = (int)((h & this.mask) >>> 27)][displ = (int)(h & (long)this.segmentMask)]) != null) {
                if (curr.equals(k)) {
                    return false;
                }
                while ((curr = key[base = (base + (((displ = (displ + 1 & this.segmentMask)) == 0) ? 1 : 0) & this.baseMask)][displ]) != null) {
                    if (curr.equals(k)) {
                        return false;
                    }
                }
            }
            key[base][displ] = k;
        }
        if (this.size++ >= this.maxFill) {
            this.rehash(2L * this.n);
        }
        return true;
    }
    
    public K addOrGet(final K k) {
        if (k == null) {
            if (this.containsNull) {
                return null;
            }
            this.containsNull = true;
        }
        else {
            final K[][] key = this.key;
            final long h = HashCommon.mix((long)k.hashCode());
            int base;
            int displ;
            K curr;
            if ((curr = key[base = (int)((h & this.mask) >>> 27)][displ = (int)(h & (long)this.segmentMask)]) != null) {
                if (curr.equals(k)) {
                    return curr;
                }
                while ((curr = key[base = (base + (((displ = (displ + 1 & this.segmentMask)) == 0) ? 1 : 0) & this.baseMask)][displ]) != null) {
                    if (curr.equals(k)) {
                        return curr;
                    }
                }
            }
            key[base][displ] = k;
        }
        if (this.size++ >= this.maxFill) {
            this.rehash(2L * this.n);
        }
        return k;
    }
    
    protected final void shiftKeys(long pos) {
        final K[][] key = this.key;
        long last = 0L;
    Label_0006:
        while (true) {
            pos = ((last = pos) + 1L & this.mask);
            while (BigArrays.get(key, pos) != null) {
                final long slot = HashCommon.mix((long)BigArrays.get(key, pos).hashCode()) & this.mask;
                Label_0106: {
                    if (last <= pos) {
                        if (last >= slot) {
                            break Label_0106;
                        }
                        if (slot > pos) {
                            break Label_0106;
                        }
                    }
                    else if (last >= slot && slot > pos) {
                        break Label_0106;
                    }
                    pos = (pos + 1L & this.mask);
                    continue;
                }
                BigArrays.set(key, last, (K)BigArrays.get((K[][])key, pos));
                continue Label_0006;
            }
            break;
        }
        BigArrays.set(key, last, (K)null);
    }
    
    private boolean removeEntry(final int base, final int displ) {
        --this.size;
        this.shiftKeys(base * 134217728L + displ);
        if (this.n > this.minN && this.size < this.maxFill / 4L && this.n > 16L) {
            this.rehash(this.n / 2L);
        }
        return true;
    }
    
    private boolean removeNullEntry() {
        this.containsNull = false;
        --this.size;
        if (this.n > this.minN && this.size < this.maxFill / 4L && this.n > 16L) {
            this.rehash(this.n / 2L);
        }
        return true;
    }
    
    @Override
    public boolean remove(final Object k) {
        if (k == null) {
            return this.containsNull && this.removeNullEntry();
        }
        final K[][] key = this.key;
        final long h = HashCommon.mix((long)k.hashCode());
        int base;
        int displ;
        K curr;
        if ((curr = key[base = (int)((h & this.mask) >>> 27)][displ = (int)(h & (long)this.segmentMask)]) == null) {
            return false;
        }
        if (curr.equals(k)) {
            return this.removeEntry(base, displ);
        }
        while ((curr = key[base = (base + (((displ = (displ + 1 & this.segmentMask)) == 0) ? 1 : 0) & this.baseMask)][displ]) != null) {
            if (curr.equals(k)) {
                return this.removeEntry(base, displ);
            }
        }
        return false;
    }
    
    @Override
    public boolean contains(final Object k) {
        if (k == null) {
            return this.containsNull;
        }
        final K[][] key = this.key;
        final long h = HashCommon.mix((long)k.hashCode());
        int base;
        int displ;
        K curr;
        if ((curr = key[base = (int)((h & this.mask) >>> 27)][displ = (int)(h & (long)this.segmentMask)]) == null) {
            return false;
        }
        if (curr.equals(k)) {
            return true;
        }
        while ((curr = key[base = (base + (((displ = (displ + 1 & this.segmentMask)) == 0) ? 1 : 0) & this.baseMask)][displ]) != null) {
            if (curr.equals(k)) {
                return true;
            }
        }
        return false;
    }
    
    public K get(final Object k) {
        if (k == null) {
            return null;
        }
        final K[][] key = this.key;
        final long h = HashCommon.mix((long)k.hashCode());
        int base;
        int displ;
        K curr;
        if ((curr = key[base = (int)((h & this.mask) >>> 27)][displ = (int)(h & (long)this.segmentMask)]) == null) {
            return null;
        }
        if (curr.equals(k)) {
            return curr;
        }
        while ((curr = key[base = (base + (((displ = (displ + 1 & this.segmentMask)) == 0) ? 1 : 0) & this.baseMask)][displ]) != null) {
            if (curr.equals(k)) {
                return curr;
            }
        }
        return null;
    }
    
    @Override
    public void clear() {
        if (this.size == 0L) {
            return;
        }
        this.size = 0L;
        this.containsNull = false;
        BigArrays.fill(this.key, (K)null);
    }
    
    @Override
    public ObjectIterator<K> iterator() {
        return new SetIterator();
    }
    
    @Override
    public ObjectSpliterator<K> spliterator() {
        return new SetSpliterator();
    }
    
    @Override
    public void forEach(final Consumer<? super K> action) {
        if (this.containsNull) {
            action.accept(null);
        }
        long pos = 0L;
        final long max = this.n;
        final K[][] key = this.key;
        while (pos < max) {
            final K gotten = BigArrays.get(key, pos++);
            if (gotten != null) {
                action.accept(gotten);
            }
        }
    }
    
    public boolean trim() {
        return this.trim(this.size);
    }
    
    public boolean trim(final long n) {
        final long l = HashCommon.bigArraySize(n, this.f);
        if (l >= this.n || this.size > HashCommon.maxFill(l, this.f)) {
            return true;
        }
        try {
            this.rehash(l);
        }
        catch (final OutOfMemoryError cantDoIt) {
            return false;
        }
        return true;
    }
    
    protected void rehash(final long newN) {
        final K[][] key = this.key;
        final K[][] newKey = (K[][])ObjectBigArrays.newBigArray(newN);
        final long mask = newN - 1L;
        final int newSegmentMask = newKey[0].length - 1;
        final int newBaseMask = newKey.length - 1;
        int base = 0;
        int displ = 0;
        long i = this.realSize();
        while (i-- != 0L) {
            while (key[base][displ] == null) {
                base += (((displ = (displ + 1 & this.segmentMask)) == 0) ? 1 : 0);
            }
            final K k = key[base][displ];
            final long h = HashCommon.mix((long)k.hashCode());
            int b;
            int d;
            if (newKey[b = (int)((h & mask) >>> 27)][d = (int)(h & (long)newSegmentMask)] != null) {
                while (newKey[b = (b + (((d = (d + 1 & newSegmentMask)) == 0) ? 1 : 0) & newBaseMask)][d] != null) {}
            }
            newKey[b][d] = k;
            base += (((displ = (displ + 1 & this.segmentMask)) == 0) ? 1 : 0);
        }
        this.n = newN;
        this.key = newKey;
        this.initMasks();
        this.maxFill = HashCommon.maxFill(this.n, this.f);
    }
    
    @Deprecated
    @Override
    public int size() {
        return (int)Math.min(2147483647L, this.size);
    }
    
    @Override
    public long size64() {
        return this.size;
    }
    
    @Override
    public boolean isEmpty() {
        return this.size == 0L;
    }
    
    public ObjectOpenHashBigSet<K> clone() {
        ObjectOpenHashBigSet<K> c;
        try {
            c = (ObjectOpenHashBigSet)super.clone();
        }
        catch (final CloneNotSupportedException cantHappen) {
            throw new InternalError();
        }
        c.key = BigArrays.copy(this.key);
        c.containsNull = this.containsNull;
        return c;
    }
    
    @Override
    public int hashCode() {
        final K[][] key = this.key;
        int h = 0;
        int base = 0;
        int displ = 0;
        long j = this.realSize();
        while (j-- != 0L) {
            while (key[base][displ] == null) {
                base += (((displ = (displ + 1 & this.segmentMask)) == 0) ? 1 : 0);
            }
            if (this != key[base][displ]) {
                h += key[base][displ].hashCode();
            }
            base += (((displ = (displ + 1 & this.segmentMask)) == 0) ? 1 : 0);
        }
        return h;
    }
    
    private void writeObject(final ObjectOutputStream s) throws IOException {
        final ObjectIterator<K> i = this.iterator();
        s.defaultWriteObject();
        long j = this.size;
        while (j-- != 0L) {
            s.writeObject(i.next());
        }
    }
    
    private void readObject(final ObjectInputStream s) throws IOException, ClassNotFoundException {
        s.defaultReadObject();
        this.n = HashCommon.bigArraySize(this.size, this.f);
        this.maxFill = HashCommon.maxFill(this.n, this.f);
        final Object[][] bigArray = ObjectBigArrays.newBigArray(this.n);
        this.key = (K[][])bigArray;
        final K[][] key = (K[][])bigArray;
        this.initMasks();
        long i = this.size;
        while (i-- != 0L) {
            final K k = (K)s.readObject();
            if (k == null) {
                this.containsNull = true;
            }
            else {
                final long h = HashCommon.mix((long)k.hashCode());
                int base;
                int displ;
                if (key[base = (int)((h & this.mask) >>> 27)][displ = (int)(h & (long)this.segmentMask)] != null) {
                    while (key[base = (base + (((displ = (displ + 1 & this.segmentMask)) == 0) ? 1 : 0) & this.baseMask)][displ] != null) {}
                }
                key[base][displ] = k;
            }
        }
    }
    
    private void checkTable() {
    }
    
    static {
        TO_SET_COLLECTOR = Collector.of(ObjectOpenHashBigSet::new, ObjectOpenHashBigSet::add, ObjectOpenHashBigSet::combine, new Collector.Characteristics[0]);
    }
    
    private class SetIterator implements ObjectIterator<K>
    {
        int base;
        int displ;
        long last;
        long c;
        boolean mustReturnNull;
        ObjectArrayList<K> wrapped;
        
        private SetIterator() {
            this.base = ObjectOpenHashBigSet.this.key.length;
            this.last = -1L;
            this.c = ObjectOpenHashBigSet.this.size;
            this.mustReturnNull = ObjectOpenHashBigSet.this.containsNull;
        }
        
        @Override
        public boolean hasNext() {
            return this.c != 0L;
        }
        
        @Override
        public K next() {
            if (!this.hasNext()) {
                throw new NoSuchElementException();
            }
            --this.c;
            if (this.mustReturnNull) {
                this.mustReturnNull = false;
                this.last = ObjectOpenHashBigSet.this.n;
                return null;
            }
            final K[][] key = ObjectOpenHashBigSet.this.key;
            while (this.displ != 0 || this.base > 0) {
                if (this.displ-- == 0) {
                    final K[][] array = key;
                    final int base = this.base - 1;
                    this.base = base;
                    this.displ = array[base].length - 1;
                }
                final K k = key[this.base][this.displ];
                if (k != null) {
                    this.last = this.base * 134217728L + this.displ;
                    return k;
                }
            }
            this.last = Long.MIN_VALUE;
            final ObjectArrayList<K> wrapped = this.wrapped;
            final int base2 = this.base - 1;
            this.base = base2;
            return wrapped.get(-base2 - 1);
        }
        
        private final void shiftKeys(long pos) {
            final K[][] key = ObjectOpenHashBigSet.this.key;
            long last = 0L;
        Label_0009:
            while (true) {
                pos = ((last = pos) + 1L & ObjectOpenHashBigSet.this.mask);
                K curr;
                while ((curr = BigArrays.get(key, pos)) != null) {
                    final long slot = HashCommon.mix((long)curr.hashCode()) & ObjectOpenHashBigSet.this.mask;
                    Label_0117: {
                        if (last <= pos) {
                            if (last >= slot) {
                                break Label_0117;
                            }
                            if (slot > pos) {
                                break Label_0117;
                            }
                        }
                        else if (last >= slot && slot > pos) {
                            break Label_0117;
                        }
                        pos = (pos + 1L & ObjectOpenHashBigSet.this.mask);
                        continue;
                    }
                    if (pos < last) {
                        if (this.wrapped == null) {
                            this.wrapped = new ObjectArrayList<K>();
                        }
                        this.wrapped.add(BigArrays.get(key, pos));
                    }
                    BigArrays.set(key, last, curr);
                    continue Label_0009;
                }
                break;
            }
            BigArrays.set(key, last, (K)null);
        }
        
        @Override
        public void remove() {
            if (this.last == -1L) {
                throw new IllegalStateException();
            }
            if (this.last == ObjectOpenHashBigSet.this.n) {
                ObjectOpenHashBigSet.this.containsNull = false;
            }
            else {
                if (this.base < 0) {
                    ObjectOpenHashBigSet.this.remove(this.wrapped.set(-this.base - 1, null));
                    this.last = -1L;
                    return;
                }
                this.shiftKeys(this.last);
            }
            final ObjectOpenHashBigSet this$0 = ObjectOpenHashBigSet.this;
            --this$0.size;
            this.last = -1L;
        }
    }
    
    private class SetSpliterator implements ObjectSpliterator<K>
    {
        private static final int POST_SPLIT_CHARACTERISTICS = 1;
        long pos;
        long max;
        long c;
        boolean mustReturnNull;
        boolean hasSplit;
        
        SetSpliterator() {
            this.pos = 0L;
            this.max = ObjectOpenHashBigSet.this.n;
            this.c = 0L;
            this.mustReturnNull = ObjectOpenHashBigSet.this.containsNull;
            this.hasSplit = false;
        }
        
        SetSpliterator(final long pos, final long max, final boolean mustReturnNull, final boolean hasSplit) {
            this.pos = 0L;
            this.max = ObjectOpenHashBigSet.this.n;
            this.c = 0L;
            this.mustReturnNull = ObjectOpenHashBigSet.this.containsNull;
            this.hasSplit = false;
            this.pos = pos;
            this.max = max;
            this.mustReturnNull = mustReturnNull;
            this.hasSplit = hasSplit;
        }
        
        @Override
        public boolean tryAdvance(final Consumer<? super K> action) {
            if (this.mustReturnNull) {
                this.mustReturnNull = false;
                ++this.c;
                action.accept(null);
                return true;
            }
            final K[][] key = ObjectOpenHashBigSet.this.key;
            while (this.pos < this.max) {
                final K gotten = BigArrays.get(key, this.pos);
                if (gotten != null) {
                    ++this.c;
                    ++this.pos;
                    action.accept(gotten);
                    return true;
                }
                ++this.pos;
            }
            return false;
        }
        
        @Override
        public void forEachRemaining(final Consumer<? super K> action) {
            if (this.mustReturnNull) {
                this.mustReturnNull = false;
                action.accept(null);
                ++this.c;
            }
            final K[][] key = ObjectOpenHashBigSet.this.key;
            while (this.pos < this.max) {
                final K gotten = BigArrays.get(key, this.pos);
                if (gotten != null) {
                    action.accept(gotten);
                    ++this.c;
                }
                ++this.pos;
            }
        }
        
        @Override
        public int characteristics() {
            return this.hasSplit ? 1 : 65;
        }
        
        @Override
        public long estimateSize() {
            if (!this.hasSplit) {
                return ObjectOpenHashBigSet.this.size - this.c;
            }
            return Math.min(ObjectOpenHashBigSet.this.size - this.c, (long)(ObjectOpenHashBigSet.this.realSize() / (double)ObjectOpenHashBigSet.this.n * (this.max - this.pos)) + (long)(this.mustReturnNull ? 1 : 0));
        }
        
        @Override
        public SetSpliterator trySplit() {
            if (this.pos >= this.max - 1L) {
                return null;
            }
            final long retLen = this.max - this.pos >> 1;
            if (retLen <= 1L) {
                return null;
            }
            long myNewPos = this.pos + retLen;
            myNewPos = BigArrays.nearestSegmentStart(myNewPos, this.pos + 1L, this.max - 1L);
            final long retPos = this.pos;
            final long retMax = myNewPos;
            final SetSpliterator split = new SetSpliterator(retPos, retMax, this.mustReturnNull, true);
            this.pos = myNewPos;
            this.mustReturnNull = false;
            this.hasSplit = true;
            return split;
        }
        
        @Override
        public long skip(long n) {
            if (n < 0L) {
                throw new IllegalArgumentException("Argument must be nonnegative: " + n);
            }
            if (n == 0L) {
                return 0L;
            }
            long skipped = 0L;
            if (this.mustReturnNull) {
                this.mustReturnNull = false;
                ++skipped;
                --n;
            }
            final K[][] key = ObjectOpenHashBigSet.this.key;
            while (this.pos < this.max && n > 0L) {
                if (BigArrays.get(key, this.pos++) != null) {
                    ++skipped;
                    --n;
                }
            }
            return skipped;
        }
    }
}
