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

package net.ranides.assira.collection;

import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.ListIterator;
import java.util.Set;
import net.ranides.assira.generic.CoFunction;
import net.ranides.assira.generic.Function;
import net.ranides.assira.generic.ValueUtils;
import net.ranides.assira.math.Randomizer;
import net.ranides.assira.reflection.util.ClassUtils;
import net.ranides.assira.trace.LoggerUtils;
import org.junit.Test;
import static org.junit.Assert.*;

/**
 *
 * @author ranides
 */
@SuppressWarnings({
    "PMD.AvoidDuplicateLiterals",
    "MismatchedReadAndWriteOfArray",
    "UnnecessaryBoxing"
})
public class ArrayUtilsTest {

    public ArrayUtilsTest() {
    }

    @Test
    public void testWrap() {
        Integer[] boxArray = new Integer[]{1,2,3,4,5,6};
        List<Integer> boxList = ArrayUtils.wrap(boxArray);

        boxArray[2] = 77;
        boxList.set(3, 78);

        assertEquals(boxList, Arrays.asList(1,2,77,78,5,6));
        assertArrayEquals(boxArray, new Integer[]{1,2,77,78,5,6});
    }

    @Test
    public void testWrapRaw() {
        int[] rawArray = new int[]{1,2,3,4,5,6};
        List<Integer> rawList = ArrayUtils.wrap(rawArray);

        rawArray[2] = 77;
        rawList.set(3, 78);

        assertEquals(rawList, Arrays.asList(1,2,77,78,5,6));
        assertArrayEquals(rawArray, new int[]{1,2,77,78,5,6});
    }

    @Test
    public void testWrapCast() {
        short[] array = new short[]{0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77,0x88};
        List<Integer> list = ArrayUtils.wrap(int[].class, array);

        array[2] = 0xE33;
        list.set(2, 0x1c2c3c4c);

        assertArrayEquals(new short[]{0x11, 0x22, 0xe33, 0x44, 0x3c4c, 0x1c2c, 0x77, 0x88 }, array);
        assertEquals(Arrays.asList(0x220011, 0x440e33, 0x1c2c3c4c, 0x880077), list);

        assertSame(list, ArrayUtils.wrap(int[].class, list));
        assertEquals(Collections.emptyList(), ArrayUtils.wrap(int[].class, null));
        assertEquals(Collections.emptyList(), ArrayUtils.wrap(int[].class, new float[0]));
    }

    @Test
    public void testAsList() {
        Integer[] boxArray = new Integer[]{1,2,3,4,5,6};
        List<Integer> boxList = ArrayUtils.asList(boxArray);

        boxArray[2] = 77;
        boxList.set(3, 78);

        assertEquals(boxList, Arrays.asList(1,2,77,78,5,6));
        assertArrayEquals(boxArray, new Integer[]{1,2,77,78,5,6});

    }

    @Test
    public void testShift() {
        Integer[] data1 = new Integer[]{1,2,3,4,5,6};

        ArrayUtils.shift(data1);
        assertArrayEquals(new Integer[]{2, 3, 4, 5, 6, 6}, data1);

        ArrayUtils.shift(data1);
        assertArrayEquals(new Integer[]{3, 4, 5, 6, 6, 6}, data1);


        Integer[] data2 = new Integer[]{1,2};
        ArrayUtils.shift(data2);
        assertArrayEquals(new Integer[]{2, 2}, data2);


        Integer[] data3 = new Integer[]{1};
        ArrayUtils.shift(data3);
        assertArrayEquals(new Integer[]{1}, data3);

        try {
            ArrayUtils.shift(new Integer[]{});
            fail("exception expected");
        } catch(EmptyCollectionException cause) {
            assertTrue(true);
        }

        try {
            ArrayUtils.shift((Integer[])null);
            fail("exception expected");
        } catch(EmptyCollectionException cause) {
            assertTrue(true);
        }
        try {
            ArrayUtils.shift(new int[]{});
            fail("exception expected");
        } catch(EmptyCollectionException cause) {
            assertTrue(true);
        }
        try {
            ArrayUtils.shift((int[])null);
            fail("exception expected");
        } catch(EmptyCollectionException cause) {
            assertTrue(true);
        }
    }

    @Test
    public void testShiftPrimitive() {
        // wiemy już, że shift(T[]) działa dobrze, resztę testujemy w oparciu
        // o porównanie, czy shift tablicy wrapowanej daje to samo co shift native


        new ShiftTest<char[]>(2, char[].class) { @Override void run() {
            ArrayUtils.shift(array);
            ArrayUtils.shift(boxed);
        }};
        new ShiftTest<char[]>(20, char[].class) { @Override void run() {
            ArrayUtils.shift(array);
            ArrayUtils.shift(array);
            ArrayUtils.shift(array);
            ArrayUtils.unshift(array);

            ArrayUtils.shift(boxed);
            ArrayUtils.shift(boxed);
            ArrayUtils.shift(boxed);
            ArrayUtils.unshift(boxed);
        }};

        new ShiftTest<byte[]>(2, byte[].class) { @Override void run() {
            ArrayUtils.shift(array);
            ArrayUtils.shift(boxed);
        }};
        new ShiftTest<byte[]>(20, byte[].class) { @Override void run() {
            ArrayUtils.shift(array);
            ArrayUtils.shift(array);
            ArrayUtils.shift(array);
            ArrayUtils.unshift(array);

            ArrayUtils.shift(boxed);
            ArrayUtils.shift(boxed);
            ArrayUtils.shift(boxed);
            ArrayUtils.unshift(boxed);
        }};

        new ShiftTest<short[]>(2, short[].class) { @Override void run() {
            ArrayUtils.shift(array);
            ArrayUtils.shift(boxed);
        }};
        new ShiftTest<short[]>(20, short[].class) { @Override void run() {
            ArrayUtils.shift(array);
            ArrayUtils.shift(array);
            ArrayUtils.shift(array);
            ArrayUtils.unshift(array);

            ArrayUtils.shift(boxed);
            ArrayUtils.shift(boxed);
            ArrayUtils.shift(boxed);
            ArrayUtils.unshift(boxed);
        }};

        new ShiftTest<int[]>(2, int[].class) { @Override void run() {
            ArrayUtils.shift(array);
            ArrayUtils.shift(boxed);
        }};
        new ShiftTest<int[]>(20, int[].class) { @Override void run() {
            ArrayUtils.shift(array);
            ArrayUtils.shift(array);
            ArrayUtils.shift(array);
            ArrayUtils.unshift(array);

            ArrayUtils.shift(boxed);
            ArrayUtils.shift(boxed);
            ArrayUtils.shift(boxed);
            ArrayUtils.unshift(boxed);
        }};

        new ShiftTest<long[]>(2, long[].class) { @Override void run() {
            ArrayUtils.shift(array);
            ArrayUtils.shift(boxed);
        }};
        new ShiftTest<long[]>(20, long[].class) { @Override void run() {
            ArrayUtils.shift(array);
            ArrayUtils.shift(array);
            ArrayUtils.shift(array);
            ArrayUtils.unshift(array);

            ArrayUtils.shift(boxed);
            ArrayUtils.shift(boxed);
            ArrayUtils.shift(boxed);
            ArrayUtils.unshift(boxed);
        }};

        new ShiftTest<float[]>(2, float[].class) { @Override void run() {
            ArrayUtils.shift(array);
            ArrayUtils.shift(boxed);
        }};
        new ShiftTest<float[]>(20, float[].class) { @Override void run() {
            ArrayUtils.shift(array);
            ArrayUtils.shift(array);
            ArrayUtils.shift(array);
            ArrayUtils.unshift(array);

            ArrayUtils.shift(boxed);
            ArrayUtils.shift(boxed);
            ArrayUtils.shift(boxed);
            ArrayUtils.unshift(boxed);
        }};

        new ShiftTest<double[]>(2, double[].class) { @Override void run() {
            ArrayUtils.shift(array);
            ArrayUtils.shift(boxed);
        }};
        new ShiftTest<double[]>(20, double[].class) { @Override void run() {
            ArrayUtils.shift(array);
            ArrayUtils.shift(array);
            ArrayUtils.shift(array);
            ArrayUtils.unshift(array);

            ArrayUtils.shift(boxed);
            ArrayUtils.shift(boxed);
            ArrayUtils.shift(boxed);
            ArrayUtils.unshift(boxed);
        }};

        new ShiftTest<boolean[]>(2, boolean[].class) { @Override void run() {
            ArrayUtils.shift(array);
            ArrayUtils.shift(boxed);
        }};
        new ShiftTest<boolean[]>(20, boolean[].class) { @Override void run() {
            ArrayUtils.shift(array);
            ArrayUtils.shift(array);
            ArrayUtils.shift(array);
            ArrayUtils.unshift(array);

            ArrayUtils.shift(boxed);
            ArrayUtils.shift(boxed);
            ArrayUtils.shift(boxed);
            ArrayUtils.unshift(boxed);
        }};
        assertTrue(true);
    }

    private static abstract class ShiftTest<P> {

        final P array;
        final Object[] boxed;

        abstract void run();

        @SuppressWarnings({"unchecked", "OverridableMethodCallInConstructor"})
        public ShiftTest(int size, Class<P> type) {
            List list = Randomizer.list(type.getComponentType(), size);

            array = (P)ArrayUtils.toArray(type, list);
            boxed = (Object[])ArrayUtils.toArray(ClassUtils.box(type), list);

            List plist = ArrayUtils.wrap(array);
            List blist = ArrayUtils.wrap(boxed);

            for(int cycle=0; cycle<5; cycle++) {
                run();
                for(int i=0; i<size; i++) {
                    if( !ValueUtils.equals(blist.get(i), plist.get(i), 0.1)) {
                        LoggerUtils.dump("%s\n", plist);
                        LoggerUtils.dump("%s\n", blist);
                        fail("primivite test: "+type.getSimpleName());
                    }
                }
            }
        }
    }

    @Test
    public void testUnshift() {
        Integer[] data1 = new Integer[]{1,2,3,4,5,6};

        ArrayUtils.unshift(data1);
        assertArrayEquals(new Integer[]{1, 1, 2, 3, 4, 5}, data1);

        ArrayUtils.unshift(data1);
        assertArrayEquals(new Integer[]{1, 1, 1, 2, 3, 4}, data1);

        Integer[] data2 = new Integer[]{1,2};
        ArrayUtils.unshift(data2);
        assertArrayEquals(new Integer[]{1, 1}, data2);


        Integer[] data3 = new Integer[]{1};
        ArrayUtils.unshift(data3);
        assertArrayEquals(new Integer[]{1}, data3);

        try {
            ArrayUtils.unshift(new Integer[]{});
            fail("exception expected");
        } catch(EmptyCollectionException cause) {
            assertTrue(true);
        }
        try {
            ArrayUtils.unshift((Integer[])null);
            fail("exception expected");
        } catch(EmptyCollectionException cause) {
            assertTrue(true);
        }
        try {
            ArrayUtils.unshift(new int[]{});
            fail("exception expected");
        } catch(EmptyCollectionException cause) {
            assertTrue(true);
        }
        try {
            ArrayUtils.unshift((int[])null);
            fail("exception expected");
        } catch(EmptyCollectionException cause) {
            assertTrue(true);
        }
    }

    @Test
    public void testGetDefault() {
        Integer numbers[] = new Integer[]{1,2,3,4,5,6};
        assertEquals(Integer.valueOf(3), ArrayUtils.get(numbers, 2, -1));
        assertEquals(Integer.valueOf(-1), ArrayUtils.get(numbers, 10, -1));
        try {
            ArrayUtils.get(numbers, -3, -1);
            fail("exception expected");
        } catch(IndexOutOfBoundsException ex) {
            assertTrue(true);
        }
    }

    @Test
    public void testGet() {
        Integer numbers[] = new Integer[]{1,2,3,4,5,6};
        assertEquals(Integer.valueOf(3), ArrayUtils.get(numbers, 2));
        assertEquals(null, ArrayUtils.get(numbers, 10));
        try {
            ArrayUtils.get(numbers, -3);
            fail("exception expected");
        } catch(IndexOutOfBoundsException ex) {
            assertTrue(true);
        }
    }

    @Test
    public void testFirstByClass() {
        Object empty[] = {};
        Object values[] = { "14", Float.valueOf(12f), Float.valueOf(10f), Integer.valueOf(8) };

        assertEquals(null, ArrayUtils.first(empty, Object.class) );
        assertEquals(null, ArrayUtils.first(null) );
        assertEquals(Float.valueOf(12f), ArrayUtils.first(values, Float.class));
        assertEquals(Integer.valueOf(8), ArrayUtils.first(values, Integer.class));

        assertEquals(Float.valueOf(12f), ArrayUtils.first(values, Number.class));
        assertEquals("14", ArrayUtils.first(values, Object.class));
        assertEquals(null, ArrayUtils.first(values, Long.class));

    }

    @Test
    public void testListToArray() {
        List<Long> longList = java.util.Arrays.asList(10L, 20L, 30L, 40L, 50L);

        Long[] objectArray = ArrayUtils.toArray(Long[].class, longList);
        long[] primtvArray = ArrayUtils.toArray(long[].class, longList);

        assertArrayEquals(new Long[]{10L, 20L, 30L, 40L, 50L}, objectArray);
        assertArrayEquals(new long[]{10L, 20L, 30L, 40L, 50L}, primtvArray);

        try {
            String[] result = ArrayUtils.toArray(String[].class, longList);
            fail("cast exception expected");
        } catch(ClassCastException cause) {
            assertEquals("cannot cast java.lang.Long to class java.lang.String", cause.getMessage());
        }
    }

    @Test
    public void testObjectSort() {
        Integer[] aIndex =  new Integer[]{  3, 21,  6,  1, 44, 42, 33, 19 };
        Integer[] aValues = new Integer[]{ 79, 51, 33, 34, 38, 63, 32, 31 };

        int[]     bIndex =  new int[]    {  3, 21,  6,  1, 44, 42, 33, 19 };
        Integer[] bValues = new Integer[]{ 79, 51, 33, 34, 38, 63, 32, 31 };

        ArrayUtils.sort(aValues, aIndex);
        assertArrayEquals(new Integer[]{34, 79, 33, 31, 51, 32, 63, 38}, aValues);
        assertArrayEquals(new Integer[]{ 1,  3,  6, 19, 21, 33, 42, 44}, aIndex);

        ArrayUtils.sort(bValues, bIndex);
        assertArrayEquals(new Integer[]{34, 79, 33, 31, 51, 32, 63, 38}, bValues);
        assertArrayEquals(new int[]    { 1,  3,  6, 19, 21, 33, 42, 44}, bIndex);
    }

    @Test
    public void testConcat() {
        assertArrayEquals(
            new String[]{"a","b","c","d","e"},
            ArrayUtils.concat(new String[]{"a","b"}, new String[]{"c","d","e"})
        );
        assertArrayEquals(
            new String[]{"a","b","c"},
            ArrayUtils.concat(new String[]{"a","b"}, new String[]{"c"})
        );
        assertArrayEquals(
            new String[]{"a","b","c"},
            ArrayUtils.concat(new String[]{"a"}, new String[]{"b", "c"})
        );
        assertArrayEquals(
            new String[]{"b","c"},
            ArrayUtils.concat(new String[]{}, new String[]{"b", "c"})
        );
        assertArrayEquals(
            new String[]{"b","c"},
            ArrayUtils.concat(new String[]{"b", "c"}, new String[]{})
        );
    }

    @SuppressWarnings("PMD")
    @Test
    public void testConcatPrimitive() {
        new ConcatTest<byte[]>(0, 10, byte[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};
        new ConcatTest<byte[]>(10, 0, byte[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};
        new ConcatTest<byte[]>(10, 20, byte[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};
        new ConcatTest<byte[]>(30, 20, byte[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};


        new ConcatTest<short[]>(0, 10, short[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};
        new ConcatTest<short[]>(10, 0, short[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};
        new ConcatTest<short[]>(10, 20, short[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};
        new ConcatTest<short[]>(30, 20, short[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};


        new ConcatTest<int[]>(0, 10, int[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};
        new ConcatTest<int[]>(10, 0, int[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};
        new ConcatTest<int[]>(10, 20, int[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};
        new ConcatTest<int[]>(30, 20, int[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};


        new ConcatTest<long[]>(0, 10, long[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};
        new ConcatTest<long[]>(10, 0, long[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};
        new ConcatTest<long[]>(10, 20, long[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};
        new ConcatTest<long[]>(30, 20, long[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};


        new ConcatTest<float[]>(0, 10, float[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};
        new ConcatTest<float[]>(10, 0, float[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};
        new ConcatTest<float[]>(10, 20, float[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};
        new ConcatTest<float[]>(30, 20, float[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};


        new ConcatTest<double[]>(0, 10, double[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};
        new ConcatTest<double[]>(10, 0, double[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};
        new ConcatTest<double[]>(10, 20, double[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};
        new ConcatTest<double[]>(30, 20, double[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};


        new ConcatTest<char[]>(0, 10, char[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};
        new ConcatTest<char[]>(10, 0, char[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};
        new ConcatTest<char[]>(10, 20, char[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};
        new ConcatTest<char[]>(30, 20, char[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};


        new ConcatTest<boolean[]>(0, 10, boolean[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};
        new ConcatTest<boolean[]>(10, 0, boolean[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};
        new ConcatTest<boolean[]>(10, 20, boolean[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};
        new ConcatTest<boolean[]>(30, 20, boolean[].class){ @Override void run() {
            result = ArrayUtils.concat(first, second);
        }};
    }

    private static abstract class ConcatTest<P> {

        final P first;
        final P second;
        P result;

        abstract void run();

        @SuppressWarnings({"unchecked", "OverridableMethodCallInConstructor"})
        public ConcatTest(int ifirst, int isecond, Class<P> type) {
            List lfirst = Randomizer.list(type.getComponentType(), ifirst);
            List lsecond = Randomizer.list(type.getComponentType(), isecond);

            first = (P)ArrayUtils.toArray(type, lfirst);
            second = (P)ArrayUtils.toArray(type, lsecond);

            run();
            if(result == null) {
                fail("unknown result of primivite test: "+type.getSimpleName());
            }

            Object[] ofirst = (Object[])ArrayUtils.toArray(ClassUtils.box(type), lfirst);
            Object[] osecond = (Object[])ArrayUtils.toArray(ClassUtils.box(type), lsecond);
            Object[] oresult = ArrayUtils.concat(ofirst, osecond);

            List<Object> expected = ArrayUtils.wrap(oresult);
            List<Object> current = ArrayUtils.wrap(result);

            if( expected.size() != current.size() ) {
                LoggerUtils.dump("expected = %s\ncurrent  = %s\n", expected, current);
                fail("primivite test: "+type.getSimpleName());
            }
            int n = expected.size();
            for(int i=0; i<n; i++) {
                if( !ValueUtils.equals(expected.get(i), current.get(i), 0.1)) {
                    LoggerUtils.dump("expected = %s\ncurrent  = %s\n", expected, current);
                    fail("primivite test: "+type.getSimpleName());
                }
            }
        }
    }

    @Test
    public void testFirst() {
        assertNull( ArrayUtils.first(null) );
        assertNull( ArrayUtils.first(new Integer[]{}) );
        assertEquals( (Integer)7, ArrayUtils.first(new Integer[]{7}) );
        assertEquals( (Integer)7, ArrayUtils.first(new Integer[]{7,2,3}) );

        assertNull( ArrayUtils.first(null, String.class) );
        assertNull( ArrayUtils.first(new Number[]{}, Float.class) );
        assertNull( ArrayUtils.first( new Number[]{7.0f}, Double.class) );
        assertEquals( 7.0, ArrayUtils.first( new Number[]{1, 2.0f, 7.0, 8.0f, 9.0 }, Double.class), 0.1 );
        assertEquals( 7.0, ArrayUtils.first( new Number[]{1, 2.0f, 7.0, 8.0f, 9.0 }, Double.class), 0.1 );
    }

    @Test
    public void testLast() {
        assertNull( ArrayUtils.last(null) );
        assertNull( ArrayUtils.last(new Integer[]{}) );
        assertEquals( (Integer)7, ArrayUtils.last(new Integer[]{7}) );
        assertEquals( (Integer)3, ArrayUtils.last(new Integer[]{7,2,3}) );

        assertNull( ArrayUtils.last(null, String.class) );
        assertNull( ArrayUtils.last(new Number[]{}, Float.class) );
        assertNull( ArrayUtils.last( new Number[]{7.0f}, Double.class) );
        assertEquals( 9.0, ArrayUtils.last( new Number[]{1, 2.0f, 7.0, 8.0f, 9.0 }, Double.class), 0.1 );
        assertEquals( 9.0, ArrayUtils.last( new Number[]{1, 2.0f, 7.0, 8.0f, 9.0 }, Double.class), 0.1 );
    }

    @Test
    public void testIndexOf() {
        Integer[] list = new Integer[]{1, 5, 2, 3, 4, 5, 6};

        assertEquals(-1, ArrayUtils.indexOf(null, 77));
        assertEquals(-1, ArrayUtils.indexOf(new Integer[]{}, 77));
        assertEquals(-1, ArrayUtils.indexOf(null, null));
        assertEquals(-1, ArrayUtils.indexOf(new Integer[]{}, null));

        assertEquals(-1, ArrayUtils.indexOf(list, 77));
        assertEquals(1, ArrayUtils.indexOf(list, 5));
        assertEquals(6, ArrayUtils.indexOf(list, 6));

        // jakim cudem to się kompiluje?
        // bo do Number jest typ sprowadzany, a Integer porównując sprawdza instanceof Integer,
        // więc w efekcie nie znajdzie naszej klasy Short
        assertEquals(-1, ArrayUtils.indexOf(list, (short)6 ));
    }

    @Test
    public void testContains() {
        Integer[] list = new Integer[]{1, 5, 2, 3, 4, 5, 6};

        assertFalse(ArrayUtils.contains(null, 77));
        assertFalse(ArrayUtils.contains(new Integer[]{}, 77));
        assertFalse(ArrayUtils.contains(null, null));
        assertFalse(ArrayUtils.contains(new Integer[]{}, null));

        assertFalse(ArrayUtils.contains(list, 77));
        assertTrue(ArrayUtils.contains(list, 5));
        assertTrue(ArrayUtils.contains(list, 6));

        // jakim cudem to się kompiluje?
        // bo do Number jest typ sprowadzany, a Integer porównując sprawdza instanceof Integer,
        // więc w efekcie nie znajdzie naszej klasy Short
        assertFalse(ArrayUtils.contains(list, (short)6 ));

    }

    @Test
    public void testApply() {
        Function<String, Integer> functor = new Function<String, Integer>(){
            @Override
            public String apply(Integer source) {
                return source.toString();
            }
        };
        CoFunction<Object, Integer> cofunctor = new CoFunction<Object, Integer>(){
            @Override
            public Integer inverse(Object source) {
                return Integer.valueOf(source.toString());
            }
        };
        Integer[] input1 = new Integer[]{1,2,3,4,5};
        Object[] output1 = ArrayUtils.apply(input1, functor);
        Object[] output2 = ArrayUtils.inverse(output1, cofunctor);

        assertNull( ArrayUtils.apply(null, functor) );
        assertNull( ArrayUtils.inverse(null, cofunctor) );

        assertArrayEquals(new String[]{"1","2","3","4","5"}, output1);
        assertArrayEquals(input1, output2);
    }

    @Test
    public void testSize() {
        assertEquals(0, ArrayUtils.size((int[])null) );
        assertEquals(0, ArrayUtils.size((Object)null) );

        assertEquals(0, ArrayUtils.size(new int[0]) );
        assertEquals(0, ArrayUtils.size(new Integer[0]) );

        assertEquals(4, ArrayUtils.size(new int[4]) );
        assertEquals(4, ArrayUtils.size(new Integer[4]) );

        try {
            ArrayUtils.size("");
        } catch(IllegalArgumentException cause) {
            assertTrue( cause.getMessage().contains("type is not an array"));
        }


    }

    @Test
    public void testIsEmpty() {
        assertTrue( ArrayUtils.isEmpty(null) );
        assertTrue( ArrayUtils.isEmpty(new int[0]) );
        assertTrue( ArrayUtils.isEmpty(new Integer[0]) );
        assertFalse( ArrayUtils.isEmpty(new int[3]) );
        assertFalse( ArrayUtils.isEmpty(new Integer[3]) );

        try {
            ArrayUtils.isEmpty("");
        } catch(IllegalArgumentException cause) {
            assertTrue( cause.getMessage().contains("type is not an array"));
        }
    }

    @Test
    public void testClip() {
        int[] int0 = new int[]{};
        int[] int3 = new int[]{1, 2, 3};
        int[] int5 = new int[]{1, 2, 3, 4, 5};

        Integer[] integer0 = new Integer[]{};
        Integer[] integer3 = new Integer[]{1, 2, 3};
        Integer[] integer5 = new Integer[]{1, 2, 3, 4, 5};

        assertArrayEquals(int0, ArrayUtils.clip(int0, 0));
        assertArrayEquals(int0, ArrayUtils.clip(int3, 0));
        assertArrayEquals(int0, ArrayUtils.clip(int0, 2));

        assertArrayEquals(int3, ArrayUtils.clip(int3, 3));
        assertArrayEquals(int3, ArrayUtils.clip(int3, 5));
        assertArrayEquals(int3, ArrayUtils.clip(int5, 3));

        assertArrayEquals(integer0, ArrayUtils.clip(integer0, 0));
        assertArrayEquals(integer0, ArrayUtils.clip(integer3, 0));
        assertArrayEquals(integer0, ArrayUtils.clip(integer0, 2));

        assertArrayEquals(integer3, ArrayUtils.clip(integer3, 3));
        assertArrayEquals(integer3, ArrayUtils.clip(integer3, 5));
        assertArrayEquals(integer3, ArrayUtils.clip(integer5, 3));

    }

    @Test
    public void testMake() {
        int[] int4zeroA = ArrayUtils.make(int.class, 4);
        int[] int4fiveA = ArrayUtils.make(int.class, 4, 5);
        Integer[] int4A = ArrayUtils.make(Integer.class, 4, 9);

        int[] int4zeroB = ArrayUtils.make(int[].class, 4);
        int[] int4fiveB = ArrayUtils.make(int[].class, 4, 5);
        Integer[] int4B = ArrayUtils.make(Integer[].class, 4, 9);

        int[] int4seven = ArrayUtils.make(4, 7);
        String[] text5a = ArrayUtils.make(5, "a");

        Number[] object1 = ArrayUtils.makeT(Integer.class, 4);

        Integer[] object2 = ArrayUtils.makeT(Integer.class, 4, 0);
        Number[]  object3 = ArrayUtils.makeT(Float.class, 4, 0f);

        Number[] object4 = ArrayUtils.makeT(4, 7L);
        Number[] object5 = ArrayUtils.makeT(4, 7f);

        assertArrayEquals(new int[]{0,0,0,0}, int4zeroA);
        assertArrayEquals(new int[]{5,5,5,5}, int4fiveA);
        assertArrayEquals(new Integer[]{9,9,9,9}, int4A);

        assertArrayEquals(new int[]{0,0,0,0}, int4zeroB);
        assertArrayEquals(new int[]{5,5,5,5}, int4fiveB);
        assertArrayEquals(new Integer[]{9,9,9,9}, int4B);

        assertArrayEquals(new int[]{7,7,7,7}, int4seven);

        assertArrayEquals(new String[]{"a","a","a","a","a"}, text5a);

        assertArrayEquals(new Integer[]{null,null,null,null}, object1);
        assertArrayEquals(new Integer[]{0,0,0,0}, object2);
        assertArrayEquals(new Float[]{0f,0f,0f,0f}, object3);
        assertArrayEquals(new Long[]{7L,7L,7L,7L}, object4);
        assertArrayEquals(new Float[]{7f,7f,7f,7f}, object5);

        try {
            int[] int4cast = ArrayUtils.make(int[].class, 4, (short)5);
            fail("exception expected");
        } catch(ClassCastException cause) {
            assertTrue(cause.getMessage().matches(".*Short.*Integer.*"));
        }
        try {
            String[] int4cast = ArrayUtils.make(int[].class, 4, 5);
            fail("exception expected");
        } catch(ClassCastException cause) {
            assertTrue(cause.getMessage().matches(".*\\[I.*String;*"));
        }
        try {
            int[] long4cast = ArrayUtils.make(4, 7L);
            fail("exception expected");
        } catch(ClassCastException cause) {
            assertTrue(cause.getMessage().matches(".*\\[J.*\\[I*"));
        }
        try {
            ArrayUtils.make(4, null);
        } catch(AssertionError cause) {
            assertTrue(true); // dla trybu z włączonymi asercjami
        } catch(NullPointerException cause) {
            assertTrue(true); // dla trybu release
        }
        try {
            Integer[] object1cast = ArrayUtils.makeT(int.class, 7);
        } catch(ClassCastException cause) {
            assertTrue(cause.getMessage().matches(".*\\[I.*Object.*"));
        }
    }

    @Test
    public void testPair() {
        Set<ArrayUtils.Pair<String>> pairs = new HashSet<>();
        ArrayUtils.Pair<String>
            p1  = new ArrayUtils.Pair<>("33",17),
            p2  = new ArrayUtils.Pair<>("44",17),
            p3  = new ArrayUtils.Pair<>("55",17),
            p1c = new ArrayUtils.Pair<>("33",19);
        pairs.add(p1);
        pairs.add(p2);
        pairs.add(p3);

        assertTrue(pairs.contains(p1));
        assertTrue(pairs.contains(p2));
        assertTrue(pairs.contains(p1c));

        Set<ArrayUtils.IntPair> ipairs = new HashSet<>();
        ArrayUtils.IntPair
            ip1  = new ArrayUtils.IntPair(33, 17),
            ip2  = new ArrayUtils.IntPair(44, 17),
            ip3  = new ArrayUtils.IntPair(55, 17),
            ip1c = new ArrayUtils.IntPair(33, 19);
        ipairs.add(ip1);
        ipairs.add(ip2);
        ipairs.add(ip3);

        assertTrue(ipairs.contains(ip1));
        assertTrue(ipairs.contains(ip2));
        assertTrue(ipairs.contains(ip1c));
    }

    @Test
    public void testIterator() {
        Integer[] array = new Integer[]{1,2,3,4,5,6};

        ListIterator<Integer> it1 = ArrayUtils.iterator(array);
        int sum1 = 0;
        while(it1.hasNext()) {
            sum1 += it1.next();
            it1.set(sum1);
        }
        assertEquals(21, sum1);
        assertArrayEquals(new Integer[]{1,3,6,10,15,21}, array);

        ListIterator<Integer> it2 = ArrayUtils.iterator(array, 3, 1);
        int sum2 = 0;
        while(it2.hasNext()) {
            sum2 += it2.next();
            it2.set(-sum2);
        }
        assertEquals(19, sum2);
        assertArrayEquals(new Integer[]{1,-3,-9,-19,15,21}, array);

        try {
             ListIterator<Integer> it3 = ArrayUtils.iterator(array, 3, 1);
             it3.next();
             it3.remove();
             fail("exception expected");
        } catch(UnsupportedOperationException cause) {
            assertTrue( cause.getMessage().contains("array"));
        }

        try {
             ListIterator<Integer> it3 = ArrayUtils.iterator(array, 3, 1);
             it3.next();
             it3.add(17);
             fail("exception expected");
        } catch(UnsupportedOperationException cause) {
            assertTrue( cause.getMessage().contains("array"));
        }
    }

    @Test
    public void testArrayCopy() {
        int[] input1 = new int[]{1,2,3,4,5};
        Integer[] input2 = new Integer[]{1,2,3,4,5};

        assertArrayEquals(input1, ArrayUtils.arraycopy(input1));
        assertArrayEquals(input2, ArrayUtils.arraycopy(input2));

        Integer[] target2a = new Integer[3];
        Integer[] target2b = new Integer[5];
        Integer[] output2a = ArrayUtils.arraycopy(target2a, input2);
        Integer[] output2b = ArrayUtils.arraycopy(target2b, input2);

        assertNotSame(target2a, output2a);
        assertArrayEquals(input2, output2a);

        assertSame(target2b, output2b);
        assertArrayEquals(input2, output2b);

    }

}
