package org.jctools.sets;

import java.util.AbstractSet;
import java.util.Iterator;
import java.util.NoSuchElementException;

import org.jctools.util.Pow2;

public class IdentityOpenHashSet<E> extends AbstractSet<E> {
    /* current element count */
    private int size;
    /* buffer.length is a power of 2 */
    private E[] buffer;
    private int resizeThreshold;

    @SuppressWarnings("unchecked")
    public IdentityOpenHashSet(int capacity) {
        int actualCapacity = Pow2.roundToPowerOfTwo(capacity);
        // pad data on either end with some empty slots?
        buffer = (E[]) new Object[actualCapacity];
        resizeThreshold = (int) (0.75 * buffer.length);
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public boolean add(E newVal) {
        final E[] buffer = this.buffer;
        final int mask = buffer.length - 1;

        final int hash = System.identityHashCode(newVal);
        final int offset = hash & mask;
        final E currVal = buffer[offset];

        boolean result;
        if (currVal == null) {
            size++;
            buffer[offset] = newVal;
            result = true;
        } else {
            result = currVal != newVal && addSlowPath(buffer, mask, newVal, hash);
        }

        if (result && size > resizeThreshold) {
            resize();
        }
        return result;
    }

    private void addForResize(final E[] buffer, final int mask, E newVal) {
        final int hash = System.identityHashCode(newVal);
        final int limit = hash + mask;
        for (int i = hash; i <= limit; i++) {
            final int offset = i & mask;
            final E currVal = buffer[offset];
            if (currVal == null) {
                buffer[offset] = newVal;
                return;
            }
        }
    }

    private boolean addSlowPath(E[] buffer, int mask, E newVal, int hash) {
        final int limit = hash + mask;
        for (int i = hash + 1; i <= limit; i++) {
            final int offset = i & mask;
            final E currVal = buffer[offset];
            if (currVal == null) {
                size++;
                buffer[offset] = newVal;
                return true;
            }
            else if (currVal == newVal) {
                return false;
            }
        }
        return false;
    }

    @SuppressWarnings("unchecked")
    private void resize() {
        final E[] oldBuffer = buffer;
        final E[] newBuffer = (E[]) new Object[oldBuffer.length * 2];
        final int mask = newBuffer.length - 1;
        int countdown = size;
        for (int i = 0; i < oldBuffer.length && countdown > 0; i++) {
            if (oldBuffer[i] != null) {
                addForResize(newBuffer, mask, oldBuffer[i]);
                countdown--;
            }
        }

        buffer = newBuffer;
        resizeThreshold = (int) (0.75 * buffer.length);
    }

    @Override
    public boolean remove(Object val) {
        final E[] buffer = this.buffer;
        final int mask = buffer.length - 1;
        final int hash = System.identityHashCode(val);
        final int offset = hash & mask;
        final E e = buffer[offset];
        if (e == null) {
            return false;
        }
        else if (e == val) {
            size--;
            if (buffer[(hash + 1) & mask] == null) {
                buffer[offset] = null;
            }
            else {
                compactAndRemove(buffer, mask, hash);
            }
            return true;
        }
        return removeSlowPath(val, buffer, mask, hash);
    }

    private boolean removeSlowPath(Object val, final E[] buffer, final int mask, final int hash) {
        final int limit = hash + mask;
        for (int searchIndex = hash + 1; searchIndex <= limit; searchIndex++) {
            final int offset = searchIndex & mask;
            final E e = buffer[offset];
            if (e == null) {
                return false;
            }
            else if (e == val) {
                size--;
                if (buffer[(searchIndex + 1) & mask] == null) {
                    buffer[offset] = null;
                }
                else {
                    compactAndRemove(buffer, mask, searchIndex);
                }
                return true;
            }
        }
        return false;
    }

    /*
     * implemented as per wiki suggested algo with minor adjustments.
     */
    private void compactAndRemove(final E[] buffer, final int mask, int removeHashIndex) {
        // remove(9a): [9a,9b,10a,9c,10b,11a,null] -> [9b,9c,10a,10b,null,11a,null]
        removeHashIndex = removeHashIndex & mask;
        int j = removeHashIndex;
        while(true) {
            int k;
            // skip elements which belong where they are
            do {
                // j := (j+1) modulo num_slots
                j = (j + 1) & mask;
                // if slot[j] is unoccupied exit
                if (buffer[j] == null) {
                    // delete last duplicate slot
                    buffer[removeHashIndex] = null;
                    return;
                }

                // k := hash(slot[j].key) modulo num_slots
                k = System.identityHashCode(buffer[j]) & mask;
                // determine if k lies cyclically in [i,j]
                // |    i.k.j |
                // |....j i.k.| or  |.k..j i...|
            }
            while ( (removeHashIndex  <= j) ?
                    ((removeHashIndex < k) && (k <= j)) :
                    ((removeHashIndex < k) || (k <= j)) );
            // slot[removeHashIndex] := slot[j]
            buffer[removeHashIndex] = buffer[j];
            // removeHashIndex := j
            removeHashIndex = j;
        }
    }

    @Override
    public boolean contains(Object needle) {
        // contains takes a snapshot of the buffer.
        final E[] buffer = this.buffer;
        final int mask = buffer.length - 1;
        final int hash = System.identityHashCode(needle);
        final E e = buffer[hash & mask];
        if (e == null) {
            return false;
        }
        else if (e == needle) {
            return true;
        }
        return containsSlowPath(buffer, mask, hash, needle);
    }

    private boolean containsSlowPath(final E[] buffer, final int mask, final int hash, Object needle) {
        for (int i = hash + 1; i <= hash + mask; i++) {
            final E e = buffer[i & mask];
            if (e == null) {
                return false;
            }
            else if (e == needle) {
                return true;
            }
        }
        return false;
    }

    @Override
    public Iterator<E> iterator() {
        return new Iter<E>(this);
    }

    private static class Iter<E> implements Iterator<E> {
        private final E[] buffer;
        private final IdentityOpenHashSet<E> set;
        private int nextValIndex;
        private int lastValIndex;
        private E nextVal = null;
        private E lastVal = null;

        public Iter(IdentityOpenHashSet<E> set) {
            this.set = set;
            this.buffer = set.buffer;
            findNextVal();
        }

        @Override
        public boolean hasNext() {
            return nextVal != null;
        }

        @Override
        public E next() {
            if (nextVal == null)
                throw new NoSuchElementException();
            E e = nextVal;
            lastValIndex = nextValIndex;
            findNextVal();
            lastVal = e;
            return e;
        }

        private void findNextVal() {
            E[] array = buffer;
            int i = nextValIndex;
            E e = null;
            for (; i < array.length; i++) {
                e = array[i];
                if (e != null) {
                    nextVal = e;
                    nextValIndex = i + 1;
                    return;
                }
            }
            nextVal = null;
        }

        @Override
        public void remove() {
            E e;
            if ((e = lastVal) != null) {
                lastVal = null;
                set.remove(e);
                nextValIndex = lastValIndex - 1;
                findNextVal();
            }
        }
    }
}
