/*
 * @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 lombok.experimental.UtilityClass;

import java.util.List;

/**
 * Utility class for dynamic allocating arrays of any type.
 */
@UtilityClass
public final class ArrayAllocator {

    /**
     * Creates a new array with provided component type.
     *
     * @param component component
     * @param size size
     * @param <A> array
     * @return array
     */
    public static <A> A forComponent(Class<?> component, int size) {
        return NativeArray.allocate(component, size).$array();
    }
    
    /**
     * 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}.
     *
     * @param prototype an array that will be used to type the new one.
     * @param length the length of the new array.
     * @param <A> array
     * @return a new array of given type and length.
     */
    public static <A> A forPrototype(A prototype, int length) {
        return NativeArray.wrap(prototype).allocate(length).$array();
    }

    /**
     * Creates a new array copying values from given list.
     *
     * This method returns a new array with length identical as source list.
     * Type of returned array must be passed as first argument. Type can be array of primitives.
     * Primitive values are correctly copied from boxed elements from list by unboxing.
     *
     * @param type type of array (not type of component!)
     * @param source source list, with elements assignable to array
     * @param <A> array type
     * @return new array
     * @throws ClassCastException if any element can't be inserted into array
     */
    public static <A> A fromList(Class<A> type, List<?> source) throws ClassCastException {
        return NativeArrayAllocator.fromList(type, source).$array();
    }

    /**
     * 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()} instead.
     *
     * @param array an array.
     * @param length the new minimum length for this array.
     * @param <A> array
     * @return {@code array}, if it contains {@code length} entries or
     * more; otherwise, an array with {@code length} entries whose
     * first {@code array.length} entries are the same as those
     * of {@code array}.
     */
    public static <A> A ensureCapacity(A array, int length) {
        return NativeArrayAllocator.ensureCapacity(NativeArray.wrap(array), length).$array();
    }
   
    /**
     * 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.
     * @param <A> array
     * @return {@code array}, if it can contain {@code length} entries
     * or more; otherwise, an array with {@code length} entries whose *
     * first {@code preserve} entries are the same as those * *
     * of {@code array}.
     */
    public static <A> A ensureCapacity(A array, int length, int preserve) {
        return NativeArrayAllocator.ensureCapacity(NativeArray.wrap(array), length, preserve).$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).
     * @param <A> array
     * @throws IllegalArgumentException if {@code length} is negative.
     * @throws ArrayIndexOutOfBoundsException if {@code offset} is negative
     * or {@code offset}+{@code length} is greater than the array
     * length.
     */
    public static <A> void ensureOffsetLength(A array, int offset, int length) {
        NativeArrayAllocator.ensureOffsetLength(NativeArray.wrap(array), offset, length);
    }
    
    /**
     * 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).
     * @param <A> array
     * @throws IllegalArgumentException if {@code from} is greater * *
     * than {@code to}.
     * @throws ArrayIndexOutOfBoundsException if {@code from} * *
     * or {@code to} are greater than the array length or negative.
     */
    public static <A> void ensureFromTo(A array, int begin, int end) {
        NativeArrayAllocator.ensureFromTo(NativeArray.wrap(array), begin, end);
    }

    /**
     * Reallocates array if size is different than current array.length
     * Copies all old elements from input. Returns new allocated array.
     *
     * @param array array
     * @param size size
     * @param <A> array
     * @return array
     */
    public static <A> A resize(A array, int size) {
        return NativeArrayAllocator.resize(NativeArray.wrap(array), size).$array();
    }
    
    /**
     * 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()} instead.
     *
     * @param array an array.
     * @param length the new minimum length for this array.
     * @param <A> array
     * @return {@code array}, if it can contain {@code length}
     * entries; otherwise, an array with
     * max({@code length},{@code array.length}/&phi;) entries whose
     * first {@code array.length} entries are the same as those * *
     * of {@code array}.
     *
     */
    public static <A> A grow(A array, int length) {
        return NativeArrayAllocator.grow(NativeArray.wrap(array), length).$array();
    }
    
    /**
     * 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()} 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.
     * @param <A> array
     * @return {@code array}, if it can contain {@code length}
     * entries; otherwise, an array with
     * max({@code length},{@code array.length}/&phi;) entries whose
     * first {@code preserve} entries are the same as those
     * of {@code array}.
     *
     */
    public static <A> A grow(A array, int length, int preserve) {
        return NativeArrayAllocator.grow(NativeArray.wrap(array), length, preserve).$array();
    }
    
    /**
     * Trims the given array to the given length.
     *
     * @param array an array.
     * @param length the new maximum length for the array.
     * @param <A> array
     * @return {@code array}, if it contains {@code length} entries or
     * less; otherwise, an array with {@code length} entries whose entries
     * are the same as the first {@code length} entries * *
     * of {@code array}.
     *
     */
    public static <A> A trim(A array, int length) {
        return NativeArrayAllocator.trim(NativeArray.wrap(array), length).$array();
    }
    
}
