/*
 *  @author Ranides Atterwim <ranides@gmail.com>
 *  @copyright Ranides Atterwim
 *  @license WTFPL
 *  @url http://ranides.net/projects/assira
 */

package net.ranides.assira.collection.list;

import java.util.AbstractList;
import java.util.RandomAccess;
import net.ranides.assira.collection.ArrayUtils;
import net.ranides.assira.reflection.inspect.ArrayInspector;

/**
 * Lista odwzorowująca tablicę elementów typu prostego (int, float, etc)
 * dokonując jedynie po drodze koniecznego autoboxingu.
 *
 * <p>
 * Utworzona lista posiada maksymalną pojemność wyliczoną na podstawie długości
 * prezentowanej tablicy (patrz: {@link #capacity() capacity}). Rozmiar listy nie ma
 * bezpośredniego związku z długością tablicy i może mieć dowolną wartość z zakresu
 * [0;capacity]. Rozmiar listy może być modyfikowany: wszystkie operacje usuwania
 * i wstawiania są obsługiwane tak długo, jak długo nie zostanie przekroczony
 * dozwolony zakres.
 * </p>
 *
 * <p>
 * Standardowe kolekcje nie mogą przechowywać typów prostych, co oznacza, że np
 * ArrayList nie przechowuje elementów typu int ale <b>obiekty</b> typu Integer.
 * Powoduje to duży narzut pamięciowy, który może być nieakceptowalny dla bardzo
 * dużych list. Dodatkowo niemożliwa jest wydajna współpraca metod oczekujących
 * {@code List<Integer>} oraz tablic {@code int[]}. W celu wymiany danych konieczna
 * jest nowa alokacja pamięci i skopiowanie wszystkich danych do nowego formatu
 * (rzutowanie {@code Integer[] <-> int[]} jest niemożliwe).
 * </p><p>
 * JIT/HotSpot powinien sobie bez trudu poradzić z eliminacją operacji
 * box'ujących - zwłaszcza biorąc pod uwagę wewnętrzną implementację
 * {@code NativeArrayList}. Jest niemal pewne, że tradycyjnych pętli kopiujących
 * optymalizator nie może usunąć wcale.
 * </p>
 *
 * @see ArrayUtils#wrap(Object)
 * @see ArrayUtils#wrap(Class, Object)
 *
 * @param <T>
 * @author ranides
 */
@SuppressWarnings("unchecked")
public class NativeArrayList<T> extends AbstractList<T> implements RandomAccess {

    private final ArrayInspector accessor;
    private final Object array;
    private final int capacity;
    private int size;

    /**
     * Tworzy nową listę odwierciedlającą tablicę.
     * Modyfikacja listy zmienia zawartość tablicy, i vice versa.
     * Rozmiar listy jest równy rozmiarowi podanej tablicy.
     * @param array
     * @see ArrayUtils#wrap(Object)
     */
    public NativeArrayList(Object array) {
        checkType(array.getClass());
        this.array = array;
        this.accessor = ArrayInspector.forType(array.getClass());
        this.capacity = accessor.length(array);
        this.size = capacity;
    }

    /**
     * Tworzy nową listę odwierciedlającą tablicę.
     * Modyfikacja listy zmienia zawartość tablicy, i vice versa.
     * Początkowy rozmiar listy jest podawany jako argument.
     * Do listy mogą być wstawiane nowe elementy tak długo, jak długo lista
     * nie przekroczy maksymalnej pojemności tablicy.
     * @param array
     * @param size początkowy rozmiar zwracany przez listę
     */
    public NativeArrayList(Object array, int size) {
        this(array);
        this.size = size;
    }

    /**
     * Tworzy nową listę odwierciedlającą tablicę.
     * Modyfikacja listy zmienia zawartość tablicy, i vice versa.
     * <p>
     * Podana tablica jest "transparentnie" konwertowana na wskazany typ, a dopiero
     * wynik konwersji jest używany przez kolekcję. Przykładowo traktowanie
     * tablicy {@code long[]} jako tablicy {@code byte[]} umożliwia utworzenie
     * listy typu {@code List<Byte>}. Konwersja jest wykonywana natywnie przez system hostujący (a nie przez VM!). Przekłada się to przede wszystkim na względnie większą wydajność kosztem nieprzenośności kodu ze względu na endianess.
     * </p>
     * <p>
     * Uwaga! Rzutowanie między typami o różnych rozmiarach powoduje, że lista ma
     * {@code capacity} różne niż {@code array.length}
     * </p>
     *
     * @param clazz
     * @param array
     *
     * @see ArrayUtils#wrap(Class, Object)
     */
    public NativeArrayList(Class<?> clazz, Object array) {
        checkType(clazz);
        checkType(array.getClass());
        this.array = array;
        this.accessor = ArrayInspector.forType(clazz, array.getClass());
        this.capacity =  accessor.length(array);
        this.size = capacity;
    }

    /**
     * Tworzy nową listę odwierciedlającą tablicę.
     * Modyfikacja listy zmienia zawartość tablicy, i vice versa.
     * Początkowy rozmiar listy jest podawany jako argument.
     * Do listy mogą być wstawiane nowe elementy tak długo, jak długo lista
     * nie przekroczy maksymalnej pojemności tablicy.
     *
     * <p>
     * Podana tablica jest "transparentnie" konwertowana na wskazany typ, a dopiero
     * wynik konwersji jest używany przez kolekcję. Przykładowo traktowanie
     * tablicy {@code long[]} jako tablicy {@code byte[]} umożliwia utworzenie
     * listy typu {@code List<Byte>}. Konwersja jest wykonywana natywnie przez system hostujący (a nie przez VM!). Przekłada się to przede wszystkim na względnie większą wydajność kosztem nieprzenośności kodu ze względu na endianess.
     * </p>
     * <p>
     * Uwaga! Rzutowanie między typami o różnych rozmiarach powoduje, że lista ma
     * {@code capacity} różne niż {@code array.length}
     * </p>
     * @param clazz
     * @param array
     * @param size początkowy rozmiar zwracany przez listę
     */
    public NativeArrayList(Class<?> clazz, Object array, int size) {
        this(clazz, array);
        this.size = size;
    }

    /**
     * Zwraca maksymalny dopuszczalny rozmiar listy.
     * @return
     */
    public int capacity() {
        return capacity;
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public T get(int index) {
        checkBounds(index);
        return (T) accessor.get(array, index);
    }

    @Override
    public T set(int index, T element) {
        checkCapacity(index);
        checkBounds(index);
        return (T) accessor.change(array, index, element);
    }


    @Override
    public void add(int index, T value) {
        checkCapacity(index);
        if(index > size) {
            throw new IndexOutOfBoundsException(index +" out of bound " + size);
        }
        System.arraycopy(array, index, array, index + 1, size - index);
        size++;
        accessor.put(array, index, value);
    }

    @Override
    public T remove(int index) {
        checkBounds(index);
        T prev = (T)accessor.get(array, index);
        int shift = size-index-1;
        if (shift > 0) {
            System.arraycopy(array, index+1, array, index, shift);
        }
        size--;
        return prev;
    }

    /**
     * Zwraca typ elementów przechowywanych przez oryginalną tablicę.
     * @return
     */
    public Class<?> component() {
        return array.getClass().getComponentType();
    }

    /**
     * Zwraca oryginalną tablicę. Ponieważ jest to tablica typów prostych, to
     * niemożliwe jest zwrócenie typu generycznego - wywołujący metodę samodzielnie
     * musi dokonać odpowiedniego rzutowania.
     * @return
     */
    public Object delegator() {
        return array;
    }

    private void checkCapacity(int index) {
        if(index >= capacity) {
            throw new IndexOutOfBoundsException(index + " out of maximal capacity " + capacity);
        }
    }

    private void checkBounds(int index) {
        if(index >= size || index<0) {
            throw new IndexOutOfBoundsException(index +" out of bound " + size);
        }
    }

    private void checkType(Class<?> clazz) {
        if (!clazz.isArray()) {
            throw new IllegalArgumentException(clazz + " is not an array");
        }
    }


}