import static org.junit.jupiter.api.Assertions.assertArrayEquals;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;

import org.junit.jupiter.api.Test;

public class MyArrayListAutomatedTest
{
    @Test
    public void testConstructors()
    {
        MyArrayList<Integer> mALDefault = new MyArrayList<Integer>();
        
        assertEquals(10, mALDefault.a.length);
        assertEquals(0, mALDefault.size);
        
        for(Object elem : mALDefault.a)
            assertEquals(null, elem);

        
        MyArrayList<Integer> mAL25 = new MyArrayList<Integer>(25);
        
        assertEquals(25, mAL25.a.length);
        assertEquals(0, mAL25.size);
        
        for(Object elem : mAL25.a)
            assertEquals(null, elem);
    }

    @Test
    public void testSize()
    {
        MyArrayList<Integer> mAL = new MyArrayList<Integer>();
        mAL.a = new Integer[]{5, 1, 4, 1, 0, null, null, null, null, null};
        mAL.size = 5;
        
        assertEquals(5, mAL.size());
    }

    @Test
    public void testSizeIsntCountingNulls()
    {
        MyArrayList<Integer> mAL = new MyArrayList<Integer>();
        mAL.a = new Integer[]{5, 1, 4, 1, null, null, null, null, null, null};
        mAL.size = 5;

        assertEquals(5, mAL.size());
    }

    @Test
    public void testGet()
    {
        MyArrayList<Integer> mAL = new MyArrayList<Integer>();
        mAL.a = new Integer[]{5, 3, null, null, null, null, null, null, null, null};
        mAL.size = 2;
        
        assertEquals(5, mAL.get(0));
        assertEquals(3, mAL.get(1));
        
        assertArrayEquals(new Integer[]{
                5, 3, null, null, null, null, null, null, null, null},
                mAL.a);
    }

    @Test
    public void testGetIndexValidityCheck()
    {
        MyArrayList<Integer> mAL = new MyArrayList<Integer>();
        mAL.a = new Integer[]{5, 3, null, null, null, null, null, null, null, null};
        mAL.size = 2;

        int[] validIndexes = new int[]{0, 1};

        // all this has to do is not throw an exception
        for(int index : validIndexes)
            mAL.get(index);

        int[] invalidIndexes = new int[]{-1, -2, 2, 10, 11};
        for(int index : invalidIndexes)
        {
            try
            {
                mAL.get(index);
                assertTrue(false); // should have thrown IndexOutOfBoundsException
            }
            catch(IndexOutOfBoundsException e)
            {
                assertTrue(true);
            }
        }
    }

    @Test
    public void testSet()
    {
        MyArrayList<Integer> mAL = new MyArrayList<Integer>();
        mAL.a = new Integer[]{5, 3, null, null, null, null, null, null, null, null};
        mAL.size = 2;
        
        assertEquals(3, mAL.set(1, 7));

        assertArrayEquals(new Integer[]{
                5, 7, null, null, null, null, null, null, null, null},
                mAL.a);
    }

    @Test
    public void testContains()
    {
        MyArrayList<Integer> mAL = new MyArrayList<Integer>();
        mAL.a = new Integer[]{5, 3, 8, 1, 7, null, null, null, null, null};
        mAL.size = 5;

        assertTrue(mAL.contains(8));
        assertTrue( ! mAL.contains(4) );

        assertTrue( ! mAL.contains(null) );

        mAL.size = 6; // The first null in the array is now part of the list.

        assertTrue(mAL.contains(null));
    }

    @Test
    public void testTrimToSize()
    {
        MyArrayList<Integer> mAL = new MyArrayList<Integer>();
        mAL.a = new Integer[]{5, 3, null, null, null, null, null, null, null, null};
        mAL.size = 2;

        mAL.trimToSize();
        
        assertArrayEquals(new Integer[]{5, 3}, mAL.a);
        assertEquals(2, mAL.size);
    }

    @Test
    public void testTrimOnlyTrimsOnExcessCapacity()
    {
        MyArrayList<Integer> mAL = new MyArrayList<Integer>(2);
        mAL.a = new Integer[]{5, 3};
        mAL.size = 2;

        Object[] aBefore = mAL.a;

        mAL.trimToSize();

        assertEquals(aBefore, mAL.a); // must point to exact same array
    }
    
    @Test
    public void testAddIndexValidityCheck()
    {
        int[] invalidIndexesForEmptyAL = new int[]{-1, -2, 1, 2};
        
        for (int index : invalidIndexesForEmptyAL)
        {
            try
            {
                MyArrayList<Integer> mAL = new MyArrayList<Integer>();
                mAL.a = new Integer[]{null, null, null, null, null, null, null, null, null, null};
                mAL.size = 0;

                mAL.add(index, 6);
                assertTrue(false); // should have thrown IndexOutOfBoundsException
            }
            catch (IndexOutOfBoundsException e)
            {
                assertTrue(true);
            }
        }

        MyArrayList<Integer> mAL = new MyArrayList<Integer>();

        int[] validIndexes = new int[]{0, 1, 2, 0, 1}; // order is important

        // all this has to do is not throw an exception
        for(int index : validIndexes)
            mAL.add(index, 6);


        int[] invalidIndexesForNonEmptyAL = new int[]{-1, -2, 6, 7};
        
        for(int index : invalidIndexesForNonEmptyAL)
        {
            try
            {
                mAL.a = new Integer[]{6, 6, 6, 6, 6, null, null, null, null, null};
                mAL.size = 5;
                
                mAL.add(index, 6);
                assertTrue(false); // should have thrown IndexOutOfBoundsException
            }
            catch(IndexOutOfBoundsException e)
            {
                assertTrue(true);
            }
        }
    }
    
    @Test
    public void testAddWhenCapacityZero()
    {
        MyArrayList<Integer> mAL = new MyArrayList<Integer>(0);
        mAL.a = new Integer[0];
        mAL.size = 0;

        mAL.add(5);
        
        assertEquals(1, mAL.a.length);
        assertEquals(1, mAL.size);
        assertEquals(5, mAL.a[0]);
    }

    @Test
    public void testAddDoesNotUnnecessarilyMakeNewArray()
    {
        MyArrayList<Integer> mAL = new MyArrayList<Integer>(3);
        mAL.a = new Integer[] {5, null, null};
        mAL.size = 1;

        Object[] aBefore = mAL.a;

        mAL.add(mAL.size(), 7);
        assertEquals(aBefore, mAL.a); // must point to exact same array

        mAL.add(8);
        assertEquals(aBefore, mAL.a); // must point to exact same array
    }

    @Test
    public void testRemoveDoesNotMakeNewArray()
    {
        MyArrayList<Integer> mAL = new MyArrayList<Integer>(3);
        mAL.a = new Integer[] {5, 6, 7};
        mAL.size = 3;

        Object[] aBefore = mAL.a;

        mAL.remove(1);
        assertEquals(aBefore, mAL.a); // must point to exact same array

        mAL.remove(1);
        assertEquals(aBefore, mAL.a); // must point to exact same array

        mAL.remove(0);
        assertEquals(aBefore, mAL.a); // must point to exact same array
    }

    @Test
    public void testAddWhenAtCapacity()
    {
        MyArrayList<Integer> mAL = new MyArrayList<Integer>(3);
        mAL.a = new Integer[]{5, 3, 6};
        mAL.size = 3;

        mAL.add(2, 5);

        assertEquals(7, mAL.a.length);
        assertEquals(4, mAL.size);
    }

    @Test
    public void testAddAtPositions()
    {
        MyArrayList<Integer> mAL = new MyArrayList<Integer>();
        mAL.a = new Integer[10];
        mAL.size = 0;

        
        mAL.add(5);
        
        assertArrayEquals(new Integer[] {
                5, null, null, null, null, null, null, null, null, null},
                mAL.a);
        
        assertEquals(1, mAL.size);

        
        mAL.add(0, 4);
        
        assertArrayEquals(new Integer[] {
                4, 5, null, null, null, null, null, null, null, null},
                mAL.a);
        
        assertEquals(2, mAL.size);
        
        
        mAL.add(1, 3);
        
        assertArrayEquals(new Integer[] {
                4, 3, 5, null, null, null, null, null, null, null},
                mAL.a);
        
        assertEquals(3, mAL.size);
        

        mAL.add(1, 6);
        
        assertArrayEquals(new Integer[] {
                4, 6, 3, 5, null, null, null, null, null, null},
                mAL.a);
        
        assertEquals(4, mAL.size);
        
        
        mAL.add(4, 9);
        
        assertArrayEquals(new Integer[] {
                4, 6, 3, 5, 9, null, null, null, null, null},
                mAL.a);
        
        assertEquals(5, mAL.size);
    }

    @Test
    public void testRemoveAtIndex()
    {
        MyArrayList<Integer> mAL = new MyArrayList<Integer>();
        mAL.a = new Integer[]{6, 7, 8, 9, 10, null, null, null, null, null};
        mAL.size = 5;

        
        assertEquals(6, mAL.remove(0));
        
        assertArrayEquals(new Integer[] {
                7, 8, 9, 10, null, null, null, null, null, null},
                mAL.a);
        
        assertEquals(4, mAL.size);
        
        
        assertEquals(10, mAL.remove(3));
        
        assertArrayEquals(new Integer[] {
                7, 8, 9, null, null, null, null, null, null, null},
                mAL.a);
        
        assertEquals(3, mAL.size);
        

        assertEquals(8, mAL.remove(1));
        
        assertArrayEquals(new Integer[] {
                7, 9, null, null, null, null, null, null, null, null},
                mAL.a);
        
        assertEquals(2, mAL.size);
    }
    
    @Test
    public void testRemoveValue()
    {
        MyArrayList<Integer> mAL = new MyArrayList<Integer>();
        mAL.a = new Integer[]{6, 7, 8, 9, 10, null, null, null, null, null};
        mAL.size = 5;

        Integer val = 9;
        assertTrue(mAL.remove(val));
        
        assertArrayEquals(new Integer[] {
                6, 7, 8, 10, null, null, null, null, null, null},
                mAL.a);
        
        assertEquals(4, mAL.size);
        
        
        mAL.a = new Integer[]{6, 9, 8, 9, 10, null, null, null, null, null};
        mAL.size = 5;
        
        
        val = 9;
        assertTrue(mAL.remove(val));
        
        assertArrayEquals(new Integer[] {
                6, 8, 9, 10, null, null, null, null, null, null},
                mAL.a);
        
        assertEquals(4, mAL.size);
        

        val = 15;
        assertTrue( ! mAL.remove(val) );
        
        assertArrayEquals(new Integer[] {
                6, 8, 9, 10, null, null, null, null, null, null},
                mAL.a);
        
        assertEquals(4, mAL.size);
    }
}
