/*
 * @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.ArrayList;

public final class NativeArrayAllocator {
    
    /**
     * This is a safe value used by {@link ArrayList} (as of Java 7) to avoid
     * throwing {@link OutOfMemoryError} on some JVMs. We adopt the same value.
     */
    private static final int MAX_SIZE = Integer.MAX_VALUE - 8;
    
    private NativeArrayAllocator() {
        // utility class
    }
    
    public static NativeArray forComponent(Class<?> component, int size) {
        return NativeArray.allocate(component, size);
    }
    
    /**
     * Creates a new array using a the given one as prototype.
     *
     * <P>This method returns a new array of the given length whose element are
     * of the same class as of those of <code>prototype</code>. 
     * In case of an empty array, it tries to return {@link #EMPTY_ARRAY}, if possible.
     *
     * @param prototype an array that will be used to type the new one.
     * @param length the length of the new array.
     * @return a new array of given type and length.
     */
    public static NativeArray forPrototype(NativeArray prototype, int length) {
        return prototype.allocate(length);
    }
    
    /**
     * Ensures that an array can contain the given number of entries.
     *
     * <P>If you cannot foresee whether this array will need again to be
     * enlarged, you should probably use
     * <code>grow()</code> instead.
     *
     * @param array an array.
     * @param length the new minimum length for this array.
     * @return <code>array</code>, if it contains <code>length</code> entries or
     * more; otherwise, an array with <code>length</code> entries whose * *
     * first <code>array.length</code> entries are the same as those * *
     * of <code>array</code>.
     */
    public static NativeArray ensureCapacity(NativeArray array, int length) {
        return ensureCapacity(array, length, -1);
    }
    
    /**
     * Ensures that an array can contain the given number of entries, preserving
     * just a part of the array.
     *
     * @param array an array.
     * @param length the new minimum length for this array.
     * @param preserve the number of elements of the array that must be
     * preserved in case a new allocation is necessary.
     * @return <code>array</code>, if it can contain <code>length</code> entries
     * or more; otherwise, an array with <code>length</code> entries whose *
     * first <code>preserve</code> entries are the same as those * *
     * of <code>array</code>.
     */
    public static NativeArray ensureCapacity(NativeArray array, int length, int preserve) {
        int n = array.size();
        if(preserve < 0) {
            preserve = n;
        }
        if (length > n) {
            NativeArray target = array.allocate(length);
            NativeArrayUtils.copy(array, 0, target, 0, preserve);
            return target;
        }
        return array;
    }
    
    /**
     * Ensures that a range given by an offset and a length fits an array.
     *
     * <P>This method may be used whenever an array range check is needed.
     *
     * @param array an array.
     * @param offset a start index.
     * @param length a length (the number of elements in the range).
     * @throws IllegalArgumentException if <code>length</code> is negative.
     * @throws ArrayIndexOutOfBoundsException if <code>offset</code> is negative
     * or <code>offset</code>+<code>length</code> is greater than the array
     * length.
     */
    public static void ensureOffsetLength(NativeArray array, int offset, int length) {
        ensureOffsetLength(array.size(), offset, length);
    }
    
    /**
     * Ensures that a range given by an offset and a length fits an array of
     * given length.
     *
     * <P>This method may be used whenever an array range check is needed.
     *
     * @param arrayLength an array length.
     * @param offset a start index for the fragment
     * @param length a length (the number of elements in the fragment).
     * @throws IllegalArgumentException if <code>length</code> is negative.
     * @throws ArrayIndexOutOfBoundsException if <code>offset</code> is negative
     * or <code>offset</code>+<code>length</code> is greater *      * than <code>arrayLength</code>.
     */
    public static void ensureOffsetLength(int arrayLength, int offset, int length) {
        if (offset < 0) {
            throw new ArrayIndexOutOfBoundsException("Offset (" + offset + ") is negative");
        }
        if (length < 0) {
            throw new IllegalArgumentException("Length (" + length + ") is negative");
        }
        if (offset + length > arrayLength) {
            throw new ArrayIndexOutOfBoundsException("Last index (" + (offset + length) + ") is greater than array length (" + arrayLength + ")");
        }
    }

    /**
     * Ensures that a range given by its first (inclusive) and last (exclusive)
     * elements fits an array.
     *
     * <P>This method may be used whenever an array range check is needed.
     *
     * @param array an array.
     * @param begin a start index (inclusive).
     * @param end an end index (exclusive).
     * @throws IllegalArgumentException if <code>from</code> is greater * *
     * than <code>to</code>.
     * @throws ArrayIndexOutOfBoundsException if <code>from</code> * *
     * or <code>to</code> are greater than the array length or negative.
     */
    public static void ensureFromTo(NativeArray array, int begin, int end) {
        ensureFromTo(array.size(), begin, end);
    }
    
    /**
     * Ensures that a range given by its first (inclusive) and last (exclusive)
     * elements fits an array of given length.
     *
     * <P>This method may be used whenever an array range check is needed.
     *
     * @param arrayLength an array length.
     * @param begin a start index (inclusive).
     * @param end an end index (inclusive).
     * @throws IllegalArgumentException if <code>from</code> is greater *      * than <code>to</code>.
     * @throws ArrayIndexOutOfBoundsException if <code>from</code> *      * or <code>to</code> are greater than <code>arrayLength</code> or
     * negative.
     */
    public static void ensureFromTo(int arrayLength, int begin, int end) {
        if (begin < 0) {
            throw new ArrayIndexOutOfBoundsException("Start index (" + begin + ") is negative");
        }
        if (begin > end) {
            throw new IllegalArgumentException("Start index (" + begin + ") is greater than end index (" + end + ")");
        }
        if (end > arrayLength) {
            throw new ArrayIndexOutOfBoundsException("End index (" + end + ") is greater than array length (" + arrayLength + ")");
        }
    }
    
    public static NativeArray resize(NativeArray array, int size) {
		int n = array.size();
		if(size == n) {
			return array;
		}
        return NativeArrayUtils.copy(array, 0, array.allocate(size), 0, Math.min(size, n));
    }
 
    /**
     * Grows the given array to the maximum between the given length and the
     * current length multiplied by two, provided that the given length is
     * larger than the current length.
     *
     * <P>If you want complete control on the array growth, you should probably
     * use
     * <code>ensureCapacity()</code> instead.
     *
     * @param array an array.
     * @param length the new minimum length for this array.
     * @return <code>array</code>, if it can contain <code>length</code>
     * entries; otherwise, an array with
     * max(<code>length</code>,<code>array.length</code>/&phi;) entries whose
     * first <code>array.length</code> entries are the same as those * *
     * of <code>array</code>.
     *
     */
    public static NativeArray grow(NativeArray array, int length) {
        return grow(array, length, -1);
    }
    
    /**
     * Grows the given array to the maximum between the given length and the
     * current length multiplied by two, provided that the given length is
     * larger than the current length, preserving just a part of the array.
     *
     * <P>If you want complete control on the array growth, you should probably
     * use
     * <code>ensureCapacity()</code> instead.
     *
     * @param array an array.
     * @param length the new minimum length for this array.
     * @param preserve the number of elements of the array that must be
     * preserved in case a new allocation is necessary.
     * @return <code>array</code>, if it can contain <code>length</code>
     * entries; otherwise, an array with
     * max(<code>length</code>,<code>array.length</code>/&phi;) entries whose
     * first <code>preserve</code> entries are the same as those
     * of <code>array</code>.
     *
     */
    public static NativeArray grow(NativeArray array, int length, int preserve) {
        int n = array.size();
        if(preserve < 0) {
            preserve = n;
        }
        if (length > n) {
            int newLength = (int) Math.min(Math.max(2L * n, length), MAX_SIZE);
            NativeArray target = array.allocate(newLength);
            NativeArrayUtils.copy(array, 0, target, 0, preserve);
            return target;
        }
        return array;
    }

    /**
     * Trims the given array to the given length.
     *
     * @param array an array.
     * @param length the new maximum length for the array.
     * @return <code>array</code>, if it contains <code>length</code> entries or
     * less; otherwise, an array with <code>length</code> entries whose entries
     * are the same as the first <code>length</code> entries * *
     * of <code>array</code>.
     *
     */
    public static NativeArray trim(NativeArray array, int length) {
        if (length >= array.size()) {
            return array;
        }
        NativeArray target = array.allocate(length);
        NativeArrayUtils.copy(array, 0, target, 0, length);
        return target;
    }

    
}
