ArraySupport.java
package church.lang;

import java.lang.reflect.Array;
import java.util.function.BiFunction;
import java.util.function.IntFunction;
import java.util.stream.Stream;

import church.util.CollectionsSupport;
import church.util.CollectionsSupport.EquivalenceRelation;
import church.util.CollectionsSupport.HashingFunction;

@SuppressWarnings("unused")
public class ArraySupport {
    /**
     * A specialised entry point is required here as {@link Array#newInstance(Class, int)} is overloaded.
     */
    @SuppressWarnings("unchecked")
    public static <T> T[] newArray(Class<T> clarse, int length) {
        return (T[]) Array.newInstance(clarse, length);
    }

    @SuppressWarnings("unchecked")
    public static <T> T[] createArray0(Class<T> clarse, int length, java.util.function.IntFunction<T> generator) {
        T[] result = (T[]) Array.newInstance(clarse, length);
        for (int i = 0; i < result.length; i++) {
            result[i] = generator.apply(i);
        }
        return result;
    }

    @SafeVarargs
    public static <T> T[] array0(T... elements) {
        return elements;
    }

/* 
    public static <T> T[] emptyArray0(Class<T> clarse) { 
        return (T[]) Array.newInstance(clarse, 0); 
    } 
*/

    /**
     * A specialised entry point is required here as the signature of {@link Stream#toArray(IntFunction)} is:
     * <pre>{@code
     * <A> A[] toArray(IntFunction<A[]> generator)
     * }</pre>
     * which, when operating on Stream&#60;T&#62;, has two type parameters instead of one. The above signature specifies
     * that the array will have the same type as the function that created it, but not that the values arriving from
     * stream must also be of this type -- which they must be to avoid an ArrayStoreException being raised at run-time.
     * Church's type inference also requires a single type parameter to work correctly here.
     * <p>
     * The signature below tightens the specification to align all three type parameters; those of the stream, the array
     * constructor and the returned array.
     */
    public static <T> T[] toArray(Stream<T> stream, IntFunction<T[]> constructor) {
        return stream.toArray(constructor);
    }

    public static <T> int length0(T[] a) {
        return a.length;
    }

    public static <T> T get0(T[] a, int i) {
        return a[i];
    }

    @SuppressWarnings("unchecked")
    private static <T> Class<T> getComponentType(T[] a) {
        return (Class<T>) a.getClass().getComponentType();
    }

    @SuppressWarnings("unchecked")
    private static <T> T[] newInstance(Class<T> type, int length) {
        return (T[]) Array.newInstance(type, length);
    }

    public static <T> T[] append0(T[] a, T[] b) {
        T[] result = newInstance(getComponentType(a), a.length + b.length);
        java.lang.System.arraycopy(a, 0, result, 0, a.length);
        java.lang.System.arraycopy(b, 0, result, a.length, b.length);
        return result;
    }

    public static <T> boolean equal0(EquivalenceRelation<T> equivalent, T[] a, T[] b) {
        if (a == b) {
            return true;
        }
        if (a.length != b.length) {
            return false;
        }
        int length = a.length;
        for (int i = 0; i < length; i++) {
            if (!equivalent.equal(a[i], b[i])) {
                return false;
            }
        }
        return true;
    }

    // See Arrays.hashCode(Object[]);
    public static <T> int hashCode0(HashingFunction<T> hashingFunction, T[] a) {
        int result = 1;
        for (T element : a) {
            result = 31 * result + hashingFunction.hashCode(element);
        }
        return result;
    }

    public static <T> CollectionsSupport.LeftFold<T[], T> leftFold() {
        return new CollectionsSupport.LeftFold<T[], T>() {
            @Override
            public <R> R reduce(BiFunction<R, T, R> accumulator, R initialValue, T[] array) {
                R result = initialValue;
                for (T element : array) {
                    result = accumulator.apply(result, element);
                }
                return result;
            }
        };
    }

    public static <T> CollectionsSupport.RightFold<T[], T> rightFold() {
        return new CollectionsSupport.RightFold<T[], T>() {
            @Override
            public <R> R reduce(BiFunction<T, R, R> accumulator, R initialValue, T[] array) {
                R result = initialValue;
                for (int i = array.length - 1; i >= 0; i--) {
                    T element = array[i];
                    result = accumulator.apply(element, result);
                }
                return result;
            }
        };
    }
}