/*
 * Copyright (c) 2022 MiLaboratories Inc.
 *
 * 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.milaboratory.util;

import java.util.Arrays;

/**
 * Array list of primitive integers.
 *
 * @author Dmitry Bolotin
 * @author Stanislav Poslavsky
 */
public final class IntArrayList implements java.io.Serializable {

    int[] data;
    int size = 0;

    public IntArrayList() {
        data = new int[10];
    }

    public IntArrayList(int initialCapacity) {
        data = new int[initialCapacity];
    }

    public IntArrayList(int... data) {
        this.data = data;
        size = data.length;
    }

    private IntArrayList(int[] data, int size) {
        this.data = data;
        this.size = size;
    }

    public void clear() {
        size = 0;
    }

    public void ensureCapacity(int minCapacity) {
        int oldCapacity = data.length;
        if (minCapacity > oldCapacity) {
            int newCapacity = (oldCapacity * 3) / 2 + 1;
            if (newCapacity < minCapacity)
                newCapacity = minCapacity;
            data = Arrays.copyOf(data, newCapacity);
        }
    }

    public void add(int num) {
        ensureCapacity(size + 1);
        data[size++] = num;
    }

    public void add(int position, int num) {
        if (position < 0 || position >= size)
            throw new IndexOutOfBoundsException();
        ensureCapacity(size + 1);
        System.arraycopy(data, position, data, position + 1,
                size - position);
        data[position] = num;
        size++;
    }

    public void addAll(int[] arr) {
        int arrLen = arr.length;
        ensureCapacity(arrLen + size);
        System.arraycopy(arr, 0, data, size, arrLen);
        size += arrLen;
    }

    public void addAll(IntArrayList intArrayList) {
        int arrLen = intArrayList.size();
        ensureCapacity(arrLen + size);
        System.arraycopy(intArrayList.data, 0, data, size, arrLen);
        size += arrLen;
    }

    public void set(int position, int num) {
        if (position < 0 || position >= size)
            throw new IndexOutOfBoundsException();
        data[position] = num;
    }

    public void copyFrom(IntArrayList other) {
        clear();
        addAll(other);
    }

    public void sort() {
        Arrays.sort(data, 0, size);
    }

    public void stableSort(IntComparator comparator) {
        int i, j;
        int key;
        for (i = 1; i < size; i++) {
            key = data[i];
            for (j = i; j > 0 && comparator.compare(data[j - 1], key) > 0; j--) {
                data[j] = data[j - 1];
            }
            data[j] = key;
        }
    }

    /**
     * Alias for addAll(int) method.
     *
     * @param value
     */
    public void push(int value) {
        add(value);
    }

    /**
     * Return last element leaving it in stack.
     *
     * @return last element leaving it in stack
     */
    public int peek() {
        return data[size - 1];
    }

    /**
     * Removes the object at the top of this stack and returns that object as the value of this function.
     *
     * @return pop
     */
    public int pop() {
        return data[--size];
    }

    /**
     * Copies an array from the specified source array, beginning at the specified position. The number of components
     * copied is equal to the length argument.
     *
     * @param src       - the source array.
     * @param fromIndex - starting position in the source array.
     * @param length    - the number of array elements to be copied.
     */
    public void add(int[] src, int fromIndex, int length) {
        ensureCapacity(size + length);
        System.arraycopy(src, fromIndex, data, size, length);
        size = size + length;
    }

    public void reverse() {
        reverse(0, size);
    }

    public void reverse(int from, int to) {
        int tmp, index1, index2, length = to - from;
        for (int i = (length + 1) / 2 - 1; i >= 0; --i) {
            tmp = data[index1 = from + i];
            data[index1] = data[index2 = from + length - i - 1];
            data[index2] = tmp;
        }
    }

    public int get(int i) {
        if (i < 0 || i >= size)
            throw new IndexOutOfBoundsException("Size: " + size + " index: " + i);
        return data[i];
    }

    public boolean replaceFirst(int from, int to) {
        for (int i = 0; i < size; ++i)
            if (data[i] == from) {
                data[i] = to;
                return true;
            }
        return false;
    }

    public boolean replaceAll(int from, int to) {
        boolean replaced = false;
        for (int i = 0; i < size; ++i)
            if (data[i] == from) {
                data[i] = to;
                replaced = true;
            }
        return replaced;
    }

    public int last() {
        return data[size - 1];
    }

    public int[] toArray() {
        return Arrays.copyOfRange(data, 0, size);
    }

    public int size() {
        return size;
    }

    public boolean contains(int value) {
        return indexOf(value) >= 0;
    }

    public int indexOf(int value) {
        for (int i = 0; i < size; ++i)
            if (data[i] == value)
                return i;
        return -1;
    }

    public boolean isEmpty() {
        return size == 0;
    }

    @Override
    public IntArrayList clone() {
        return new IntArrayList(Arrays.copyOf(data, data.length), size);
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null)
            return false;
        if (getClass() != obj.getClass())
            return false;
        final IntArrayList other = (IntArrayList) obj;
        if (this.size != other.size)
            return false;
        for (int i = 0; i < size; ++i)
            if (this.data[i] != other.data[i])
                return false;
        return true;
    }

    @Override
    public int hashCode() {
        int hash = 5;
        for (int i = 0; i < size; ++i)
            hash = 31 * hash + data[i];
        return hash;
    }

    @Override
    public String toString() {
        int iMax = size() - 1;
        if (iMax == -1)
            return "[]";
        StringBuilder b = new StringBuilder();
        b.append('[');
        for (int i = 0; ; i++) {
            b.append(data[i]);
            if (i == iMax)
                return b.append(']').toString();
            b.append(", ");
        }
    }

    public static int[] getArrayReference(IntArrayList list) {
        return list.data;
    }

    public interface IntComparator {
        int compare(int i1, int i2);
    }
}
