package allen.town.podcast.core.util;

import java.util.Arrays;

/**
 * Fast and memory efficient long list
 */
public final class LongList {

    private long[] values;
    private int size;

    /**
     * Constructs an empty instance with a default initial capacity.
     */
    public LongList() {
        this(4);
    }

    /**
     * Constructs an empty instance.
     *
     * @param initialCapacity {@code >= 0;} initial capacity of the list
     */
    public LongList(int initialCapacity) {
        if(initialCapacity < 0) {
            throw new IllegalArgumentException("initial capacity must be 0 or higher");
        }
        values = new long[initialCapacity];
        size = 0;
    }

    public static LongList of(long... values) {
        if(values == null || values.length == 0) {
            return new LongList(0);
        }
        LongList result = new LongList(values.length);
        for(long value : values) {
            result.add(value);
        }
        return result;
    }

    @Override
    public int hashCode() {
        int hashCode = 1;
        for (int i = 0; i < size; i++) {
            long value = values[i];
            hashCode = 31 * hashCode + (int)(value ^ (value >>> 32));
        }
        return hashCode;
    }

    @Override
    public boolean equals(Object other) {
        if (other == this) {
            return true;
        }
        if (! (other instanceof LongList)) {
            return false;
        }
        LongList otherList = (LongList) other;
        if (size != otherList.size) {
            return false;
        }
        for (int i = 0; i < size; i++) {
            if (values[i] != otherList.values[i]) {
                return false;
            }
        }
        return true;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder(size * 5 + 10);
        sb.append("LongList{");
        for (int i = 0; i < size; i++) {
            if (i != 0) {
                sb.append(", ");
            }
            sb.append(values[i]);
        }
        sb.append("}");
        return sb.toString();
    }

    /**
     * Gets the number of elements in this list.
     */
    public int size() {
        return size;
    }

    /**
     * Gets the indicated value.
     *
     * @param n {@code >= 0, < size();} which element
     * @return the indicated element's value
     */
    public long get(int n) {
        if (n >= size) {
            throw new IndexOutOfBoundsException("n >= size()");
        } else if(n < 0) {
            throw new IndexOutOfBoundsException("n < 0");
        }
        return values[n];
    }

    /**
     * Sets the value at the given index.
     *
     * @param index the index at which to put the specified object.
     * @param value the object to add.
     * @return the previous element at the index.
     */
    public long set(int index, long value) {
        if (index >= size) {
            throw new IndexOutOfBoundsException("n >= size()");
        } else if(index < 0) {
            throw new IndexOutOfBoundsException("n < 0");
        }
        long result = values[index];
        values[index] = value;
        return result;
    }

    /**
     * Adds an element to the end of the list. This will increase the
     * list's capacity if necessary.
     *
     * @param value the value to add
     */
    public void add(long value) {
        growIfNeeded();
        values[size++] = value;
    }

    /**
     * Inserts element into specified index, moving elements at and above
     * that index up one. May not be used to insert at an index beyond the
     * current size (that is, insertion as a last element is legal but
     * no further).
     *
     * @param n {@code >= 0, <=size();} index of where to insert
     * @param value value to insert
     */
    public void insert(int n, int value) {
        if (n > size) {
            throw new IndexOutOfBoundsException("n > size()");
        } else if(n < 0) {
            throw new IndexOutOfBoundsException("n < 0");
        }

        growIfNeeded();

        System.arraycopy(values, n, values, n+1, size - n);
        values[n] = value;
        size++;
    }

    /**
     * Removes value from this list.
     *
     * @param value  value to remove
     * return {@code true} if the value was removed, {@code false} otherwise
     */
    public boolean remove(long value) {
        for (int i = 0; i < size; i++) {
            if (values[i] == value) {
                size--;
                System.arraycopy(values, i+1, values, i, size-i);
                return true;
            }
        }
        return false;
    }

    /**
     * Removes values from this list.
     *
     * @param values  values to remove
     */
    public void removeAll(long[] values) {
        for(long value : values) {
            remove(value);
        }
    }

    /**
     * Removes values from this list.
     *
     * @param list List with values to remove
     */
    public void removeAll(LongList list) {
        for(long value : list.values) {
            remove(value);
        }
    }

    /**
     * Removes an element at a given index, shifting elements at greater
     * indicies down one.
     *
     * @param index index of element to remove
     */
    public void removeIndex(int index) {
        if (index >= size) {
            throw new IndexOutOfBoundsException("n >= size()");
        } else if(index < 0) {
            throw new IndexOutOfBoundsException("n < 0");
        }
        size--;
        System.arraycopy(values, index + 1, values, index, size - index);
    }

    /**
     * Increases size of array if needed
     */
    private void growIfNeeded() {
        if (size == values.length) {
            // Resize.
            long[] newArray = new long[size * 3 / 2 + 10];
            System.arraycopy(values, 0, newArray, 0, size);
            values = newArray;
        }
    }

    /**
     * Returns the index of the given value, or -1 if the value does not
     * appear in the list.
     *
     * @param value value to find
     * @return index of value or -1
     */
    public int indexOf(long value) {
        for (int i = 0; i < size; i++) {
            if (values[i] == value) {
                return i;
            }
        }
        return -1;
    }

    /**
     * Removes all values from this list.
     */
    public void clear() {
        values = new long[4];
        size = 0;
    }


    /**
     * Returns true if the given value is contained in the list
     *
     * @param value value to look for
     * @return {@code true} if this list contains {@code value}, {@code false} otherwise
     */
    public boolean contains(long value) {
        return indexOf(value) >= 0;
    }

    /**
     * Returns an array with a copy of this list's values
     *
     * @return array with a copy of this list's values
     */
    public long[] toArray() {
        return Arrays.copyOf(values, size);

    }
}
