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

import java.io.Serializable;
import java.lang.reflect.Array;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.function.IntPredicate;
import net.ranides.assira.collection.IntComparator;
import net.ranides.assira.collection.Swapper;
import net.ranides.assira.generic.CompareUtils;
import net.ranides.assira.reflection.util.ClassTraits;
import net.ranides.assira.reflection.util.ClassUtils;

/**
 * NativeArray is a class which wraps any array (especially primitive array) and gives possibility to
 * work with array in generic way (all boxing operations are done internally).
 *
 * It defines a bunch of methods for optimized comparing elements.
 * For example, you can create IntPredicate for value "x" which checks if item at some index equals "x".
 * Those predicate won't do unboxing over and over again. It will select optimized primitive operation.
 *
 * You can create those kind of comparators for
 *      equality: value == array[index]
 *      left comparison: compareTo(value, array[index])
 *      right comparison: compareTo(array[index], value)
 *
 * You can create optimized comparator between array elements:
 *      comparator(array) will return IntComparator which takes array indexes and compares elements inside array.
 *
 * You can create optimized function for moving elements from one array to another:
 *      moveTo(target) will represent operation target[index] = source[index]
 *      moveFrom(source) will represent operation source[index] = target[index]
 *
 * @author Ranides Atterwim {@literal <ranides@gmail.com>}
 */
public abstract class NativeArray implements Swapper, Comparable<NativeArray>, Cloneable, Serializable {
    
    private static final long serialVersionUID = 1L;

    /**
     * Creates new array of the same type but different size.
     * It is not copy or clone operation, it takes only component type from current array.
     *
     * @param size size
     * @return NativeArray
     */
    public abstract NativeArray allocate(int size);

    /**
     * Returns array wrapped inside this object.
     *
     * @return array
     */
    public abstract Object array();

    /**
     * Returns array wrapped inside this object.
     * It infers array type.
     *
     * @param <Auto> array
     * @return array
     */
    @SuppressWarnings("unchecked")
    public <Auto> Auto $array() {
        return (Auto)array();
    }

    /**
     * Returns length of array
     * @return int
     */
    public abstract int size();

    /**
     * Returns component type of array
     * @return type
     */
    public Class<?> component() {
        return array().getClass().getComponentType();
    }

    /**
     * Sets value at specified index.
     * Unboxes value if target array is primitive.
     *
     * @param index index
     * @param value value
     */
    public abstract void set(int index, Object value);

    /**
     * Sets value for all elements in provided range.
     * Unboxes value (but only once) if target array is primitive.
     *
     * @param begin begin
     * @param end end
     * @param value value
     */
    public abstract void set(int begin, int end, Object value);

    /**
     * Returns value stored in array at specified index
     * Boxes value if target array is primitive.
     *
     * @param index index
     * @return value
     */
    public abstract Object get(int index);

    /**
     * Returns operator, which represents assignment: target[index] = source[index]
     *
     * Returned operators avoid unecessary boxing/unboxing operations
     *
     * @param source source
     * @return operator
     */
    public abstract NativeMove moveFrom(NativeArray source);

    /**
     * Returns operator, which represents assignment: source[index] = target[index]
     *
     * Returned operators avoid unecessary boxing/unboxing operations
     *
     * @param source source
     * @return operator
     */
    public NativeMove moveTo(NativeArray source) {
        return source.moveFrom(this);
    }

    /**
     * Overwrites value at [index] with value from position [source].
     * Avoid unecessary boxing/unboxing operations
     *
     * @param index index
     * @param source source
     */
    public abstract void move(int index, int source);

    /**
     * Sets value at specified index.
     * Returns previous value stored in array.
     *
     * Unboxes value and boxes result if target array is primitive.
     *
     * @param index index
     * @param value value
     * @return value
     */
    public Object replace(int index, Object value) {
        Object prv = get(index);
        set(index, value);
        return prv;
    }
    
    @Override
    @SuppressWarnings({
        "PMD",
        "CloneDoesntCallSuperClone", 
        "CloneDeclaresCloneNotSupported", 
        "SuspiciousSystemArraycopy"
    })
    public NativeArray clone() {
        NativeArray ret = allocate(size());
        System.arraycopy(array(), 0, ret.array(), 0, size());
        return ret;
    }

    /**
     * Allocates new array with boxed component and copies all elements into it.
     * If array contains already boxed values, returns this object.
     *
     * @return array with boxed values
     */
    public NativeArray box() {
        if(ClassTraits.isBoxed(component())) {
            return this;
        }
        NativeArray ret = allocate(ClassUtils.box(component()), size());
        for(int i=0, n=size(); i<n; i++) {
            ret.set(i, get(i));
        }
        return ret;
    }

    /**
     * Allocates new array with primitive component and copies all elements into it.
     * If array contains primitive boxed values, returns this object.
     *
     * @return array with primitive values
     */
    public NativeArray unbox() {
        if(!ClassTraits.isBoxed(component())) {
            return this;
        }
        NativeArray ret = allocate(ClassUtils.unbox(component()), size());
        for(int i=0, n=size(); i<n; i++) {
            ret.set(i, get(i));
        }
        return ret;
    }

    /**
     * Returns operator, which represents comparison: {@code value equals array[index]}
     * In other words: checks if element at some "index" equals to "value"
     *
     * Returned operators avoid unecessary boxing/unboxing operations
     *
     * @param value value
     * @return operator
     */
    public abstract IntPredicate cmp(Object value);

    /**
     * Returns operator, which represents comparison: {@code value.compareTo(array[index])}
     * In other words: checks if element at some "index" is lower or greater than "value"
     *
     * Returned operators avoid unecessary boxing/unboxing operations
     *
     * @param value value
     * @return operator
     */
    public abstract NativeComparator lcmp(Object value);

    /**
     * Returns operator, which represents comparison: {@code cmp.compareTo(value, array[index]) }
     * In other words: checks if element at some "index" is lower or greater than "value" using provided comparator
     *
     * Because we pass values into generic comparator, all values will be boxed.
     * There is optimization if you provide IntComparator for int[] arrays
     *
     * @param value value
     * @param cmp cmp
     * @return operator
     */
    @SuppressWarnings("unchecked")
    public NativeComparator lcmp(Object value, Comparator cmp) {
        return index -> cmp.compare(value, get(index));
    }

    /**
     * Returns operator, which represents comparison: {@code array[index].compareTo(value)}
     * In other words: checks if "value" is lower or greater than element at some "index"
     *
     * Returned operators avoid unecessary boxing/unboxing operations
     *
     * @param value value
     * @return operator
     */
    public abstract NativeComparator rcmp(Object value);

    /**
     * Returns operator, which represents comparison: {@code cmp.compareTo(array[index], value) }
     * In other words: checks if "value" is lower or greater than element at some "index" using provided comparator
     *
     * Because we pass values into generic comparator, all values will be boxed.
     * There is optimization if you provide IntComparator for int[] arrays
     *
     * @param value value
     * @param cmp cmp
     * @return operator
     */
    @SuppressWarnings("unchecked")
    public NativeComparator rcmp(Object value, Comparator cmp) {
        return index -> cmp.compare(get(index), value);
    }

    /**
     * Returns operator, which represents comparison: {@code array[index].compareTo(values[index])}
     * In other words: compares elements of arrays at "index"
     *
     * Returned operators avoid unecessary boxing/unboxing operations
     *
     * @param values values
     * @return operator
     */
    public abstract IntComparator comparator(NativeArray values);

    /**
     * Returns operator, which represents comparison: {@code cmp.compareTo(array[index], values[index])}
     * In other words: compares elements of arrays at "index" using provided comparator
     *
     * Because we pass values into generic comparator, all values will be boxed.
     * There is optimization if you provide IntComparator for int[] arrays
     *
     * @param values values
     * @param cmp cmp
     * @return operator
     */
    public abstract IntComparator comparator(NativeArray values, Comparator cmp);

    /**
     * Returns operator, which represents comparison: {@code array[index1].compareTo(array[index2])}
     * In other words: compares two elements of this array
     *
     * Returned operators avoid unecessary boxing/unboxing operations
     *
     * @return operator
     */
    public abstract IntComparator comparator();

    /**
     * Returns operator, which represents comparison: {@code cmp.compareTo(array[index1], array[index2])}
     * In other words: compares two elements of this array using provided comparator
     *
     * Because we pass values into generic comparator, all values will be boxed.
     * There is optimization if you provide IntComparator for int[] arrays
     *
     * @param cmp cmp
     * @return operator
     */
    @SuppressWarnings("unchecked")
    public IntComparator comparator(Comparator cmp) {
        return (a,b) -> cmp.compare(get(a), get(b));
    }
    
    @Override
    public int hashCode() {
        return NativeArray.this.hashCode(0, size());
    }

    /**
     * Calculates hashCode of element at "index"
     *
     * @param index index
     * @return int
     */
    public abstract int hashCode(int index);

    /**
     * Calculates hashCode for elements from specified range.
     * It uses the same algorithm as {@link List#hashCode}
     *
     * @param begin begin
     * @param end end
     * @return int
     */
    public int hashCode(int begin, int end) {
        int result = 1;
        for(int i=begin; i<end; i++) {
            result = 31 * result + hashCode(i);
        }
        return result;
    }

    /**
     * Checks for equality comparing all corresponding elements.
     *
     * @param object object
     * @return bool
     */
    @Override
    public boolean equals(Object object) {
        if(!(object instanceof NativeArray)) {
            return false;
        }
        NativeArray na = (NativeArray)object;
        return na.size()==size() && equals(na, 0, 0, size());
    }

    /**
     * Checks for equality comparing all corresponding elements in specified range.
     *
     * @param object other array
     * @param src start index of this array
     * @param dst start index of another array
     * @param size number of elements to compare
     * @return bool
     */
    public boolean equals(NativeArray object, int src, int dst, int size) {
        return 0 == compareTo(object, src, dst, size);
    }

    /**
     * Compares all corresponding elements.
     *
     * @param object object
     * @return int
     */
    @Override
    public int compareTo(NativeArray object) {
        int a = size();
        int b = object.size();
        int c = compareTo(object, 0, 0, Math.min(a,b));
        return 0 != c ? c : Integer.compare(a, b);
    }

    /**
     * Compares all corresponding elements using provided comparator.
     *
     * @param object object
     * @param comp comp
     * @return int
     */
    public int compareTo(NativeArray object, Comparator comp) {
        int a = size();
        int b = object.size();
        int c = compareTo(object, 0, 0, Math.min(a,b), comp);
        return 0 != c ? c : Integer.compare(a, b);
    }

    /**
     * Compares all corresponding elements in specified range.
     *
     * @param object other array
     * @param src start index of this array
     * @param dst start index of other array
     * @param size number of elements to compare
     * @return int
     */
    public int compareTo(NativeArray object, int src, int dst, int size) {
        if(size == 0) {
            return 0;
        }
        if( object.array() == array()) {
            return 0;
        }
        IntComparator na = NativeArray.this.comparator(object);
        for(int i=0; i<size; i++) {
            int cmp = na.compare(src++, dst++);
            if(0 != cmp) {
                return cmp;
            }
        }
        return 0;
    }

    /**
     * Compares all corresponding elements in specified range using provided comparator.
     *
     * @param object other array
     * @param src start index of this array
     * @param dst start index of other array
     * @param size number of elements to compare
     * @param comp comp
     * @return int
     */
    public int compareTo(NativeArray object, int src, int dst, int size, Comparator comp) {
        if(size == 0) {
            return 0;
        }
        if( object.array() == array()) {
            return 0;
        }
        IntComparator na = NativeArray.this.comparator(object, comp);
        for(int i=0; i<size; i++) {
            int cmp = na.compare(src++, dst++);
            if(0 != cmp) {
                return cmp;
            }
        }
        return 0;
    }


    /**
     * Operator: it compares array element at "index" with some other value.
     * For example:
     *      this comparator can contain binded value, which we want to find.
     *      this comparator can compare array[index] with another list[index]
     *
     * The whole idea is to provide optimized comparators which avoid boxing everything to Object
     */
    public interface NativeComparator {

        /**
         * Compare array element at "index" with some other value
         * @param index index
         * @return int
         */
        int compare(int index);
        
    }

    /**
     * Operator: represent assignment operation: array1[target] = array2[source]
     * Used for moving elements between arrays.
     *
     * The whole idea is to provide optimized operators which avoid boxing everything to Object and then unboxing again
     */
    public interface NativeMove {

        /**
         * Moves value from one place to another.
         * "source" and "target" index can point to the different arrays.
         *
         * @param target target
         * @param source source
         */
        void move(int target, int source);
        
    }

    /**
     * Allocates new array of provided component type and wraps it into NativeArray.
     * Primitive component types are supported.
     *
     * @param component component
     * @param size size
     * @return NativeArray
     */
    @SuppressWarnings({
        "PMD.NPathComplexity",
        "PMD.CyclomaticComplexity"
    })
    public static NativeArray allocate(Class<?> component, int size) {
        if(!component.isPrimitive()) {
            return new ObjectArray(component, size);
        }
        if(byte.class.equals(component)) {
            return new ByteArray(size);
        }
        if(short.class.equals(component)) {
            return new ShortArray(size);
        }
        if(int.class.equals(component)) {
            return new IntegerArray(size);
        }
        if(long.class.equals(component)) {
            return new LongArray(size);
        }
        if(float.class.equals(component)) {
            return new FloatArray(size);
        }
        if(double.class.equals(component)) {
            return new DoubleArray(size);
        }
        if(char.class.equals(component)) {
            return new CharacterArray(size);
        }
        if(boolean.class.equals(component)) {
            return new BooleanArray(size);
        }
        throw new UnsupportedOperationException("Not supported type: " + component.getName());
    }

    /**
     * Creates NativeArray backed by provided array.
     * Primitive arrays are supported.
     *
     * @param array array
     * @return NativeArray
     */
	@SuppressWarnings({
        "PMD.NPathComplexity",
        "PMD.CyclomaticComplexity"
    })
	public static NativeArray wrap(Object array) {
        Class<?> c = array.getClass().getComponentType();
        if(null == c) {
            throw new IllegalArgumentException(array.getClass().getName() + " " + array + " is not an array.");
        }
        if(!c.isPrimitive()) {
            return new ObjectArray(array);
        }
        if(byte.class.equals(c)) {
            return new ByteArray(array);
        }
        if(short.class.equals(c)) {
            return new ShortArray(array);
        }
        if(int.class.equals(c)) {
            return new IntegerArray(array);
        }
        if(long.class.equals(c)) {
            return new LongArray(array);
        }
        if(float.class.equals(c)) {
            return new FloatArray(array);
        }
        if(double.class.equals(c)) {
            return new DoubleArray(array);
        }
        if(char.class.equals(c)) {
            return new CharacterArray(array);
        }
        if(boolean.class.equals(c)) {
            return new BooleanArray(array);
        }
        throw new UnsupportedOperationException("Not supported type: " + c.getName());
    }

    /**
     * Creates NativeArray backed by provided array.
     * This override is declared to avoid unecessary branching at runtime.
     *
     * @param array array
     * @return  NativeArray
     */
    public static NativeArray wrap(Object[] array) {
		return new ObjectArray(array);
    }

    /**
     * Creates NativeArray backed by provided array.
     * This override is declared to avoid unecessary branching at runtime.
     *
     * @param array array
     * @return  NativeArray
     */
    public static NativeArray wrap(int[] array) {
		return new IntegerArray(array);
    }

    /**
     * Creates NativeArray backed by provided array.
     * This override is declared to avoid unecessary branching at runtime.
     *
     * @param array array
     * @return  NativeArray
     */
    public static NativeArray wrap(char[] array) {
		return new CharacterArray(array);
    }

    /**
     * Creates NativeArray backed by provided array.
     * This override is declared to avoid unecessary branching at runtime.
     *
     * @param array array
     * @return  NativeArray
     */
    public static NativeArray wrap(byte[] array) {
		return new ByteArray(array);
    }

    /**
     * Creates NativeArray backed by provided array.
     * This override is declared to avoid unecessary branching at runtime.
     *
     * @param array array
     * @return  NativeArray
     */
    public static NativeArray wrap(boolean[] array) {
		return new BooleanArray(array);
    }
    
    static final class ByteArray extends NativeArray {
        
        private static final long serialVersionUID = 1L;
        
        private final byte[] array;

        public ByteArray(Object array) {
            this.array = (byte[])array;
        }
        
        public ByteArray(int size) {
            this.array = new byte[size];
        }
        
        @Override
        public NativeArray allocate(int size) {
            return new ByteArray(size);
        }

        @Override
        public Object array() {
            return array;
        }
        
        @Override
        public int size() {
            return array.length;
        }

        @Override
        public void set(int index, Object value) {
            array[index] = (Byte)value;
        }

        @Override
        public void set(int begin, int end, Object value) {
            byte ivalue = (Byte)value;
            for(int i = begin; i< end; i++) {
                array[i] = ivalue;
            }
        }

        @Override
        public Object get(int index) {
            return array[index];
        }
        
        @Override
        public NativeMove moveFrom(NativeArray source) {
            if (source.$array() instanceof byte[]) {
                byte[] array = source.$array();
                return (a, b) -> this.array[a] = array[b];
            } else {
                return (a, b) -> array[a] = (byte) source.get(b);
            }
        }
        
        @Override
        public void move(int a, int b) {
            array[a] = array[b];
        }
        
        @Override
        public IntPredicate cmp(Object value) {
            byte ivalue = (Byte)value;
            return index -> ivalue == array[index];
        }

        @Override
        public NativeComparator lcmp(Object value) {
            byte ivalue = (Byte)value;
            return index -> Byte.compare(ivalue, array[index]);
        }
        
        @Override
        public NativeComparator rcmp(Object value) {
            byte ivalue = (Byte)value;
            return index -> Byte.compare(array[index], ivalue);
        }

        @Override
        public IntComparator comparator() {
            return (a,b) -> Byte.compare(array[a], array[b]);
        }

        @Override
        public void swap(int a, int b) {
            byte temp = array[a];
            array[a] = array[b];
            array[b] = temp;
        }
        
        @Override
        public IntComparator comparator(NativeArray values) {
            if(values instanceof ByteArray) {
                ByteArray that = (ByteArray) values;
                return (a, b) -> Byte.compare(this.array[a], that.array[b]);
            } else {
                return (a, b) -> Byte.compare(this.array[a], (byte)values.get(b));
            }
        }

        @SuppressWarnings("unchecked")
        @Override
        public IntComparator comparator(NativeArray values, Comparator cmp) {
            if(values instanceof ByteArray) {
                ByteArray that = (ByteArray)values;
                return (a,b) -> cmp.compare(this.array[a], that.array[b]);
            } else {
                return (a,b) -> cmp.compare(this.array[a], values.get(b));
            }
        }

        @Override
        public int hashCode(int index) {
            return array[index];
        }

        @Override
        public String toString() {
            return Arrays.toString(array);
        }
        
    }
    
    static final class ShortArray extends NativeArray {
        
        private static final long serialVersionUID = 1L;
        
        private final short[] array;

        public ShortArray(Object array) {
            this.array = (short[])array;
        }
        
        public ShortArray(int size) {
            this.array = new short[size];
        }
        
        @Override
        public NativeArray allocate(int size) {
            return new ShortArray(size);
        }
        
        @Override
        public Object array() {
            return array;
        }
        
        @Override
        public int size() {
            return array.length;
        }

        @Override
        public void set(int index, Object value) {
            array[index] = (Short)value;
        }

        @Override
        public void set(int begin, int end, Object value) {
            short ivalue = (Short)value;
            for(int i = begin; i< end; i++) {
                array[i] = ivalue;
            }
        }

        @Override
        public Object get(int index) {
            return array[index];
        }
        
        @Override
        public NativeMove moveFrom(NativeArray source) {
            if (source.$array() instanceof short[]) {
                short[] array = source.$array();
                return (a, b) -> this.array[a] = array[b];
            } else {
                return (a, b) -> array[a] = (short) source.get(b);
            }
        }
        
        @Override
        public void move(int a, int b) {
            array[a] = array[b];
        }
        
        @Override
        public IntPredicate cmp(Object value) {
            short ivalue = (Short)value;
            return index -> ivalue == array[index];
        }

        @Override
        public NativeComparator lcmp(Object value) {
            short ivalue = (Short)value;
            return index -> Short.compare(ivalue, array[index]);
        }
        
        @Override
        public NativeComparator rcmp(Object value) {
            short ivalue = (Short)value;
            return index -> Short.compare(array[index],ivalue);
        }

        @Override
        public IntComparator comparator() {
            return (a,b) -> Short.compare(array[a], array[b]);
        }

        @Override
        public void swap(int a, int b) {
            short temp = array[a];
            array[a] = array[b];
            array[b] = temp;
        }
        
        @Override
        public IntComparator comparator(NativeArray values) {
            if(values instanceof ShortArray) {
                ShortArray that = (ShortArray) values;
                return (a, b) -> Short.compare(this.array[a], that.array[b]);
            } else {
                return (a, b) -> Short.compare(this.array[a], (short)values.get(b));
            }
        }

        @SuppressWarnings("unchecked")
        @Override
        public IntComparator comparator(NativeArray values, Comparator cmp) {
            if(values instanceof ShortArray) {
                ShortArray that = (ShortArray) values;
                return (a, b) -> cmp.compare(this.array[a], that.array[b]);
            } else {
                return (a,b) -> cmp.compare(this.array[a], values.get(b));
            }
        }
        
        @Override
        public int hashCode(int index) {
            return array[index];
        }
        
        @Override
        public String toString() {
            return Arrays.toString(array);
        }
        
    }
    
    static final class IntegerArray extends NativeArray {
        
        private static final long serialVersionUID = 1L;
        
        private final int[] array;

        public IntegerArray(Object array) {
            this.array = (int[])array;
        }
        
        public IntegerArray(int size) {
            this.array = new int[size];
        }
        
        @Override
        public NativeArray allocate(int size) {
            return new IntegerArray(size);
        }
        
        @Override
        public Object array() {
            return array;
        }
        
        @Override
        public int size() {
            return array.length;
        }

        @Override
        public void set(int index, Object value) {
            array[index] = (Integer)value;
        }

        @Override
        public void set(int begin, int end, Object value) {
            int ivalue = (Integer)value;
            for(int i = begin; i< end; i++) {
                array[i] = ivalue;
            }
        }

        @Override
        public Object get(int index) {
            return array[index];
        }
        
        @Override
        public NativeMove moveFrom(NativeArray source) {
            if (source.$array() instanceof int[]) {
                int[] array = source.$array();
                return (a, b) -> this.array[a] = array[b];
            } else {
                return (a, b) -> array[a] = (int) source.get(b);
            }
        }
        
        @Override
        public void move(int a, int b) {
            array[a] = array[b];
        }
        
        @Override
        public IntPredicate cmp(Object value) {
            int ivalue = (Integer)value;
            return index -> ivalue == array[index];
        }
        
        @SuppressWarnings("unchecked")
        @Override
        public NativeComparator lcmp(Object value, Comparator cmp) {
            if(cmp instanceof IntComparator) {
                IntComparator icmp = (IntComparator)cmp;
                int ivalue = (Integer)value;
                return index -> icmp.compare(ivalue, array[index]);
            } else {
                return index -> cmp.compare(value, array[index]);
            }
        }
        
        @SuppressWarnings("unchecked")
        @Override
        public NativeComparator rcmp(Object value, Comparator cmp) {
            if(cmp instanceof IntComparator) {
                IntComparator icmp = (IntComparator)cmp;
                int ivalue = (Integer)value;
                return index -> icmp.compare(array[index],ivalue);
            } else {
                return index -> cmp.compare(array[index],value);
            }
        }

        @Override
        public NativeComparator lcmp(Object value) {
            int ivalue = (Integer)value;
            return index -> Integer.compare(ivalue, array[index]);
        }
        
        @Override
        public NativeComparator rcmp(Object value) {
            int ivalue = (Integer)value;
            return index -> Integer.compare(array[index],ivalue);
        }

        @Override
        public IntComparator comparator() {
            return (a,b) -> Integer.compare(array[a], array[b]);
        }
        
        @SuppressWarnings("unchecked")
        @Override
        public IntComparator comparator(Comparator cmp) {
            if(cmp instanceof IntComparator) {
                IntComparator icmp = (IntComparator)cmp;
                return (a,b) -> icmp.compare(array[a], array[b]);
            } else {
                return (a,b) -> cmp.compare(array[a], array[b]);
            }
        }

        @Override
        public void swap(int a, int b) {
            int temp = array[a];
            array[a] = array[b];
            array[b] = temp;
        }
        
        @Override
        public IntComparator comparator(NativeArray values) {
            if(values instanceof IntegerArray) {
                IntegerArray that = (IntegerArray) values;
                return (a, b) -> Integer.compare(this.array[a], that.array[b]);
            } else {
                return (a, b) -> Integer.compare(this.array[a], (int)values.get(b));
            }
        }

        @SuppressWarnings("unchecked")
        @Override
        public IntComparator comparator(NativeArray values, Comparator cmp) {
            if(values instanceof IntegerArray && cmp instanceof IntComparator) {
                IntegerArray that = (IntegerArray) values;
                IntComparator icmp = (IntComparator) cmp;
                return (a, b) -> icmp.compare(this.array[a], that.array[b]);
            } else {
                return (a, b) -> cmp.compare(this.array[a], values.get(b));
            }
        }
        
        @Override
        public int hashCode(int index) {
            return array[index];
        }
        
        @Override
        public String toString() {
            return Arrays.toString(array);
        }
        
    }
    
    static final class LongArray extends NativeArray {
        
        private static final long serialVersionUID = 1L;
        
        private final long[] array;

        public LongArray(Object array) {
            this.array = (long[])array;
        }
        
        public LongArray(int size) {
            this.array = new long[size];
        }
        
        @Override
        public NativeArray allocate(int size) {
            return new LongArray(size);
        }
        
        @Override
        public Object array() {
            return array;
        }
        
        @Override
        public int size() {
            return array.length;
        }

        @Override
        public void set(int index, Object value) {
            array[index] = (Long)value;
        }

        @Override
        public void set(int begin, int end, Object value) {
            long ivalue = (Long)value;
            for(int i = begin; i< end; i++) {
                array[i] = ivalue;
            }
        }

        @Override
        public Object get(int index) {
            return array[index];
        }
        
        @Override
        public NativeMove moveFrom(NativeArray source) {
            if (source.$array() instanceof long[]) {
                long[] array = source.$array();
                return (a, b) -> this.array[a] = array[b];
            } else {
                return (a, b) -> array[a] = (long) source.get(b);
            }
        }
        
        @Override
        public void move(int a, int b) {
            array[a] = array[b];
        }
        
        @Override
        public IntPredicate cmp(Object value) {
            long ivalue = (Long)value;
            return index -> ivalue == array[index];
        }

        @Override
        public NativeComparator lcmp(Object value) {
            long ivalue = (Long)value;
            return index -> Long.compare(ivalue, array[index]);
        }
        
        @Override
        public NativeComparator rcmp(Object value) {
            long ivalue = (Long)value;
            return index -> Long.compare(array[index],ivalue);
        }

        @Override
        public IntComparator comparator() {
            return (a,b) -> Long.compare(array[a], array[b]);
        }

        @Override
        public void swap(int a, int b) {
            long temp = array[a];
            array[a] = array[b];
            array[b] = temp;
        }
        
        @Override
        public IntComparator comparator(NativeArray values) {
            if(values instanceof LongArray) {
                LongArray that = (LongArray) values;
                return (a, b) -> Long.compare(this.array[a], that.array[b]);
            } else {
                return (a, b) -> Long.compare(this.array[a], (long)values.get(b));
            }
        }

        @SuppressWarnings("unchecked")
        @Override
        public IntComparator comparator(NativeArray values, Comparator cmp) {
            if(values instanceof LongArray) {
                LongArray that = (LongArray) values;
                return (a, b) -> cmp.compare(this.array[a], that.array[b]);
            } else {
                return (a,b) -> cmp.compare(this.array[a], values.get(b));
            }
        }
        
        @Override
        public int hashCode(int index) {
            return (int)(array[index] ^ (array[index] >>> 32));
        }
        
        @Override
        public String toString() {
            return Arrays.toString(array);
        }
        
    }
    
    static final class FloatArray extends NativeArray {
        
        private static final long serialVersionUID = 1L;
        
        private final float[] array;

        public FloatArray(Object array) {
            this.array = (float[])array;
        }
        
        public FloatArray(int size) {
            this.array = new float[size];
        }
        
        @Override
        public NativeArray allocate(int size) {
            return new FloatArray(size);
        }
        
        @Override
        public Object array() {
            return array;
        }
        
        @Override
        public int size() {
            return array.length;
        }

        @Override
        public void set(int index, Object value) {
            array[index] = (Float)value;
        }

        @Override
        public void set(int begin, int end, Object value) {
            float ivalue = (Float)value;
            for(int i = begin; i< end; i++) {
                array[i] = ivalue;
            }
        }

        @Override
        public Object get(int index) {
            return array[index];
        }
        
        @Override
        public NativeMove moveFrom(NativeArray source) {
            if (source.$array() instanceof float[]) {
                float[] array = source.$array();
                return (a, b) -> this.array[a] = array[b];
            } else {
                return (a, b) -> array[a] = (float) source.get(b);
            }
        }
        
        @Override
        public void move(int a, int b) {
            array[a] = array[b];
        }
        
        @Override
        public IntPredicate cmp(Object value) {
            float ivalue = (Float)value;
            return index -> ivalue == array[index];
        }

        @Override
        public NativeComparator lcmp(Object value) {
            float ivalue = (Float)value;
            return index -> Float.compare(ivalue, array[index]);
        }
        
        @Override
        public NativeComparator rcmp(Object value) {
            float ivalue = (Float)value;
            return index -> Float.compare(array[index],ivalue);
        }

        @Override
        public IntComparator comparator() {
            return (a,b)-> Float.compare(array[a], array[b]);
        }

        @Override
        public void swap(int a, int b) {
            float temp = array[a];
            array[a] = array[b];
            array[b] = temp;
        }
        
        @Override
        public IntComparator comparator(NativeArray values) {
            if(values instanceof FloatArray) {
                FloatArray that = (FloatArray) values;
                return (a, b) -> Float.compare(this.array[a], that.array[b]);
            } else {
                return (a, b) -> Float.compare(this.array[a], (float)values.get(b));
            }
        }

        @SuppressWarnings("unchecked")
        @Override
        public IntComparator comparator(NativeArray values, Comparator cmp) {
            if(values instanceof FloatArray) {
                FloatArray that = (FloatArray) values;
                return (a, b) -> cmp.compare(this.array[a], that.array[b]);
            } else {
                return (a,b) -> cmp.compare(this.array[a], values.get(b));
            }
        }
        
        @Override
        public int hashCode(int index) {
            return Float.floatToIntBits(array[index]);
        }
        
        @Override
        public String toString() {
            return Arrays.toString(array);
        }
        
    }
    
    static final class DoubleArray extends NativeArray {
        
        private static final long serialVersionUID = 1L;
        
        private final double[] array;

        public DoubleArray(Object array) {
            this.array = (double[])array;
        }
        
        public DoubleArray(int size) {
            this.array = new double[size];
        }
        
        @Override
        public NativeArray allocate(int size) {
            return new DoubleArray(size);
        }
        
        @Override
        public Object array() {
            return array;
        }
        
        @Override
        public int size() {
            return array.length;
        }

        @Override
        public void set(int index, Object value) {
            array[index] = (Double)value;
        }

        @Override
        public void set(int begin, int end, Object value) {
            double ivalue = (Double)value;
            for(int i = begin; i< end; i++) {
                array[i] = ivalue;
            }
        }

        @Override
        public Object get(int index) {
            return array[index];
        }
        
        @Override
        public NativeMove moveFrom(NativeArray source) {
            if (source.$array() instanceof double[]) {
                double[] array = source.$array();
                return (a, b) -> this.array[a] = array[b];
            } else {
                return (a, b) -> array[a] = (double) source.get(b);
            }
        }
        
        @Override
        public void move(int a, int b) {
            array[a] = array[b];
        }
        
        @Override
        public IntPredicate cmp(Object value) {
            double ivalue = (Double)value;
            return index -> ivalue == array[index];
        }

        @Override
        public NativeComparator lcmp(Object value) {
            double ivalue = (Double)value;
            return index -> Double.compare(ivalue, array[index]);
        }
        
        @Override
        public NativeComparator rcmp(Object value) {
            double ivalue = (Double)value;
            return index -> Double.compare(array[index],ivalue);
        }

        @Override
        public IntComparator comparator() {
            return (a,b)-> Double.compare(array[a], array[b]);
        }

        @Override
        public void swap(int a, int b) {
            double temp = array[a];
            array[a] = array[b];
            array[b] = temp;
        }
        
        @Override
        public IntComparator comparator(NativeArray values) {
            if(values instanceof DoubleArray) {
                DoubleArray that = (DoubleArray) values;
                return (a, b) -> Double.compare(this.array[a], that.array[b]);
            } else {
                return (a, b) -> Double.compare(this.array[a], (double)values.get(b));
            }
        }

        @SuppressWarnings("unchecked")
        @Override
        public IntComparator comparator(NativeArray values, Comparator cmp) {
            if(values instanceof DoubleArray) {
                DoubleArray that = (DoubleArray) values;
                return (a, b) -> cmp.compare(this.array[a], that.array[b]);
            } else {
                return (a,b) -> cmp.compare(this.array[a], values.get(b));
            }
        }
        
        @Override
        public int hashCode(int index) {
            long bits = Double.doubleToLongBits(array[index]);
            return (int)(bits ^ (bits >>> 32));
        }
        
        @Override
        public String toString() {
            return Arrays.toString(array);
        }
        
    }
    
    static final class CharacterArray extends NativeArray {
        
        private static final long serialVersionUID = 1L;
        
        private final char[] array;

        public CharacterArray(Object array) {
            this.array = (char[])array;
        }
        
        public CharacterArray(int size) {
            this.array = new char[size];
        }
        
        @Override
        public NativeArray allocate(int size) {
            return new CharacterArray(size);
        }
        
        @Override
        public Object array() {
            return array;
        }
        
        @Override
        public int size() {
            return array.length;
        }

        @Override
        public void set(int index, Object value) {
            array[index] = (Character)value;
        }

        @Override
        public void set(int begin, int end, Object value) {
            char ivalue = (Character)value;
            for(int i = begin; i< end; i++) {
                array[i] = ivalue;
            }
        }

        @Override
        public Object get(int index) {
            return array[index];
        }
        
        @Override
        public NativeMove moveFrom(NativeArray source) {
            if (source.$array() instanceof char[]) {
                char[] array = source.$array();
                return (a, b) -> this.array[a] = array[b];
            } else {
                return (a, b) -> array[a] = (char) source.get(b);
            }
        }
        
        @Override
        public void move(int a, int b) {
            array[a] = array[b];
        }
        
        @Override
        public IntPredicate cmp(Object value) {
            char ivalue = (Character)value;
            return index -> ivalue == array[index];
        }

        @Override
        public NativeComparator lcmp(Object value) {
            char ivalue = (Character)value;
            return index -> Character.compare(ivalue, array[index]);
        }
        
        @Override
        public NativeComparator rcmp(Object value) {
            char ivalue = (Character)value;
            return index -> Character.compare(array[index],ivalue);
        }

        @Override
        public IntComparator comparator() {
            return (a,b) -> Character.compare(array[a], array[b]);
        }

        @Override
        public void swap(int a, int b) {
            char temp = array[a];
            array[a] = array[b];
            array[b] = temp;
        }
        
        @Override
        public IntComparator comparator(NativeArray values) {
            if(values instanceof CharacterArray) {
                CharacterArray that = (CharacterArray) values;
                return (a, b) -> Character.compare(this.array[a], that.array[b]);
            } else {
                return (a, b) -> Character.compare(this.array[a], (char)values.get(b));
            }
        }

        @SuppressWarnings("unchecked")
        @Override
        public IntComparator comparator(NativeArray values, Comparator cmp) {
            if(values instanceof CharacterArray) {
                CharacterArray that = (CharacterArray) values;
                return (a, b) -> cmp.compare(this.array[a], that.array[b]);
            } else {
                return (a,b) -> cmp.compare(this.array[a], values.get(b));
            }
        }
        
        @Override
        public int hashCode(int index) {
            return array[index];
        }
        
        @Override
        public String toString() {
            return Arrays.toString(array);
        }
        
    }
    
    static final class BooleanArray extends NativeArray {
        
        private static final long serialVersionUID = 1L;
        
        private final boolean[] array;

        public BooleanArray(Object array) {
            this.array = (boolean[])array;
        }
        
        public BooleanArray(int size) {
            this.array = new boolean[size];
        }
        
        @Override
        public NativeArray allocate(int size) {
            return new BooleanArray(size);
        }
        
        @Override
        public Object array() {
            return array;
        }
        
        @Override
        public int size() {
            return array.length;
        }

        @Override
        public void set(int index, Object value) {
            array[index] = (Boolean)value;
        }

        @Override
        public void set(int begin, int end, Object value) {
            boolean ivalue = (Boolean)value;
            for(int i = begin; i< end; i++) {
                array[i] = ivalue;
            }
        }

        @Override
        public Object get(int index) {
            return array[index];
        }
        
        @Override
        public NativeMove moveFrom(NativeArray source) {
            if(source.$array() instanceof boolean[]) {
                boolean[] array = source.$array();
                return (a, b) -> this.array[a] = array[b];
            } else {
                return (a, b) -> array[a] = (boolean) source.get(b);
            }
        }
        
        @Override
        public void move(int a, int b) {
            array[a] = array[b];
        }
        
        @Override
        public IntPredicate cmp(Object value) {
            boolean ivalue = (Boolean)value;
            return index -> ivalue == array[index];
        }

        @Override
        public NativeComparator lcmp(Object value) {
            boolean ivalue = (Boolean)value;
            return index -> Boolean.compare(ivalue, array[index]);
        }
        
        @Override
        public NativeComparator rcmp(Object value) {
            boolean ivalue = (Boolean)value;
            return index -> Boolean.compare(array[index],ivalue);
        }

        @Override
        public IntComparator comparator() {
            return (a,b)-> Boolean.compare(array[a], array[b]);
        }

        @Override
        public void swap(int a, int b) {
            boolean temp = array[a];
            array[a] = array[b];
            array[b] = temp;
        }
        
        @Override
        public IntComparator comparator(NativeArray values) {
            if(values instanceof BooleanArray) {
                BooleanArray that = (BooleanArray) values;
                return (a, b) -> Boolean.compare(this.array[a], that.array[b]);
            } else {
                return (a, b) -> Boolean.compare(this.array[a], (boolean)values.get(b));
            }
        }

        @SuppressWarnings("unchecked")
        @Override
        public IntComparator comparator(NativeArray values, Comparator cmp) {
            if(values instanceof BooleanArray) {
                BooleanArray that = (BooleanArray) values;
                return (a, b) -> cmp.compare(this.array[a], that.array[b]);
            } else {
                return (a,b) -> cmp.compare(this.array[a], values.get(b));
            }
        }
        
        @Override
        public int hashCode(int index) {
            return array[index] ? 1231 : 1237;
        }
        
        @Override
        public String toString() {
            return Arrays.toString(array);
        }
        
    }
    
    static final class ObjectArray extends NativeArray {
        
        private static final long serialVersionUID = 1L;
        
        private final Object[] array;

        public ObjectArray(Object array) {
            this.array = (Object[])array;
        }
        
        public ObjectArray(Class<?> c, int size) {
            this.array = (Object[])Array.newInstance(c, size);
        }
        
        @Override
        public NativeArray allocate(int size) {
            Class<?> c = array.getClass().getComponentType();
            return new ObjectArray(c, size);
        }
        
        @Override
        public Object array() {
            return array;
        }
        
        @Override
        public int size() {
            return array.length;
        }

        @Override
        public void set(int index, Object value) {
            array[index] = value;
        }

        @Override
        public void set(int begin, int end, Object value) {
            for(int i = begin; i< end; i++) {
                array[i] = value;
            }
        }

        @Override
        public Object get(int index) {
            return array[index];
        }
        
        @Override
        public NativeMove moveFrom(NativeArray source) {
            Object[] s = source.$array();
            return (a,b)->{ array[a]=s[b]; };
        }
        
        @Override
        public void move(int a, int b) {
            array[a] = array[b];
        }
        
        @Override
        public IntPredicate cmp(Object value) {
            return index -> CompareUtils.equals(value, array[index]);
        }

        @Override
        public NativeComparator lcmp(Object value) {
            return index -> CompareUtils.cmp(value, array[index]);
        }
        
        @Override
        public NativeComparator rcmp(Object value) {
            return index -> CompareUtils.cmp(array[index],value);
        }

        @Override
        public IntComparator comparator() {
            return (a,b) -> CompareUtils.cmp(array[a], array[b]);
        }

        @Override
        public void swap(int a, int b) {
            Object temp = array[a];
            array[a] = array[b];
            array[b] = temp;
        }
        
        @Override
        public IntComparator comparator(NativeArray values) {
            if(values instanceof ObjectArray) {
                ObjectArray that = (ObjectArray) values;
                return (a, b) -> CompareUtils.cmp(this.array[a], that.array[b]);
            } else {
                return (a, b) -> CompareUtils.cmp(this.array[a], values.get(b));
            }
        }

		@SuppressWarnings("unchecked")
        @Override
        public IntComparator comparator(NativeArray values, Comparator cmp) {
            if(values instanceof ObjectArray) {
                ObjectArray that = (ObjectArray) values;
                return (a, b) -> cmp.compare(this.array[a], that.array[b]);
            } else {
                return (a,b) -> cmp.compare(this.array[a], values.get(b));
            }
        }
        
        @Override
        public int hashCode(int index) {
            return null == array[index] ? 0 : array[index].hashCode();
        }
        
        @Override
        public String toString() {
            return Arrays.toString(array);
        }
        
    }
    
}
