package general;

import java.util.Iterator;

/**
 * For fun made a non thread safe and non out of bounds checked Vector class
 *
 * Adding elements turned out to be 20-30% faster than java.util.Vector
 * Somewhere above 2 M elements the ArrayList class becomes faster at adding
 * elements, under the assumption that backing array size is changed very often
 *
 * Getting elements ran about 400% faster than java.util.Vector,
 * Getting the size of the vector was also 400% faster, and identical
 * to reading an exposed numElements variable directly.
 *
 * Exposing the underlying array directly yielded no measurable performance
 * improvements. ( Means we have good compilers :) )
 *
 * @author Johan
 */
public final class Vector<E> implements Iterable<E> {

    private int numElements = 0;
    private Object[] data = new Object[10];

    private final void ensureCapacity(int n) {
        if (n > data.length) {
            Object[] oldData = data;
            System.arraycopy(oldData, 0, data = new Object[3 * n / 2], 0, numElements);
        }
    }

    public final E firstElement() {
        return (E) data[0];
    }

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

    public class Iterator<E> implements java.util.Iterator<E> {

        private int curElement = -1;
        private boolean deletedCurrent = false;

        @Override
        public void remove() {
            if (!deletedCurrent) {
                removeElementAt(curElement);
                deletedCurrent = true;
            }
        }

        @Override
        public E next() {
            if (!deletedCurrent) {
                return (E) data[++curElement];
            } else {
                deletedCurrent = false;
                return (E) data[curElement];
            }
        }

        @Override
        public final boolean hasNext() {
            if (!deletedCurrent) {
                return curElement + 1 < size();
            } else {
                return curElement < size();
            }
        }
    }

    public final E lastElement() {
        return (E) data[numElements - 1];
    }

    public final boolean isEmpty() {
        return numElements == 0;
    }

    public final void add(E newElement) {
        ensureCapacity(numElements + 1);
        data[numElements++] = newElement;
    }

    public final E remove(int index) {
        E toReturn = (E) data[index];
        removeElementAt(index);
        return toReturn;
    }

    public final void removeElementAt(int index) {
        for (int i = index + 1; i < numElements; i++)
            data[i - 1] = data[i];
        data[--numElements] = null;
    }

    public final E remove(E toRemove) {
        for (int i = 0; i < numElements; i++) {
            if (data[i].equals(toRemove)) {
                return remove(i);
            }
        }
        return null;
    }

    public final void removeElement(E toRemove) {
        for (int i = 0; i < numElements; i++) {
            if (data[i].equals(toRemove)) {
                removeElementAt(i);
            }
        }
    }

    /**
     * Returns shallow copy of the array
     *
     * @return
     */
    public final <E> E[] toArray(E[] out) {
        System.arraycopy(data, 0, out, 0, numElements);
        return out;
    }

    public final Object[] toArray() {
        int N = numElements;
        Object[] out = new Object[N];
        System.arraycopy(data, 0, out, 0, N);
        return out;
    }

    /**
     * Returns reference to the indexed value
     *
     * @param index
     * @return
     */
    public final E get(int index) {
        return (E) data[index];
    }

    /**
     * Backwards compatibility function
     *
     * @return
     */
    public final int size() {
        return numElements;
    }

    public final void removeAllElements() {
        for (int i = 0; i < numElements; i++)
            data[i] = null;
        numElements = 0;
    }

    public final boolean contains(E o) {
        for (int i = 0; i < numElements; i++) {
            if (data[i].equals(o)) {
                return true;
            }
        }
        return false;
    }

    public final void addAll(Vector<E> v) {
        ensureCapacity(numElements + v.numElements);
        System.arraycopy(v.data, 0, data, numElements, v.numElements);
        numElements += v.numElements;
    }

    public final void addAll(E[] v) {
        ensureCapacity(numElements + v.length);
        System.arraycopy(v, 0, data, numElements, v.length);
        numElements += v.length;
    }

    public final void copyInto(E[] v) {
        System.arraycopy(data, 0, v, 0, numElements);
    }

    public final void copyInto(E[] v, int i) {
        System.arraycopy(data, 0, v, i, numElements);
    }

    public final void set(int index, E e) {
        data[index] = e;
    }
}
