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

package net.ranides.assira.collection;

import net.ranides.assira.reflection.inspect.ArrayInspector;
import java.lang.reflect.Array;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.ListIterator;
import net.ranides.assira.annotations.Meta;
import net.ranides.assira.collection.list.AbstractListIterator;
import net.ranides.assira.collection.list.NativeArrayList;
import net.ranides.assira.generic.CoFunction;
import net.ranides.assira.generic.Function;
import net.ranides.assira.generic.ValueUtils;
import net.ranides.assira.math.HashHelper;
import net.ranides.assira.reflection.util.ClassTraits;
import net.ranides.assira.reflection.util.ClassUtils;

/**
 * Operacje na tablicach.
 * @author ranides
 */
@SuppressWarnings({"PMD.ShortVar", "PMD.AvoidArrayLoops", "PMD.TooManyPublicMethods", "PMD.ExcessiveClassLength"})
public final class ArrayUtils {

    private ArrayUtils() { }

    /**
     * Pobiera index-ty element z tablicy, lub zwraca wartość domyślną, jeśli
     * tablica ma mniejszy rozmiar niż konieczne.
     *
     * <div class="message-note">{@code null} safe method</div>
     * @param <T>
     * @param values
     * @param index
     * @param ddefault wartość domyślna
     * @return
     */
    public static <T> T get(T[] values, int index, T ddefault) {
        if(null == values) { return ddefault; }
        return index < values.length ? values[index] : ddefault;
    }

    /**
     * Pobiera index-ty element z tablicy, lub zwraca null, jeśli
     * tablica ma mniejszy rozmiar niż konieczne
     *
     * <div class="message-note">{@code null} safe method</div>
     * @param <T>
     * @param values
     * @param index
     * @return
     */
    public static <T> T get(T[] values, int index) {
        return get(values, index, null);
    }

    /**
     * Wyszukuje i zwraca pierwszy obiekt o klasie T, lub null, jeśli takiej wartości brak
     *
     * <div class="message-note">{@code null} safe method</div>
     * @param <K>
     * @param <T>
     * @param values tablica, w której obiekt klasy jest szukany
     * @param clazz szukana klasa
     * @return
     */
    public static <K, T extends K> T first(K[] values, Class<T> clazz) {
        if(null == values) { return null; }
        for (int i=0, n=values.length; i<n; i++) {
            if( clazz.isInstance(values[i]) ) { return clazz.cast(values[i]); }
        }
        return null;
    }

    /**
     * Zwraca pierwszy obiekt w tablicy, lub null, jeśli tablica jest pusta
     *
     * <div class="message-note">{@code null} safe method</div>
     * @param <T>
     * @param values tablica, w której obiekt klasy jest szukany
     * @return
     */
    public static <T> T first(T[] values) {
        if(null == values || 0==values.length) {
            return null;
        }
        return values[0];
    }

    /**
     * Wyszukuje i zwraca ostatni obiekt o klasie T, lub null, jeśli takiej wartości brak
     *
     * <div class="message-note">{@code null} safe method</div>
     * @param <T>
     * @param values tablica, w której obiekt klasy jest szukany
     * @param clazz szukana klasa
     * @return
     */
    public static <T> T last(Object[] values, Class<T> clazz) {
        if(null == values) {
            return null;
        }
        for (int i=values.length-1; i >= 0; i--) {
            if( clazz.isInstance(values[i]) ) { return clazz.cast(values[i]); }
        }
        return null;
    }

    /**
     * Zwraca ostatni obiekt w tablicy, lub null, jeśli tablica jest pusta
     *
     * <div class="message-note">{@code null} safe method</div>
     * @param <T>
     * @param values tablica, w której obiekt klasy jest szukany
     * @return
     */
    public static <T> T last(T[] values) {
        if(null == values || 0==values.length) {
            return null;
        }
        return values[values.length - 1];
    }

    /**
     * Zwraca rozmiar podanej tablicy. Jeśli przekazany obiekt nie jest tablicą
     * (np int[]) to rzuca wyjątek.
     * @param values
     * @return
     */
    public static int size(Object values) {
        if(null == values) {
            return 0;
        }
        if(!values.getClass().isArray()) {
            throw new IllegalArgumentException("type is not an array: " + values.getClass());
        }
        return Array.getLength(values);
    }

    /**
     * Sprawdza czy tablica jest pusta.
     *
     * <div class="message-note">{@code null} safe method</div>
     * @param <T>
     * @param values
     * @return
     */
    public static <T> boolean isEmpty(Object values) {
        return 0 == size(values);
    }

    /**
     * Przycina tablicę do podanego rozmiaru. To znaczy, jeśli tablica ma rozmiar
     * większy od {@code size}, to tworzona jest nowa tablica identycznego typu
     * ale o rozmiarze {@code size}, zawartość jest przepisywana, i ta nowa kopia
     * jest zwracana jako wynik. Jeśli tablica ma rozmiar mniejszy od {@code size},
     * to nic nie jest robione - funkcja zwraca {@code array}.
     * @param <ArrayT>
     * @param array
     * @param size
     * @return
     */
    @SuppressWarnings({"SuspiciousSystemArraycopy"})
    public static <ArrayT> ArrayT clip(ArrayT array, int size) {
        if( size(array) <= size ) {
            return array;
        }
        ArrayT result= make(array.getClass(), size);
        System.arraycopy(array, 0, result, 0, size);
        return result;
    }
    
    /**
     * Tworzy nową tablicę o podanym rozmiarze, kopiując starą zawartość. Rozmiar
     * nowej tablicy może być mniejszy albo większy od źródłowej.
     * @param <ArrayT>
     * @param array
     * @param size
     * @return
     */
    @SuppressWarnings({"SuspiciousSystemArraycopy"})
    public static <ArrayT> ArrayT resize(ArrayT array, int size) {
        int osize = size(array);
        ArrayT result = make(array.getClass(), size);
        System.arraycopy(array, 0, result, 0, Math.min(size, osize));
        return result;
    }
    
    /**
     * @param <ArrayT>
     * @param array
     * @param begin
     * @param end
     * @return 
     */
    @SuppressWarnings({"SuspiciousSystemArraycopy", "unchecked"})
    public static <ArrayT> ArrayT slice(ArrayT array, int begin, int end) {
        int size = end - begin;
        ArrayT result = make(array.getClass(), size);
        System.arraycopy(array, begin, result, 0, size);
        return result;
    }

    /**
     * Tworzy listę odzwierciedlającą podaną tablicę. Modyfikacja listy zmienia zawartość tablicy, i vice versa.
     * Jeśli podana tablica elementów jest pusta, zwraca pustą listę.
     * Jeśli podana tablica jest równa NULL, zwraca pustą listę.
     *
     * <div class="message-note">{@code null} safe method</div>
     * @param <T>
     * @param values
     * @return
     */
    public static <T> List<T> asList(T... values) {
        return (null==values) ? Collections.<T>emptyList() : Arrays.asList(values);
    }

    /**
     * Tworzy listę odzwierciedlającą podaną tablicę.
     * Modyfikacja listy zmienia zawartość tablicy, i vice versa.
     * Jeśli podana tablica elementów jest pusta, zwraca pustą listę.
     * Jeśli podana tablica jest równa NULL, zwraca pustą listę.
     *
     * <div class="message-note">{@code null} safe method</div>
     * @param <T>
     * @param values
     * @return
     */
    public static <T> List<T> wrap(T[] values) {
        return (null==values) ? Collections.<T>emptyList() : Arrays.asList(values);
    }

    /**
     * Tworzy listę odzwierciedlającą podaną tablicę.
     * Modyfikacja listy zmienia zawartość tablicy, i vice versa.
     * Jeśli podana tablica elementów jest pusta, zwraca pustą listę.
     * Jeśli podana tablica jest równa NULL, zwraca pustą listę.
     * Jeśli jako argument podano obiekt implementujący {@link List}, to żadna konwersja nie jest przeprowadzana.
     *
     * <div class="message-note">{@code null} safe method</div>
     * @param <T>
     * @param array tablica typu prostego, np int[]
     * @return
     */
    @Meta.Unsafe
    @SuppressWarnings("unchecked")
    public static <T> List<T> wrap(Object array) {
        if(array instanceof List) {
            return (List<T>)array;
        }
        if(null==array) {
            return Collections.<T>emptyList();
        }
        if( ClassTraits.isPrimitiveArray(array.getClass()) ) {
            return new NativeArrayList<>(array);
        } else {
            return Arrays.asList((T[])array);
        }
    }

    /**
     * Tworzy listę odzwierciedlającą podaną tablicę.
     * Modyfikacja listy zmienia zawartość tablicy, i vice versa.
     * Jeśli podana tablica elementów jest pusta, zwraca pustą listę.
     * Jeśli podana tablica jest równa NULL, zwraca pustą listę.
     * Jeśli jako argument podano obiekt implementujący {@link List}, to żadna konwersja nie jest przeprowadzana.
     *
     * <p>Funkcja umożliwia 'transparentne' rzutowanie typów prostych, np pozwala
     * traktować tablicę long[] jako listę List&lt;Byte&gt;. 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>
     *
     * <div class="message-note">{@code null} safe method</div>
     * @param <T>
     * @param clazz typ tablicy, np int[].class
     * @param array
     * @return
     */
    @Meta.Unsafe
    @SuppressWarnings("unchecked")
    public static <T> List<T> wrap(Class<?> clazz, Object array) {
        if(array instanceof List) {
            return (List<T>)array;
        }
        return (null==array) ? Collections.<T>emptyList() : new NativeArrayList<T>(clazz, array);
    }

    /**
     * Konwertuje listę elementów na tablicę o podanym typie, dokonując po drodze
     * odpowiednich rzutowań i unbox'owań, jeśli są konieczne. Zwrócona tablica jest
     * niezależna od listy.
     * @param <Auto>
     * @param type
     * @param source
     * @return
     * @throws ClassCastException jeśli konwersja elementów jest niemożliwa do przeprowadzenia
     */
    @SuppressWarnings({"PMD", "unchecked"})
    public static <Auto> Auto toArray(Class<Auto> type, List<?> source) throws ClassCastException {
        ArrayInspector access = ArrayInspector.forType(type);
        int size = source.size();
        Object target = access.create(size);
        int index = 0;
        for(Object item : source) { access.put(target, index++, item);  }
        return (Auto)target;
    }
    
    /**
     * Konwertuje zbiór elementów na tablicę o podanym typie, dokonując po drodze
     * odpowiednich rzutowań i unbox'owań, jeśli są konieczne. Zwrócona tablica jest
     * niezależna od kolekcji.
     * @param <Auto>
     * @param type
     * @param source
     * @return
     * @throws ClassCastException jeśli konwersja elementów jest niemożliwa do przeprowadzenia
     */
    @SuppressWarnings({"PMD", "unchecked"})
    public static <Auto> Auto toArray(Class<Auto> type, Iterable<?> source) throws ClassCastException {
        return (Auto)toArray(type, ListUtils.asList(source));
    }

    /**
     * Sortuje podaną tablicę ustalając porządek na podstawie porówań elementów z drugiej tablicy z kluczami.
     * <p>Uwaga! Sortowanie nie odbywa się 'in place', złożoność pamięciowa wynosi O(N).
     * Na standardowej VM około 16N bajtów.</p>
     * @param <K>
     * @param <V>
     * @param values
     * @param keys
     */
    @SuppressWarnings("MismatchedReadAndWriteOfArray")
    public static <K extends Comparable<K>, V> void sort(V[] values, K[] keys) {
        final Pair[] table = new Pair[keys.length];
        for(int i=0; i<table.length; i++) {
            table[i] = new Pair<>(keys[i], values[i]);
        }
        Arrays.sort(table);


        final Object[] ovalues = values;
        final Object[] okeys = keys;
        for(int i=0; i<table.length; i++) {
            ovalues[i] = table[i].value;
            okeys[i] = table[i].key;
        }
    }

    /**
     * Zobacz: {@link #sort(V[], K[]) sort}
     * @param <K>
     * @param <V>
     * @param values
     * @param keys
     */
    public static <K,V> void sort(V[] values, int[] keys) {
        final IntPair[] table = new IntPair[keys.length];
        for(int i=0; i<table.length; i++) { table[i] = new IntPair(keys[i], values[i]); }
        Arrays.sort(table);

        @SuppressWarnings("MismatchedReadAndWriteOfArray")
        final Object[] ovalues = values;
        for(int i=0; i<table.length; i++) {
            ovalues[i] = table[i].value;
            keys[i] = table[i].key;
        }
    }

/* ************************************************************************** */

    static class Pair<K extends Comparable<K>> implements Comparable<Pair<K>> {
        public final K key;
        public final Object value;

        public Pair(K key, Object value) {
            this.key = key;
            this.value = value;
        }
        @Override
        public int compareTo(Pair<K> value) {
            return key.compareTo(value.key);
        }

        @Override
        public int hashCode() {
            return HashHelper.hashValues(3, 97, key);
        }

        @Override
        @SuppressWarnings("unchecked")
        public boolean equals(Object obj) {
            if(obj instanceof Pair) {
                return compareTo((Pair<K>)obj) == 0;
            } else {
                return false;
            }
        }


    }

    static class IntPair implements Comparable<IntPair> {
        public final int key;
        public final Object value;

        public IntPair(int key, Object value) {
            this.key = key;
            this.value = value;
        }
        @Override
        public int compareTo(IntPair value) {
            return (key - value.key);
        }
        @Override
        public int hashCode() {
            return 97*key;
        }

        @Override
        public boolean equals(Object obj) {
            if(obj instanceof IntPair) {
                return compareTo((IntPair)obj) == 0;
            } else {
                return false;
            }
        }
    }

/* ************************************************************************** */

    /**
     * Tworzy tablicę o podanym typie oraz rozmiarze. Może tworzyć tablice typów
     * prostych. Podając jako typ {@code int.class} dostaniemy jako wynik tablicę
     * {@code int[]}. Dla obiektów tworzy tablice o dokładnym typie, a nie tablice
     * {@code Object[]}, to znaczy, że podając {@code String.class} otrzymamy
     * jako wynik {@code String[]}.
     * <p>
     * Metoda jest o tyle wygodna i jednocześnie niebezpieczna, że zwraca typ,
     * który musi być inferred przez kompilator. Czyli możemy napisać bez rzutowania:
     * <p>
     * <pre>
     * {@code int[] v1 = make(int.class, 6); // ok }
     * {@code int[] v2 = make(float.class, 6); // undetected error }
     * </pre>
     * @param <Auto>
     * @param type
     * @param size
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <Auto> Auto make(Class<?> type, int size) {
        return (Auto)ArrayInspector.forType(type).create(size);
    }

    /**
     * Tworzy tablicę o podanym typie oraz rozmiarze inicjalizując elementy
     * wartością {@code value}. Może tworzyć tablice typów
     * prostych. Podając jako typ {@code int.class} dostaniemy jako wynik tablicę
     * {@code int[]}. Dla obiektów tworzy tablice o dokładnym typie, a nie tablice
     * {@code Object[]}, to znaczy, że podając {@code String.class} otrzymamy
     * jako wynik {@code String[]}.
     * <p>
     * Metoda jest o tyle wygodna i jednocześnie niebezpieczna, że zwraca typ,
     * który musi być inferred przez kompilator. Czyli możemy napisać bez rzutowania:
     * <p>
     * <pre>
     * {@code int[] v1 = make(int.class, 6); // ok }
     * {@code int[] v2 = make(float.class, 6); // undetected error }
     * </pre>
     * @param <Auto>
     * @param type
     * @param size
     * @param value
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <Auto> Auto make(Class<?> type, int size, Object value) {
        ArrayInspector access = ArrayInspector.forType(type);
        Object content = access.create(size);
        if(size != 0 && value!=null) {
            for(int i=0; i<size; i++) { access.put(content, i, value); }
        }
        return (Auto)content;
    }

    /**
     * Tworzy tablicę o podanym rozmiarze inicjalizując elementy
     * wartością {@code value}. Typ tablicy jest dedukowany na podstawie typu
     * wartości.
     * <p>
     * Metoda preferuje tworzenie tablic typu wbudowanego, co oznacza,
     * że przekazując jako {@code value} zmienną typu {@code Integer} lub {@code int}
     * otrzymamy w obu przypadkach jako wynik {@code int[]}
     * </p><p>
     * Jeśli jako wartość zostanie przekazany {@code null}, to zwrócona zostanie
     * tablica typu {@code Object[]}, oczywiście o podanym rozmiarze.
     * </p><p>
     * Metoda jest o tyle wygodna i jednocześnie niebezpieczna, że zwraca typ,
     * który musi być inferred przez kompilator. Czyli możemy napisać bez rzutowania:
     * <p>
     * @param <Auto>
     * @param size
     * @param value
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <Auto> Auto make(int size, Object value) {
        if( value == null ) {
            return (Auto)new Object[size];
        }
        Class<?> type = ClassUtils.unbox(value.getClass());
        ArrayInspector access = ArrayInspector.forType(type);
        Object content = access.create(size);
        for(int i=0; i<size; i++) {
            access.put(content, i, value);
        }
        return (Auto)content;
    }

    /**
     * Tworzy nową tablicę o podanym rozmiarze, która może przechowywać obiekty
     * podanego typu. Metoda nie nadaje się do tworzenia tablic typu prostego.
     * Do utworzenia takich tablic użyj jednej z funkcji {@link #make}.
     * @param <T>
     * @param component
     * @param size
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T extends Object> T[] makeT(Class<T> component, int size) {
        return (T[])Array.newInstance(component, size);
    }

    /**
     * Tworzy nową tablicę o podanym rozmiarze, która może przechowywać obiekty
     * podanego typu, inicjalizując elementy podaną wartością. Metoda nie nadaje
     * się do tworzenia tablic typu prostego. Do utworzenia takich tablic użyj
     * jednej z funkcji {@link #make}.
     * @param <T>
     * @param component
     * @param size
     * @param value
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T extends Object> T[] makeT(Class<T> component, int size, T value) {
        T[] content = makeT(component, size);
        for(int i=0; i<size; i++) { content[i] = value; }
        return content;
    }

    /**
     * Tworzy nową tablicę o podanym rozmiarze, inicjalizując elementy podaną
     * wartością. Typ tablicy jest dedukowany z typu argumentu. Metoda nie nadaje
     * się do tworzenia tablic typu prostego. Do utworzenia takich tablic użyj
     * jednej z funkcji {@link #make}.
     * <p>
     * Uwaga! Jeśli {@code value} jest tak naprawdę klasą C (pochodną {@code T}),
     * to zostanie utworzona tablica typu {@code C[]}, a nie {@code T[]}.
     * </p>
     * @param <T>
     * @param size
     * @param value
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T[] makeT(int size, T value) {
        return makeT((Class<T>)value.getClass(), size, value);
    }



    /**
     * Zobacz: {@link #shift(Object[]) shift}
     * @param <Auto>
     * @param values
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <Auto> Auto shift(Object values) {
        if(values == null) {
            throw new EmptyCollectionException();
        }
        ArrayInspector access = ArrayInspector.forObject(values);
        int n = access.length(values);
        if(n == 0) {
            throw new EmptyCollectionException();
        }
        Object value = access.get(values, 0);
        for(int i=1; i<n; i++) {
            access.put(values, i-1, access.get(values, i));
        }
        return (Auto)value;
    }

    /**
     * Usuwa pierwszy element z tablicy i przesuwa wszystkie pozostałe wartości w lewo.
     * Uwaga! Ostatni element po operacji {@code shift} jest zduplikowany!
     * @param <T>
     * @param values
     * @return usunięta wartość
     */
    public static <T> T shift(T[] values) {
        if(values == null || values.length==0) { throw new EmptyCollectionException(); }
        T value = values[0];
        for(int i=1,n=values.length; i<n; i++) { values[i-1] = values[i]; }
        return value;
    }

    /**
     * Zobacz: {@link #unshift(Object[]) unshift}
     * @param <Auto>
     * @param values
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <Auto> Auto unshift(Object values) {
        if(values == null) {
            throw new EmptyCollectionException();
        }
        ArrayInspector access = ArrayInspector.forObject(values);
        int n = access.length(values);
        if(n == 0) {
            throw new EmptyCollectionException();
        }
        Object value = access.get(values, n-1);
        for(int i=n-1; i>0; i--) {
            access.put(values, i, access.get(values, i-1));
        }
        return (Auto)value;
    }

    /**
     * Usuwa ostatni element z tablicy i przesuwa wszystkie pozostałe wartości w prawo.
     * Uwaga! Pierwszy element po operacji {@code unshift} jest zduplikowany!
     * @param <T>
     * @param values
     * @return usunięta wartość
     */
    public static <T> T unshift(T[] values) {
        if(values == null || values.length==0) { throw new EmptyCollectionException(); }
        T value = values[values.length-1];
        for(int i=values.length-1; i>0; i--) { values[i] = values[i-1]; }
        return value;
    }

    /**
     * Zobacz: {@link #concat(Object[], Object[]) concat}
     * @param <ArrayT>
     * @param first
     * @param second
     * @return
     */
    @SuppressWarnings({"SuspiciousSystemArraycopy", "unchecked"})
    public static <ArrayT> ArrayT concat(ArrayT first, ArrayT second) {
        int length1 = size(first);
        int length2 = size(second);
        Object result = make(first.getClass(), length1 + length2);
        System.arraycopy(first, 0, result, 0, length1);
        System.arraycopy(second, 0, result, length1, length2);
        return (ArrayT)result;
    }

    /**
     * Łączy zawartość podanych tablic. Nowa tablica zawiera dokładnie te same
     * wartości, co tablice źródłowe. To znaczy: obiekty z tablic nie są kopiowane
     * za pomocą metody {@code clone} lub podobnej - przenoszone sdą referencje.
     * @param <T>
     * @param first
     * @param second
     * @return
     */
    public static <T> T[] concat(T[] first, T[] second) {
        @SuppressWarnings("unchecked")
        T[] result =  (T[])Array.newInstance(first.getClass().getComponentType(),  first.length + second.length);
        System.arraycopy(first, 0, result, 0, first.length);
        System.arraycopy(second, 0, result, first.length, second.length);
        return result;
    }
    
    /**
     * Zwraca iterator przeglądający elementy tablicy.
     * @param <T>
     * @param array tablica, po której porusza się iterator
     * @return
     */
    public static <T> ListIterator<T> iterator(final T[] array) {
        return iterator(array, array.length, 0);
    }

    /**
     * Zwraca iterator przeglądający elementy tablicy z określonego zakresu.
     * @param <T>
     * @param array tablica, po której porusza się iterator
     * @param size rozmiar przeglądanego zakresu
     * @param first początek przeglądanego zakresu
     * @return
     */
    public static <T> ListIterator<T> iterator(final T[] array, final int size, final int first) {
        return new AbstractListIterator<T>() {
            @Override
            protected void set(int index, T value) {
                array[first+index] = value;
            }
            @Override
            protected T get(int index) {
                return array[first+index];
            }
            @Override
            protected int size() {
                return size;
            }
            @Override
            protected void remove(int index) {
                throw new UnsupportedOperationException("Not supported by array.");
            }
            @Override
            protected void add(int index, T value) {
                throw new UnsupportedOperationException("Not supported by array.");
            }
        };
    }

    /**
     * <p>
     * Kopiuje zawartość tablicy obiektów do wskazanej tablicy docelowej, wykonując
     * odpowiedniego rzutowania dla każdego elementu osobno. Jeśli zawartość
     * nie może się zmieścić w tablicy docelowej, alokowana jest nowa tablica
     * o odpowiednim typie.
     * </p><p>
     * Metoda nie nadaje się do tworzenia tablic typu prostego. Głównym zastosowaniem
     * metody jest grupowe rzutowanie na konkretny typ {@code T}
     * </p>
     * @param <T>
     * @param target
     * @param source
     * @return {@code target} lub nowa zaalokowana tablica, jeśli zawartość nie
     * mogła się zmieścić w tablicy podanej jako docelowa.
     */
    @SuppressWarnings("unchecked")
    public static <T> T[] arraycopy(T[] target, Object[] source) {
        Class<T> component = (Class<T>)target.getClass().getComponentType();
        T[] result = target.length >= source.length ? target : makeT(component, source.length);

        for(int i=0, n=source.length; i<n; i++) {
            result[i] = component.cast(source[i]);
        }
        return result;
    }

    /**
     * Kopiuje zawartość podanej tablicy. Metoda posiada przeładowane wersje
     * przeznaczone do obsługi tablic obiektów, jak również tablic typów prostych
     * (w takim wypadku tracona jest informacja o typie).
     * @param <T>
     * @param source
     * @return
     */
    @SuppressWarnings("unchecked")
    public static <T> T[] arraycopy(T[] source) {
        return Arrays.copyOf(source, source.length);
    }

    /**
     * Kopiuje zawartość podanej tablicy. Metoda posiada przeładowane wersje
     * przeznaczone do obsługi tablic obiektów, jak również tablic typów prostych
     * (w takim wypadku tracona jest informacja o typie).
     * @param <ArrayT>
     * @param source
     * @return
     */
    @SuppressWarnings({"unchecked", "SuspiciousSystemArraycopy"})
    public static <ArrayT> ArrayT arraycopy(ArrayT source) {
        int size = size(source);
        Object target = make(source.getClass(), size);
        System.arraycopy(source, 0, target, 0, size);
        return (ArrayT)target;
    }

    /**
     * Wyszukuje w tablicy pierwszy element równy podanej wartości i zwraca
     * pozycję elementu.
     * @param <T>
     * @param array
     * @param value
     * @return pozycja elementu równego {@code value} lub {@code -1},
     * jeśli nie znaleziono elementu.
     */
    public static <T> int indexOf(T[] array, T value) {
        if( array == null) {
            return -1;
        }
        for(int i=0; i<array.length; i++) {
            if( ValueUtils.equals(array[i], value)) { return i; }
        }
        return -1;
    }
    
    /**
     * @param array
     * @param value
     * @return 
     */
    public static int indexOf(Object array, Object value) {
        return wrap(array).indexOf(value);
    }

    /**
     * Sprawdza, czy podana tablica zawiera element równy podanemu argumentowi.
     * @param <T>
     * @param array
     * @param value
     * @return
     */
    public static <T> boolean contains(T[] array, T value) {
        return indexOf(array, value) >= 0;
    }

    /**
     * Konwertuje wszystkie elementy tablicy za pomocą przekazanej funkcji,
     * umieszczając wynik w nowej tablicy typu {@code Object[]}
     * @param <S>
     * @param values
     * @param function
     * @return
     */
    public static <S> Object[] apply(S[] values, Function<?,S> function) {
        if(values == null) {
            return values;
        }
        final int n = values.length;
        Object[] target = new Object[values.length];
        for(int i=0; i<n; i++) {
            target[i] = function.apply(values[i]);
        }
        return target;
    }

    /**
     * Konwertuje wszystkie elementy tablicy za pomocą przekazanej funkcji,
     * umieszczając wynik w nowej tablicy typu {@code Object[]}
     * @param <T>
     * @param values
     * @param function
     * @return
     */
    public static <T> Object[] inverse(T[] values, CoFunction<T,?> function) {
        if(values == null) {
            return values;
        }
        final int n = values.length;
        Object[] target = new Object[values.length];
        for(int i=0; i<n; i++) {
            target[i] = function.inverse(values[i]);
        }
        return target;
    }

}
