/*
 * Copyright (C) 2008 The Guava Authors
 *
 * 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.common.collect.testing.testers;

import static com.google.common.collect.testing.Helpers.copyToList;
import static com.google.common.collect.testing.Helpers.getMethod;
import static com.google.common.collect.testing.features.CollectionFeature.SERIALIZABLE_INCLUDING_VIEWS;
import static com.google.common.collect.testing.features.CollectionSize.ONE;
import static com.google.common.collect.testing.features.CollectionSize.ZERO;
import static com.google.common.collect.testing.features.ListFeature.SUPPORTS_ADD_WITH_INDEX;
import static com.google.common.collect.testing.features.ListFeature.SUPPORTS_REMOVE_WITH_INDEX;
import static com.google.common.collect.testing.features.ListFeature.SUPPORTS_SET;
import static com.google.common.collect.testing.testers.ReflectionFreeAssertThrows.assertThrows;
import static java.util.Arrays.asList;
import static java.util.Collections.emptyList;
import static java.util.Collections.singletonList;

import com.google.common.annotations.GwtCompatible;
import com.google.common.annotations.GwtIncompatible;
import com.google.common.annotations.J2ktIncompatible;
import com.google.common.collect.testing.features.CollectionFeature;
import com.google.common.collect.testing.features.CollectionSize;
import com.google.common.collect.testing.features.ListFeature;
import com.google.common.testing.SerializableTester;
import java.lang.reflect.Method;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import org.junit.Ignore;

/**
 * A generic JUnit test which tests {@code subList()} operations on a list. Can't be invoked
 * directly; please see {@link com.google.common.collect.testing.ListTestSuiteBuilder}.
 *
 * @author Chris Povirk
 */
@GwtCompatible
@Ignore("test runners must not instantiate and run this directly, only via suites we build")
// @Ignore affects the Android test runner, which respects JUnit 4 annotations on JUnit 3 tests.
@SuppressWarnings("JUnit4ClassUsedInJUnit3")
public class ListSubListTester<E> extends AbstractListTester<E> {
  public void testSubList_startNegative() {
    assertThrows(IndexOutOfBoundsException.class, () -> getList().subList(-1, 0));
  }

  public void testSubList_endTooLarge() {
    assertThrows(IndexOutOfBoundsException.class, () -> getList().subList(0, getNumElements() + 1));
  }

  public void testSubList_startGreaterThanEnd() {
    try {
      getList().subList(1, 0);
      fail("subList(1, 0) should throw");
    } catch (IndexOutOfBoundsException expected) {
    } catch (IllegalArgumentException expected) {
      /*
       * The subList() docs claim that this should be an
       * IndexOutOfBoundsException, but many JDK implementations throw
       * IllegalArgumentException:
       * https://bugs.openjdk.org/browse/JDK-4506427
       */
    }
  }

  @SuppressWarnings("EmptyList") // ImmutableList doesn't support nullable element types
  public void testSubList_empty() {
    assertEquals("subList(0, 0) should be empty", emptyList(), getList().subList(0, 0));
  }

  public void testSubList_entireList() {
    assertEquals(
        "subList(0, size) should be equal to the original list",
        getList(),
        getList().subList(0, getNumElements()));
  }

  @ListFeature.Require(SUPPORTS_REMOVE_WITH_INDEX)
  @CollectionSize.Require(absent = ZERO)
  public void testSubList_subListRemoveAffectsOriginal() {
    List<E> subList = getList().subList(0, 1);
    subList.remove(0);
    List<E> expected = asList(createSamplesArray()).subList(1, getNumElements());
    expectContents(expected);
  }

  @ListFeature.Require(SUPPORTS_REMOVE_WITH_INDEX)
  @CollectionSize.Require(absent = ZERO)
  public void testSubList_subListClearAffectsOriginal() {
    List<E> subList = getList().subList(0, 1);
    subList.clear();
    List<E> expected = asList(createSamplesArray()).subList(1, getNumElements());
    expectContents(expected);
  }

  @ListFeature.Require(SUPPORTS_ADD_WITH_INDEX)
  public void testSubList_subListAddAffectsOriginal() {
    List<E> subList = getList().subList(0, 0);
    subList.add(e3());
    expectAdded(0, e3());
  }

  @ListFeature.Require(SUPPORTS_SET)
  @CollectionSize.Require(absent = ZERO)
  public void testSubList_subListSetAffectsOriginal() {
    List<E> subList = getList().subList(0, 1);
    subList.set(0, e3());
    List<E> expected = copyToList(createSamplesArray());
    expected.set(0, e3());
    expectContents(expected);
  }

  @ListFeature.Require(SUPPORTS_SET)
  @CollectionSize.Require(absent = ZERO)
  public void testSubList_originalListSetAffectsSubList() {
    List<E> subList = getList().subList(0, 1);
    getList().set(0, e3());
    assertEquals(
        "A set() call to a list after a sublist has been created "
            + "should be reflected in the sublist",
        singletonList(e3()),
        subList);
  }

  @ListFeature.Require(SUPPORTS_REMOVE_WITH_INDEX)
  @CollectionSize.Require(absent = {ZERO, ONE})
  public void testSubList_subListRemoveAffectsOriginalLargeList() {
    List<E> subList = getList().subList(1, 3);
    subList.remove(e2());
    List<E> expected = copyToList(createSamplesArray());
    expected.remove(2);
    expectContents(expected);
  }

  @ListFeature.Require(SUPPORTS_ADD_WITH_INDEX)
  @CollectionSize.Require(absent = {ZERO, ONE})
  public void testSubList_subListAddAtIndexAffectsOriginalLargeList() {
    List<E> subList = getList().subList(2, 3);
    subList.add(0, e3());
    expectAdded(2, e3());
  }

  @ListFeature.Require(SUPPORTS_SET)
  @CollectionSize.Require(absent = {ZERO, ONE})
  public void testSubList_subListSetAffectsOriginalLargeList() {
    List<E> subList = getList().subList(1, 2);
    subList.set(0, e3());
    List<E> expected = copyToList(createSamplesArray());
    expected.set(1, e3());
    expectContents(expected);
  }

  @ListFeature.Require(SUPPORTS_SET)
  @CollectionSize.Require(absent = {ZERO, ONE})
  public void testSubList_originalListSetAffectsSubListLargeList() {
    List<E> subList = getList().subList(1, 3);
    getList().set(1, e3());
    assertEquals(
        "A set() call to a list after a sublist has been created "
            + "should be reflected in the sublist",
        asList(e3(), e2()),
        subList);
  }

  @SuppressWarnings("EmptyList") // ImmutableList doesn't support nullable element types
  public void testSubList_ofSubListEmpty() {
    List<E> subList = getList().subList(0, 0).subList(0, 0);
    assertEquals("subList(0, 0).subList(0, 0) should be an empty list", emptyList(), subList);
  }

  @CollectionSize.Require(absent = {ZERO, ONE})
  public void testSubList_ofSubListNonEmpty() {
    List<E> subList = getList().subList(0, 2).subList(1, 2);
    assertEquals(
        "subList(0, 2).subList(1, 2) "
            + "should be a single-element list of the element at index 1",
        singletonList(getOrderedElements().get(1)),
        subList);
  }

  @CollectionSize.Require(absent = {ZERO})
  public void testSubList_size() {
    List<E> list = getList();
    int size = getNumElements();
    assertEquals(size, list.subList(0, size).size());
    assertEquals(size - 1, list.subList(0, size - 1).size());
    assertEquals(size - 1, list.subList(1, size).size());
    assertEquals(0, list.subList(size, size).size());
    assertEquals(0, list.subList(0, 0).size());
  }

  @CollectionSize.Require(absent = {ZERO})
  public void testSubList_isEmpty() {
    List<E> list = getList();
    int size = getNumElements();
    for (List<E> subList :
        asList(
            list.subList(0, size),
            list.subList(0, size - 1),
            list.subList(1, size),
            list.subList(0, 0),
            list.subList(size, size))) {
      assertEquals(subList.size() == 0, subList.isEmpty());
    }
  }

  @CollectionSize.Require(absent = {ZERO, ONE})
  public void testSubList_get() {
    List<E> list = getList();
    int size = getNumElements();
    List<E> copy = list.subList(0, size);
    List<E> head = list.subList(0, size - 1);
    List<E> tail = list.subList(1, size);
    assertEquals(list.get(0), copy.get(0));
    assertEquals(list.get(size - 1), copy.get(size - 1));
    assertEquals(list.get(1), tail.get(0));
    assertEquals(list.get(size - 1), tail.get(size - 2));
    assertEquals(list.get(0), head.get(0));
    assertEquals(list.get(size - 2), head.get(size - 2));
    for (List<E> subList : asList(copy, head, tail)) {
      for (int index : asList(-1, subList.size())) {
        assertThrows(IndexOutOfBoundsException.class, () -> subList.get(index));
      }
    }
  }

  @CollectionSize.Require(absent = {ZERO, ONE})
  public void testSubList_contains() {
    List<E> list = getList();
    int size = getNumElements();
    List<E> copy = list.subList(0, size);
    List<E> head = list.subList(0, size - 1);
    List<E> tail = list.subList(1, size);
    assertTrue(copy.contains(list.get(0)));
    assertTrue(head.contains(list.get(0)));
    assertTrue(tail.contains(list.get(1)));
    // The following assumes all elements are distinct.
    assertTrue(copy.contains(list.get(size - 1)));
    assertTrue(head.contains(list.get(size - 2)));
    assertTrue(tail.contains(list.get(size - 1)));
    assertFalse(head.contains(list.get(size - 1)));
    assertFalse(tail.contains(list.get(0)));
  }

  @CollectionSize.Require(absent = {ZERO, ONE})
  public void testSubList_indexOf() {
    List<E> list = getList();
    int size = getNumElements();
    List<E> copy = list.subList(0, size);
    List<E> head = list.subList(0, size - 1);
    List<E> tail = list.subList(1, size);
    assertEquals(0, copy.indexOf(list.get(0)));
    assertEquals(0, head.indexOf(list.get(0)));
    assertEquals(0, tail.indexOf(list.get(1)));
    // The following assumes all elements are distinct.
    assertEquals(size - 1, copy.indexOf(list.get(size - 1)));
    assertEquals(size - 2, head.indexOf(list.get(size - 2)));
    assertEquals(size - 2, tail.indexOf(list.get(size - 1)));
    assertEquals(-1, head.indexOf(list.get(size - 1)));
    assertEquals(-1, tail.indexOf(list.get(0)));
  }

  @CollectionSize.Require(absent = {ZERO, ONE})
  public void testSubList_lastIndexOf() {
    List<E> list = getList();
    int size = list.size();
    List<E> copy = list.subList(0, size);
    List<E> head = list.subList(0, size - 1);
    List<E> tail = list.subList(1, size);
    assertEquals(size - 1, copy.lastIndexOf(list.get(size - 1)));
    assertEquals(size - 2, head.lastIndexOf(list.get(size - 2)));
    assertEquals(size - 2, tail.lastIndexOf(list.get(size - 1)));
    // The following assumes all elements are distinct.
    assertEquals(0, copy.lastIndexOf(list.get(0)));
    assertEquals(0, head.lastIndexOf(list.get(0)));
    assertEquals(0, tail.lastIndexOf(list.get(1)));
    assertEquals(-1, head.lastIndexOf(list.get(size - 1)));
    assertEquals(-1, tail.lastIndexOf(list.get(0)));
  }

  @CollectionFeature.Require(SERIALIZABLE_INCLUDING_VIEWS)
  public void testReserializeWholeSubList() {
    SerializableTester.reserializeAndAssert(getList().subList(0, getNumElements()));
  }

  @CollectionFeature.Require(SERIALIZABLE_INCLUDING_VIEWS)
  public void testReserializeEmptySubList() {
    SerializableTester.reserializeAndAssert(getList().subList(0, 0));
  }

  @CollectionFeature.Require(SERIALIZABLE_INCLUDING_VIEWS)
  @CollectionSize.Require(absent = {ZERO, ONE})
  public void testReserializeSubList() {
    SerializableTester.reserializeAndAssert(getList().subList(0, 2));
  }

  /**
   * Returns the {@link Method} instance for {@link #testSubList_originalListSetAffectsSubList()} so
   * that tests of {@link CopyOnWriteArrayList} can suppress them with {@code
   * FeatureSpecificTestSuiteBuilder.suppressing()} until <a
   * href="https://bugs.openjdk.org/browse/JDK-6570631">JDK-6570631</a> is fixed.
   */
  @J2ktIncompatible
  @GwtIncompatible // reflection
  public static Method getSubListOriginalListSetAffectsSubListMethod() {
    return getMethod(ListSubListTester.class, "testSubList_originalListSetAffectsSubList");
  }

  /**
   * Returns the {@link Method} instance for {@link
   * #testSubList_originalListSetAffectsSubListLargeList()} so that tests of {@link
   * CopyOnWriteArrayList} can suppress them with {@code
   * FeatureSpecificTestSuiteBuilder.suppressing()} until <a
   * href="https://bugs.openjdk.org/browse/JDK-6570631">JDK-6570631</a> is fixed.
   */
  @J2ktIncompatible
  @GwtIncompatible // reflection
  public static Method getSubListOriginalListSetAffectsSubListLargeListMethod() {
    return getMethod(ListSubListTester.class, "testSubList_originalListSetAffectsSubListLargeList");
  }

  /**
   * Returns the {@link Method} instance for {@link
   * #testSubList_subListRemoveAffectsOriginalLargeList()} so that tests of {@link
   * CopyOnWriteArrayList} can suppress it with {@code
   * FeatureSpecificTestSuiteBuilder.suppressing()} until <a
   * href="https://bugs.openjdk.org/browse/JDK-6570575">JDK-6570575</a> is fixed.
   */
  @J2ktIncompatible
  @GwtIncompatible // reflection
  public static Method getSubListSubListRemoveAffectsOriginalLargeListMethod() {
    return getMethod(ListSubListTester.class, "testSubList_subListRemoveAffectsOriginalLargeList");
  }

  /*
   * TODO: perform all List tests on subList(), but beware infinite recursion
   */
}
