/*
 * Copyright (C) 2007 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.dx.util;

import java.util.Arrays;

/**
 * Simple list of {@code int}s.
 */
public final class IntList extends MutabilityControl {
    /** {@code non-null;} immutable, no-element instance */
    public static final IntList EMPTY = new IntList(0);

    /** {@code non-null;} array of elements */
    private int[] values;

    /** {@code >= 0;} current size of the list */
    private int size;

    /** whether the values are currently sorted */
    private boolean sorted;

    static {
        EMPTY.setImmutable();
    }

    /**
     * Constructs a new immutable instance with the given element.
     *
     * @param value the sole value in the list
     */
    public static IntList makeImmutable(int value) {
        IntList result = new IntList(1);

        result.add(value);
        result.setImmutable();

        return result;
    }

    /**
     * Constructs a new immutable instance with the given elements.
     *
     * @param value0 the first value in the list
     * @param value1 the second value in the list
     */
    public static IntList makeImmutable(int value0, int value1) {
        IntList result = new IntList(2);

        result.add(value0);
        result.add(value1);
        result.setImmutable();

        return result;
    }

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

    /**
     * Constructs an empty instance.
     *
     * @param initialCapacity {@code >= 0;} initial capacity of the list
     */
    public IntList(int initialCapacity) {
        super(true);

        try {
            values = new int[initialCapacity];
        } catch (NegativeArraySizeException ex) {
            // Translate the exception.
            throw new IllegalArgumentException("size < 0");
        }

        size = 0;
        sorted = true;
    }

    /** {@inheritDoc} */
    @Override
    public int hashCode() {
        int result = 0;

        for (int i = 0; i < size; i++) {
            result = (result * 31) + values[i];
        }

        return result;
    }

    /** {@inheritDoc} */
    @Override
    public boolean equals(Object other) {
        if (other == this) {
            return true;
        }

        if (! (other instanceof IntList)) {
            return false;
        }

        IntList otherList = (IntList) other;

        if (sorted != otherList.sorted) {
            return false;
        }

        if (size != otherList.size) {
            return false;
        }

        for (int i = 0; i < size; i++) {
            if (values[i] != otherList.values[i]) {
                return false;
            }
        }

        return true;
    }

    /** {@inheritDoc} */
    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer(size * 5 + 10);

        sb.append('{');

        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 int get(int n) {
        if (n >= size) {
            throw new IndexOutOfBoundsException("n >= size()");
        }

        try {
            return values[n];
        } catch (ArrayIndexOutOfBoundsException ex) {
            // Translate exception.
            throw new IndexOutOfBoundsException("n < 0");
        }
    }

    /**
     * Sets the value at the given index.
     *
     * @param n {@code >= 0, < size();} which element
     * @param value value to store
     */
    public void set(int n, int value) {
        throwIfImmutable();

        if (n >= size) {
            throw new IndexOutOfBoundsException("n >= size()");
        }

        try {
            values[n] = value;
            sorted = false;
        } catch (ArrayIndexOutOfBoundsException ex) {
            // Translate the exception.
            if (n < 0) {
                throw new IllegalArgumentException("n < 0");
            }
        }
    }

    /**
     * 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(int value) {
        throwIfImmutable();

        growIfNeeded();

        values[size++] = value;

        if (sorted && (size > 1)) {
            sorted = (value >= values[size - 2]);
        }
    }

    /**
     * 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()");
        }

        growIfNeeded();

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

        sorted = sorted
                && (n == 0 || value > values[n-1])
                && (n == (size - 1) || value < values[n+1]);
    }

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

        System.arraycopy (values, n + 1, values, n, size - n - 1);
        size--;

        // sort status is unchanged
    }

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

    /**
     * Returns the last element in the array without modifying the array
     *
     * @return last value in the array
     * @throws IndexOutOfBoundsException if stack is empty
     */
    public int top() {
        return get(size - 1);
    }

    /**
     * Pops an element off the end of the list and decreasing the size by one.
     *
     * @return value from what was the last element
     * @throws IndexOutOfBoundsException if stack is empty
     */
    public int pop() {
        throwIfImmutable();

        int result;

        result = get(size-1);
        size--;

        return result;
    }

    /**
     * Pops N elements off the end of the list and decreasing the size by N.
     *
     * @param n {@code >= 0;} number of elements to remove from end
     * @throws IndexOutOfBoundsException if stack is smaller than N
     */
    public void pop(int n) {
        throwIfImmutable();

        size -= n;
    }

    /**
     * Shrinks the size of the list.
     *
     * @param newSize {@code >= 0;} the new size
     */
    public void shrink(int newSize) {
        if (newSize < 0) {
            throw new IllegalArgumentException("newSize < 0");
        }

        if (newSize > size) {
            throw new IllegalArgumentException("newSize > size");
        }

        throwIfImmutable();

        size = newSize;
    }

    /**
     * Makes and returns a mutable copy of the list.
     *
     * @return {@code non-null;} an appropriately-constructed instance
     */
    public IntList mutableCopy() {
        int sz = size;
        IntList result = new IntList(sz);

        for (int i = 0; i < sz; i++) {
            result.add(values[i]);
        }

        return result;
    }

    /**
     * Sorts the elements in the list in-place.
     */
    public void sort() {
        throwIfImmutable();

        if (!sorted) {
            Arrays.sort(values, 0, size);
            sorted = true;
        }
    }

    /**
     * Returns the index of the given value, or -1 if the value does not
     * appear in the list.  This will do a binary search if the list is
     * sorted or a linear search if not.
     *
     * @param value value to find
     * @return index of value or -1
     */
    public int indexOf(int value) {
        int ret = binarysearch(value);

        return ret >= 0 ? ret : -1;

    }

    /**
     * Performs a binary search on a sorted list, returning the index of
     * the given value if it is present or
     * {@code (-(insertion point) - 1)} if the value is not present.
     * If the list is not sorted, then reverts to linear search and returns
     * {@code -size()} if the element is not found.
     *
     * @param value value to find
     * @return index of value or {@code (-(insertion point) - 1)} if the
     * value is not present
     */
    public int binarysearch(int value) {
        int sz = size;

        if (!sorted) {
            // Linear search.
            for (int i = 0; i < sz; i++) {
                if (values[i] == value) {
                    return i;
                }
            }

            return -sz;
        }

        /*
         * Binary search. This variant does only one value comparison
         * per iteration but does one more iteration on average than
         * the variant that includes a value equality check per
         * iteration.
         */

        int min = -1;
        int max = sz;

        while (max > (min + 1)) {
            /*
             * The guessIdx calculation is equivalent to ((min + max)
             * / 2) but won't go wonky when min and max are close to
             * Integer.MAX_VALUE.
             */
            int guessIdx = min + ((max - min) >> 1);
            int guess = values[guessIdx];

            if (value <= guess) {
                max = guessIdx;
            } else {
                min = guessIdx;
            }
        }

        if ((max != sz)) {
            return (value == values[max]) ? max : (-max - 1);
        } else {
            return -sz - 1;
        }
    }


    /**
     * Returns whether or not the given value appears in the list.
     * This will do a binary search if the list is sorted or a linear
     * search if not.
     *
     * @see #sort
     *
     * @param value value to look for
     * @return whether the list contains the given value
     */
    public boolean contains(int value) {
        return indexOf(value) >= 0;
    }
}
