/*
 * Copyright 2008 Google Inc.
 *
 * Licensed under the Apache License, Version 2.0 (the "License"); you may not
 * use this file except in compliance with the License. You may obtain a copy of
 * the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
 * License for the specific language governing permissions and limitations under
 * the License.
 */
package com.google.gwt.emultest.java.util;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.ListIterator;

/**
 * Tests List, and, by extension AbstractList. Uses inheritance to inherit all of Apache's TestList
 * and TestCollection.
 */
@SuppressWarnings("unchecked")
public abstract class ListTestBase extends TestArrayList {

  private static final boolean NO_OPTIMIZE_FALSE = false;

  public void testAddAll() {
    List<Integer> list = makeEmptyList();
    list.addAll(Arrays.asList(1, 2, 3, 4));
    list.addAll(2, Arrays.asList(21, 22));

    checkListSizeAndContent(list, 1, 2, 21, 22, 3, 4);

    list = makeEmptyList();
    list.addAll(Arrays.asList(1, 2, 3, 4));
    list.addAll(0, Arrays.asList(21, 22));

    checkListSizeAndContent(list, 21, 22, 1, 2, 3, 4);

    list = makeEmptyList();
    list.addAll(Arrays.asList(1, 2, 3, 4));
    list.addAll(4, Arrays.asList(21, 22));

    checkListSizeAndContent(list, 1, 2, 3, 4, 21, 22);
  }

  public void testAddAllReturnValue() {
    assertFalse(makeEmptyList().addAll(makeEmptyList()));
    assertTrue(makeEmptyList().addAll(makeFullList()));
    assertFalse(makeEmptyList().addAll(0, makeEmptyList()));
    assertTrue(makeEmptyList().addAll(0, makeFullList()));
    assertFalse(makeFullList().addAll(makeEmptyList()));
    assertTrue(makeFullList().addAll(makeFullList()));
    assertFalse(makeFullList().addAll(1, makeEmptyList()));
    assertTrue(makeFullList().addAll(1, makeFullList()));
  }

  public void testAddWatch() {
    List s = makeEmptyList();
    s.add("watch");
    assertEquals("watch", s.get(0));
  }

  public void testListIteratorAddInSeveralPositions() {
    List l = makeEmptyList();
    ListIterator i = l.listIterator();
    l.add(new Integer(0));
    for (int n = 2; n < 5; n += 2) {
      l.add(new Integer(n));
    }
    i = l.listIterator();
    i.next();
    i.add(new Integer(1));
    i.next();
    i.next();
    i.previous();
    i.add(new Integer(3));
    i.next();
    i.add(new Integer(5));
    i.add(new Integer(6));
    for (int n = 0; n < 6; n++) {
      assertEquals(new Integer(n), l.get(n));
    }
  }

  public void testListIteratorCreateInvalid() {
    List l = makeEmptyList();
    l.add(new Integer(1));
    l.listIterator(0);
    try {
      l.listIterator(1);
    } catch (IndexOutOfBoundsException e) {
      // expected
    }
    try {
      l.listIterator(-1);
    } catch (IndexOutOfBoundsException e) {
      // expected
    }
  }

  public void testListIteratorHasNextHasPreviousAndIndexes() {
    List l = makeEmptyList();
    ListIterator i = l.listIterator();
    assertFalse(i.hasNext());
    assertFalse(i.hasPrevious());
    i.add(new Integer(1));
    assertEquals(1, i.nextIndex());
    assertEquals(0, i.previousIndex());
    i = l.listIterator();
    assertEquals(0, i.nextIndex());
    assertEquals(-1, i.previousIndex());
    assertTrue(i.hasNext());
    assertFalse(i.hasPrevious());
    i.next();
    assertEquals(1, i.nextIndex());
    assertEquals(0, i.previousIndex());
    assertFalse(i.hasNext());
    assertTrue(i.hasPrevious());
  }

  public void testListIteratorRemove() {
    // TODO(jat): implement
  }

  public void testListIteratorSetInSeveralPositions() {
    List l = makeEmptyList();
    for (int n = 0; n < 5; n += 2) {
      l.add(new Integer(n));
    }
    l.listIterator();
    for (int n = 0; n < 3; n++) {
      l.set(n, new Integer(n));
    }
    for (int n = 0; n < 3; n++) {
      assertEquals(new Integer(n), l.get(n));
    }
  }

  public void testRemoveAllDuplicates() {
    Collection c = makeCollection();
    c.add("a");
    c.add("a");
    Collection d = makeCollection();
    d.add("a");
    assertTrue(c.removeAll(d));
    assertEquals(0, c.size());
  }

  public void testSubList() {
    List<Integer> wrappedList = createListWithContent(new int[] {1, 2, 3, 4, 5});
    List<Integer> testList = wrappedList.subList(1, 4);
    assertEquals(3, testList.size());

    assertEquals(testList, Arrays.asList(2, 3, 4));
    checkListSizeAndContent(testList, 2, 3, 4);
    testList.add(1, 6);
    assertEquals(testList, Arrays.asList(2, 6, 3, 4));
    checkListSizeAndContent(testList, 2, 6, 3, 4);
    assertEquals(wrappedList, Arrays.asList(1, 2, 6, 3, 4, 5));
    checkListSizeAndContent(wrappedList, 1, 2, 6, 3, 4, 5);
    testList.remove(2);
    assertEquals(testList, Arrays.asList(2, 6, 4));
    checkListSizeAndContent(testList, 2, 6, 4);

    try {
      testList.remove(3);
      fail("Expected remove to fail");
    } catch (IndexOutOfBoundsException e) {
    }

    checkListSizeAndContent(wrappedList, 1, 2, 6, 4, 5);
    testList.set(0, 7);
    checkListSizeAndContent(testList, 7, 6, 4);
    checkListSizeAndContent(wrappedList, 1, 7, 6, 4, 5);

    try {
      wrappedList.subList(-1, 5);
      fail("expected IndexOutOfBoundsException");
    } catch (IndexOutOfBoundsException e) {
    }

    try {
      wrappedList.subList(0, 15);
      fail("expected IndexOutOfBoundsException");
    } catch (IndexOutOfBoundsException e) {
    }

    try {
      wrappedList.subList(5, 1);
      fail("expected IllegalArgumentException");
    } catch (IllegalArgumentException e) {
    }

    try {
      wrappedList.subList(0, 1).add(2, 5);
      fail("expected IndexOutOfBoundsException");
    } catch (IndexOutOfBoundsException e) {
    }

    try {
      wrappedList.subList(0, 1).add(-1, 5);
      fail("expected IndexOutOfBoundsException");
    } catch (IndexOutOfBoundsException e) {
    }

    try {
      wrappedList.subList(0, 1).get(1);
      fail("expected IndexOutOfBoundsException");
    } catch (IndexOutOfBoundsException e) {
    }

    try {
      wrappedList.subList(0, 1).get(-1);
      fail("expected IndexOutOfBoundsException");
    } catch (IndexOutOfBoundsException e) {
    }

    try {
      wrappedList.subList(0, 1).set(2, 2);
      fail("expected IndexOutOfBoundsException");
    } catch (IndexOutOfBoundsException e) {
    }

    try {
      wrappedList.subList(0, 1).set(-1, 5);
      fail("expected IndexOutOfBoundsException");
    } catch (IndexOutOfBoundsException e) {
    }
  }

  /** Test add() method for list returned by List<E>.subList() method. */
  public void testSubListAdd() {
    List<Integer> baseList = createListWithContent(new int[] {1, 2, 3, 4, 5});
    List<Integer> sublist = baseList.subList(1, 3);
    assertEquals(2, sublist.size());

    sublist.add(33);
    sublist.add(34);

    assertEquals(4, sublist.size());
    assertEquals(7, baseList.size());

    /*
     * Assert correct values are found right before and after insertion. We're
     * checking the original list (baseList) even though the changes were made
     * in the sublist. That is because modifications made to the list returned
     * by sublist method should reflect in the original list.
     */
    assertEquals(3, (int) baseList.get(2));
    assertEquals(33, (int) baseList.get(3));
    assertEquals(34, (int) baseList.get(4));
    assertEquals(4, (int) baseList.get(5));

    /*
     * Assert that it is possible to add element at the beginning of the
     * sublist.
     */
    sublist.add(0, 22);
    sublist.add(0, 21);
    checkListSizeAndContent(sublist, 21, 22, 2, 3, 33, 34);
    checkListSizeAndContent(baseList, 1, 21, 22, 2, 3, 33, 34, 4, 5);

    // check adding at the end by specifying the index
    sublist.add(6, 35);
    checkListSizeAndContent(sublist, 21, 22, 2, 3, 33, 34, 35);
    checkListSizeAndContent(baseList, 1, 21, 22, 2, 3, 33, 34, 35, 4, 5);

    /*
     * Assert adding to underlying list after the sublist view has been defined.
     * After such an action behavior of sublist is undefined.
     */
    baseList.add(9, 44);
    baseList.add(55);
    baseList.add(0, 10);
    checkListSizeAndContent(baseList, 10, 1, 21, 22, 2, 3, 33, 34, 35, 4, 44, 5, 55);
  }

  public void testSubListRemove() {
    List<Integer> baseList = createListWithContent(new int[] {1, 2, 3, 4, 5});
    List<Integer> sublist = baseList.subList(1, 3);

    sublist.remove(0);
    assertEquals(4, baseList.size());
    assertEquals(3, baseList.get(1).intValue());

    try {
      sublist.remove(1);
      fail("Expected IndexOutOfBoundsException");
    } catch (IndexOutOfBoundsException expected) {
    }

    assertFalse(sublist.remove(Integer.valueOf(4)));

    assertTrue(sublist.remove(Integer.valueOf(3)));

    assertEquals(0, sublist.size());
    assertEquals(3, baseList.size());

    sublist.add(6);
    checkListSizeAndContent(baseList, 1, 6, 4, 5);
  }

  public void testToArray() {
    List l = makeEmptyList();
    for (int i = 0; i < 10; i++) {
      l.add(new Integer(i));
    }

    {
      Object[] objArray = l.toArray();
      assertEquals(10, objArray.length);
      for (int i = 0; i < 10; i++) {
        Integer elem = (Integer) objArray[i];
        assertEquals(i, elem.intValue());
      }
      // should not cause ArrayStore
      objArray[0] = new Object();
    }

    {
      Integer[] firstArray = new Integer[13];
      firstArray[10] = new Integer(10);
      firstArray[11] = new Integer(11);
      firstArray[12] = new Integer(12);
      Integer[] intArray = (Integer[]) l.toArray(firstArray);
      assertTrue(firstArray == intArray);
      assertEquals(13, intArray.length);
      for (int i = 0; i < 13; i++) {
        if (i == 10) {
          assertNull(intArray[i]);
        } else {
          Integer elem = intArray[i];
          assertEquals(i, elem.intValue());
        }
      }
      try {
        Object[] objArray = NO_OPTIMIZE_FALSE ? new Object[1] : intArray;
        assertTrue(objArray instanceof Integer[]);
        objArray[0] = new Object();
        fail("expected ArrayStoreException");
      } catch (ArrayStoreException e) {
      }
    }

    {
      Integer[] firstArray = new Integer[0];
      Integer[] intArray = (Integer[]) l.toArray(firstArray);
      assertFalse(firstArray == intArray);
      assertEquals(10, intArray.length);
      for (int i = 0; i < 10; i++) {
        Integer elem = intArray[i];
        assertEquals(i, elem.intValue());
      }
      try {
        Object[] objArray = NO_OPTIMIZE_FALSE ? new Object[1] : intArray;
        assertTrue(objArray instanceof Integer[]);
        objArray[0] = new Object();
        fail("expected ArrayStoreException");
      } catch (ArrayStoreException e) {
      }
    }
  }

  private void checkListSizeAndContent(List<Integer> in, int... expected) {
    assertEquals(expected.length, in.size());
    for (int i = 0; i < expected.length; i++) {
      assertEquals(expected[i], (int) in.get(i));
    }
  }

  private List<Integer> createListWithContent(int[] in) {
    List<Integer> results = new ArrayList<Integer>();
    for (int i = 0; i < in.length; i++) {
      results.add(in[i]);
    }
    return results;
  }
}
