/*
 * @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.util.Comparator;
import java.util.Iterator;
import java.util.ListIterator;
import java.util.Random;
import java.util.function.BiPredicate;
import net.ranides.assira.collection.IntComparator;
import net.ranides.assira.collection.Swapper;
import net.ranides.assira.collection.arrays.NativeArray.NativeComparator;
import net.ranides.assira.collection.iterators.RandomAccessIterator;
import net.ranides.assira.generic.CompareUtils;

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

    private NativeArrayUtils() {
        // utility class
    }
    
    public static <T> int collect(Iterator<? extends T> iterator, NativeArray target, int begin, int end) {
        NativeArrayAllocator.ensureFromTo(target, begin, end);
        int j = end;
        while (j-- != 0 && iterator.hasNext()) {
            target.set(begin++, iterator.next());
        }
        return end - j - 1;
    }

    public static <T> int collect(Iterator<? extends T> iterator, NativeArray target) {
        return collect(iterator, target, 0, target.size());
    }

    /**
     * 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 <T> void fill(NativeArray array, T value) {
        array.set(0, array.size(), value);
    }

    public static <T> void fill(NativeArray array, int begin, int end, T value) {
        NativeArrayAllocator.ensureFromTo(array, begin, end);
        array.set(begin, end, value);
    }
    
    public static <S extends Swapper> S reverse(S swapper, int begin, int end){
        while(begin < --end){
            swapper.swap(begin++, end);
        }
        return swapper;
    }

    public static <S extends Swapper> S reverse(S swapper, int size) {
        return reverse(swapper, 0, size);
    }
    
    public static NativeArray reverse(NativeArray array) {
        reverse(array, array.size());
        return array;
    }
    
    public static boolean equals(NativeArray array1, NativeArray array2) {
        return array1.equals(array2);
    }
    
    public static <T> NativeArray head(NativeArray values, int index) {
        return slice(values, 0, index);
    }

    public static <T> NativeArray head(NativeArray values, T value, Comparator<? super T> cmp) {
        return slice(values, 0, ifind(values, value, 0, cmp));
    }
    
    public static <T> NativeArray tail(NativeArray values, int index) {
        return slice(values, index, values.size());
    }
    
    public static <T> NativeArray tail(NativeArray values, T value, Comparator<? super T> cmp) {
        return slice(values, ifind(values, value, 0, cmp), values.size());
    }
    
    public static <T> NativeArray slice(NativeArray values, T begin, T end, Comparator<? super T> cmp) {
        int bi = ifind(values, begin, 0, cmp);
        int ei = ifind(values, end, bi, cmp);
        return slice(values, bi, ei);
    }

    public static NativeArray slice(NativeArray values, int begin, int end) {
        int size = end - begin;
        NativeArray result = values.allocate(size);
        NativeArrayUtils.copy(values, begin, result, 0, size);
        return result;
    }
    
    public static NativeArray rotate(NativeArray array, int offset) {
        int n = array.size();
        offset = offset % n;
        if( offset > 0) {
            int a = n - offset; 
            reverse(array, 0, a);
            reverse(array, a, n);
            reverse(array, 0, n);
        } 
        else if( offset < 0) {
            rotate(array, offset+n);
        }
        return array;
    }
    
    private static <T> int ifind(NativeArray values, T value, int begin, Comparator<? super T> cmp) {
        NativeComparator icmp = values.rcmp(value, cmp);
        int i = begin, n = values.size();
        while(i<n && icmp.compare(i) < 0) {
            i++;
        }
        return i;
    }
    
    public static NativeArray shuffle(Random random, NativeArray array) {
        shuffle(random, array.size(), array);
        return array;
    }
    
    public static void shuffle(Random random, int size, Swapper swapper) {
        for(int i=0; i<size; i++) {
            swapper.swap(random.nextInt(size), random.nextInt(size));
        }
    }
    
    public static int size(NativeArray array) {
        return array.size();
    }
    
    @SuppressWarnings("SuspiciousSystemArraycopy")
    public static <T> NativeArray copy(NativeArray src, int srcpos, NativeArray dst, int dstpos, int size) {
        System.arraycopy(src.array(), srcpos, dst.array(), dstpos, size);
        return dst;
    }
    
    public static NativeArray copy(NativeArray array) {
        int n = array.size();
        return copy(array, 0, NativeArrayAllocator.forPrototype(array, n), 0, n);
    }
    
    public static NativeArray concat(NativeArray a, NativeArray b) {
        int as = a.size();
        int bs = b.size();
        NativeArray result = a.allocate(as+bs);
        copy(a, 0, result, 0, as);
        copy(b, 0, result, as, bs);
        return result;
    }
    
    public static <T> ListIterator<T> iterator(NativeArray array) {
        return iterator(array, 0, array.size());
    }

    @SuppressWarnings("unchecked")
    public static <T> ListIterator<T> iterator(NativeArray array, int begin, int end) {
        return new RandomAccessIterator<T>() {
            @Override
            protected int size() {
                return end-begin;
            }

            @Override
            protected T get(int index) {
                return (T)array.get(begin+index);
            }

            @Override
            protected void set(int index, T value) {
                array.set(begin+index, value);
            }
            
        };
    }
    
    public static <T> boolean equals(NativeArray a, NativeArray b, Comparator<T> cmp) {
        if(a.size() != b.size()) {
            return false;
        }
        IntComparator icmp = a.comparator(b, cmp);
        for(int i=0, n=a.size(); i<n; i++) {
            if( 0 != icmp.compare(i,i)) {
                return false;
            }
        }
        return true;
    }
    
    public static <T> boolean equals(NativeArray a, NativeArray b, BiPredicate<T,T> predicate) {
        if(a.size() != b.size()) {
            return false;
        }
        Comparator<T> vcmp = (av,bv) -> predicate.test(av, bv) ? 0 : -1;
        IntComparator icmp = a.comparator(b, vcmp);
        for(int i=0, n=a.size(); i<n; i++) {
            if( 0 != icmp.compare(i,i)) {
                return false;
            }
        }
        return true;
    }
    
    public static <T> int compare(NativeArray a, NativeArray b) {
        return icompare(a.size(), b.size(), a.comparator(b));
    }
    
    public static <T> int compare(NativeArray a, NativeArray b, Comparator<T> cmp) {
        return icompare(a.size(), b.size(), a.comparator(b, cmp));
    }
    
    private static <T> int icompare(int a, int b, IntComparator cmp) {
        for(int i=0, n=Math.min(a,b); i<n; i++) {
            int ic = cmp.compare(i,i);
            if(ic != 0) {
                return ic;
            }
        }
        return CompareUtils.cmp(a,b);
    }
    
}
