/*
 * @author Ranides Atterwim <ranides@gmail.com>
 * @copyright Ranides Atterwim
 * @license WTFPL
 * @url http://ranides.net/projects/assira
 */
package net.ranides.assira.collection.arrays;

import java.lang.reflect.Array;
import java.util.Comparator;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Random;
import java.util.function.BiPredicate;
import net.ranides.assira.collection.iterators.IntIterator;

@SuppressWarnings({
    "PMD.AvoidReassigningParameters"
})
public final class ArrayUtils {

    private ArrayUtils() {
        // utility class
    }
    
    /**
     * Unwraps an iterator into an array starting at a given offset for a given
     * number of elements.
     *
     * <P>This method iterates over the given type-specific iterator and stores
     * the elements returned, up to a maximum of
     * <code>length</code>, in the given array starting at
     * <code>offset</code>. The number of actually unwrapped elements is
     * returned (it may be less than
     * <code>max</code> if the iterator emits less than
     * <code>max</code> elements).
     *
     * @param iterator a type-specific iterator.
     * @param target an array to contain the output of the iterator.
     * @param begin the first element of the array to be returned.
     * @param end the maximum number of elements to collect.
     * @return the number of elements unwrapped.
     */
    public static <A,T> int collect(Iterator<T> iterator, A target, int begin, int end) {
        if($isint(target)) {
            return $collect(IntIterator.$wrap(iterator), (int[])target, begin, end);
        } else {
            return NativeArrayUtils.collect(iterator, NativeArray.wrap(target), begin, end);
        }
    }

    private static <A> boolean $isint(A array) {
        return null!=array && int[].class.equals(array.getClass());
    }
    
    /**
     * Unwraps an iterator into an array.
     *
     * <P>This method iterates over the given type-specific iterator and stores
     * the elements returned in the given array. The iteration will stop when
     * the iterator has no more elements or when the end of the array has been
     * reached.
     *
     * @param iterator a type-specific iterator.
     * @param target an array to contain the output of the iterator.
     * @return the number of elements unwrapped.
     */
    public static <A,T> int collect(Iterator<T> iterator, A target) {
        if($isint(target)) {
            int[] array = (int[])target;
            return $collect(IntIterator.$wrap(iterator), array, 0, array.length);
        } else {
            return NativeArrayUtils.collect(iterator, NativeArray.wrap(target));
        }
    }
    
    static int $collect(IntIterator iterator, int target[], int begin, int end) {
        NativeArrayAllocator.ensureFromTo(target.length, begin, end);
        int j = end;
        while (j-- != 0 && iterator.hasNext()) {
            target[begin++] = iterator.nextInt();
        }
        return end - j - 1;
    }
        
    /**
     * Fills the given array with the given value.
     *
     * <P>This method uses a backward loop. It is significantly faster than the
     * corresponding method in {@link java.util.Arrays}.
     *
     * @param array an array.
     * @param value the new value for all elements of the array.
     */
    public static <A,T> void fill(A array, T value) {
        NativeArrayUtils.fill(NativeArray.wrap(array), value);
    }

    /**
     * Fills a portion of the given array with the given value.
     *
     * <P>If possible (i.e.,
     * <code>from</code> is 0) this method uses a backward loop. In this case,
     * it is significantly faster than the corresponding method in
     * {@link java.util.Arrays}.
     *
     * @param array an array.
     * @param begin the starting index of the portion to fill (inclusive).
     * @param end the end index of the portion to fill (exclusive).
     * @param value the new value for all elements of the specified portion of
     * the array.
     */
    public static <A,T> void fill(A array, int begin, int end, T value) {
        NativeArrayUtils.fill(NativeArray.wrap(array), begin, end, value);
    }
    
    public static <A> A reverse(A array, int begin, int end){
        NativeArrayUtils.reverse(NativeArray.wrap(array), begin, end);
        return array;
    }

    /**
     * Reverses the order of the elements in the specified array.
     *
     * @param array the array to be reversed.
     * @return <code>a</code>.
     */
    public static <A> A reverse(A array) {
        NativeArrayUtils.reverse(NativeArray.wrap(array));
        return array;
    }
    
    /**
     * Returns true if the two arrays are elementwise equal.
     *
     * <P>This method uses a backward loop. It is significantly faster than the
     * corresponding method in {@link java.util.Arrays}.
     *
     * @param array1 an array.
     * @param array2 another array.
     * @return true if the two arrays are of the same length, and their elements
     * are equal.
     */
    public static <A> boolean equals(A array1, A array2) {
        return NativeArrayUtils.equals(NativeArray.wrap(array1), NativeArray.wrap(array2));
    }
    
    public static <A,T> A head(A array, int index) {
        return NativeArrayUtils.head(NativeArray.wrap(array), index).$array();
    }

    public static <A,T> A head(A array, T value, Comparator<? super T> cmp) {
        return NativeArrayUtils.head(NativeArray.wrap(array), value, cmp).$array();
    }
    
    public static <A,T> A tail(A array, int index) {
        return NativeArrayUtils.tail(NativeArray.wrap(array), index).$array();
    }
    
    public static <A,T> A tail(A array, T value, Comparator<? super T> cmp) {
        return NativeArrayUtils.tail(NativeArray.wrap(array), value, cmp).$array();
    }
   
    public static <A,T> A slice(A values, T begin, T end, Comparator<? super T> cmp) {
        return NativeArrayUtils.slice(NativeArray.wrap(values), begin, end, cmp).$array();
    }
    
    public static <A> A slice(A array, int begin, int end) {
        return NativeArrayUtils.slice(NativeArray.wrap(array), begin, end).$array();
    }
    
    public static <A> A rotate(A array, int offset) {
        return NativeArrayUtils.rotate(NativeArray.wrap(array), offset).$array();
    }
    
    public static <A> A shuffle(Random random, A array) {
        return NativeArrayUtils.shuffle(random, NativeArray.wrap(array)).$array();
    }
    
    public static <A> int size(A array) {
        return null == array ? 0 : Array.getLength(array);
    }
    
    @SuppressWarnings("SuspiciousSystemArraycopy")
    public static <A> A copy(A src, int srcpos, A dst, int dstpos, int size) {
        System.arraycopy(src, srcpos, dst, dstpos, size);
        return dst;
    }
    
    public static <A> A copy(A array) {
        if(null == array) {
            return null;
        }
        int n = Array.getLength(array);
        return copy(array, 0, ArrayAllocator.forPrototype(array, n), 0, n);
    }
    
    public static <A> A concat(A a, A b) {
        return NativeArrayUtils.concat(NativeArray.wrap(a), NativeArray.wrap(b)).$array();
    }

    public static <A,T> ListIterator<T> iterator(A array) {
        return NativeArrayUtils.iterator(NativeArray.wrap(array));
    }

    public static <A,T> ListIterator<T> iterator(A array, int begin, int end) {
        return NativeArrayUtils.iterator(NativeArray.wrap(array), begin, end);
    }
    
    public static <A,T> boolean equals(A a, A b, BiPredicate<T,T> predicate) {
        return NativeArrayUtils.equals(NativeArray.wrap(a), NativeArray.wrap(b), predicate);
    }
    
    public static <A,T> int compare(A a, A b) {
        return NativeArrayUtils.compare(NativeArray.wrap(a), NativeArray.wrap(b));
    }
    
    public static <A,T> int compare(A a, A b, Comparator<T> cmp) {
        return NativeArrayUtils.compare(NativeArray.wrap(a), NativeArray.wrap(b), cmp);
    }
}
