/*
 * Copyright 2014 Ben Manes. All Rights Reserved.
 *
 * 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.github.benmanes.caffeine.cache;

import static com.github.benmanes.caffeine.testing.IsEmptyIterable.deeplyEmpty;
import static com.google.common.collect.Iterators.elementsEqual;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.contains;
import static org.hamcrest.Matchers.hasSize;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.not;
import static org.hamcrest.Matchers.nullValue;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Deque;
import java.util.List;
import java.util.NoSuchElementException;

import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;

import com.github.benmanes.caffeine.cache.AccessOrderDeque.AccessOrder;
import com.github.benmanes.caffeine.cache.LinkedDeque.PeekingIterator;
import com.github.benmanes.caffeine.cache.WriteOrderDeque.WriteOrder;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import com.google.common.collect.Iterators;

/**
 * A unit-test for the @{@link AbstractLinkedDeque} implementations.
 *
 * @author ben.manes@gmail.com (Ben Manes)
 */
public final class LinkedDequeTest {
  static final int SIZE = 100;

  @Test(dataProvider = "empty")
  public void clear_whenEmpty(Deque<?> deque) {
    deque.clear();
    assertThat(deque, is(deeplyEmpty()));
  }

  @Test(dataProvider = "full")
  public void clear_whenPopulated(Deque<?> deque) {
    deque.clear();
    assertThat(deque, is(deeplyEmpty()));
  }

  @Test(dataProvider = "empty")
  public void isEmpty_whenEmpty(Deque<?> deque) {
    assertThat(deque.isEmpty(), is(true));
  }

  @Test(dataProvider = "full")
  public void isEmpty_whenPopulated(Deque<?> deque) {
    assertThat(deque.isEmpty(), is(false));
  }

  @Test(dataProvider = "empty")
  public void size_whenEmpty(Deque<?> deque) {
    assertThat(deque.size(), is(0));
  }

  @Test(dataProvider = "full")
  public void size_whenPopulated(Deque<?> deque) {
    assertThat(deque.size(), is(SIZE));
    assertThat(Iterables.size(deque), is(SIZE));
  }

  @Test(dataProvider = "empty")
  public void contains_withNull(Deque<?> deque) {
    assertThat(deque.contains(null), is(false));
  }

  @Test(dataProvider = "full")
  public void contains_whenFound(LinkedDeque<LinkedValue> deque) {
    assertThat(deque.contains(Iterables.get(deque, SIZE / 2)), is(true));
  }

  @Test(dataProvider = "full")
  public void contains_whenNotFound(LinkedDeque<LinkedValue> deque) {
    LinkedValue unlinked = new LinkedValue(1);
    assertThat(deque.contains(unlinked), is(false));
  }

  /* ---------------- Move -------------- */

  @Test(dataProvider = "full")
  public void moveToFront_first(LinkedDeque<LinkedValue> deque) {
    checkMoveToFront(deque, deque.getFirst());
  }

  @Test(dataProvider = "full")
  public void moveToFront_middle(LinkedDeque<LinkedValue> deque) {
    checkMoveToFront(deque, Iterables.get(deque, SIZE / 2));
  }

  @Test(dataProvider = "full")
  public void moveToFront_last(LinkedDeque<LinkedValue> deque) {
    checkMoveToFront(deque, deque.getLast());
  }

  private void checkMoveToFront(LinkedDeque<LinkedValue> deque, LinkedValue element) {
    deque.moveToFront(element);
    assertThat(deque.peekFirst(), is(element));
    assertThat(deque.size(), is(SIZE));
  }

  @Test(dataProvider = "full")
  public void moveToBack_first(LinkedDeque<LinkedValue> deque) {
    checkMoveToBack(deque, deque.getFirst());
  }

  @Test(dataProvider = "full")
  public void moveToBack_middle(LinkedDeque<LinkedValue> deque) {
    checkMoveToBack(deque, Iterables.get(deque, SIZE / 2));
  }

  @Test(dataProvider = "full")
  public void moveToBack_last(LinkedDeque<LinkedValue> deque) {
    checkMoveToBack(deque, deque.getLast());
  }

  private void checkMoveToBack(LinkedDeque<LinkedValue> deque, LinkedValue element) {
    deque.moveToBack(element);
    assertThat(deque.size(), is(SIZE));
    assertThat(deque.getLast(), is(element));
  }

  /* ---------------- First / Last -------------- */

  @Test(dataProvider = "empty")
  public void isFirst_whenEmpty(LinkedDeque<LinkedValue> deque) {
    assertThat(deque.isFirst(new LinkedValue(0)), is(false));
    assertThat(deque.isFirst(null), is(false));
  }

  @Test(dataProvider = "full")
  public void isFirst_whenPopulated(AbstractLinkedDeque<LinkedValue> deque) {
    LinkedValue first = deque.first;
    assertThat(deque.isFirst(first), is(true));
    assertThat(deque.first, is(first));
    assertThat(deque, hasSize(SIZE));
    assertThat(deque.contains(first), is(true));
  }

  @Test(dataProvider = "empty")
  public void isLast_whenEmpty(LinkedDeque<LinkedValue> deque) {
    assertThat(deque.isLast(new LinkedValue(0)), is(false));
    assertThat(deque.isLast(null), is(false));
  }

  @Test(dataProvider = "full")
  public void isLast_whenPopulated(AbstractLinkedDeque<LinkedValue> deque) {
    LinkedValue last = deque.last;
    assertThat(deque.isLast(last), is(true));
    assertThat(deque.last, is(last));
    assertThat(deque, hasSize(SIZE));
    assertThat(deque.contains(last), is(true));
  }

  /* ---------------- Peek -------------- */

  @Test(dataProvider = "empty")
  public void peek_whenEmpty(LinkedDeque<LinkedValue> deque) {
    assertThat(deque.peek(), is(nullValue()));
  }

  @Test(dataProvider = "full")
  public void peek_whenPopulated(AbstractLinkedDeque<LinkedValue> deque) {
    LinkedValue first = deque.first;
    assertThat(deque.peek(), is(first));
    assertThat(deque.first, is(first));
    assertThat(deque, hasSize(SIZE));
    assertThat(deque.contains(first), is(true));
  }

  @Test(dataProvider = "empty")
  public void peekFirst_whenEmpty(LinkedDeque<LinkedValue> deque) {
    assertThat(deque.peekFirst(), is(nullValue()));
  }

  @Test(dataProvider = "full")
  public void peekFirst_whenPopulated(AbstractLinkedDeque<LinkedValue> deque) {
    LinkedValue first = deque.first;
    assertThat(deque.peekFirst(), is(first));
    assertThat(deque.first, is(first));
    assertThat(deque, hasSize(SIZE));
    assertThat(deque.contains(first), is(true));
  }

  @Test(dataProvider = "empty")
  public void peekLast_whenEmpty(LinkedDeque<LinkedValue> deque) {
    assertThat(deque.peekLast(), is(nullValue()));
  }

  @Test(dataProvider = "full")
  public void peekLast_whenPopulated(AbstractLinkedDeque<LinkedValue> deque) {
    LinkedValue last = deque.last;
    assertThat(deque.peekLast(), is(last));
    assertThat(deque.last, is(last));
    assertThat(deque, hasSize(SIZE));
    assertThat(deque.contains(last), is(true));
  }

  /* ---------------- Get -------------- */

  @Test(dataProvider = "empty", expectedExceptions = NoSuchElementException.class)
  public void getFirst_whenEmpty(LinkedDeque<LinkedValue> deque) {
    deque.getFirst();
  }

  @Test(dataProvider = "full")
  public void getFirst_whenPopulated(AbstractLinkedDeque<LinkedValue> deque) {
    LinkedValue first = deque.first;
    assertThat(deque.getFirst(), is(first));
    assertThat(deque.first, is(first));
    assertThat(deque, hasSize(SIZE));
    assertThat(deque.contains(first), is(true));
  }

  @Test(dataProvider = "empty", expectedExceptions = NoSuchElementException.class)
  public void getLast_whenEmpty(LinkedDeque<LinkedValue> deque) {
    deque.getLast();
  }

  @Test(dataProvider = "full")
  public void getLast_whenPopulated(AbstractLinkedDeque<LinkedValue> deque) {
    LinkedValue last = deque.last;
    assertThat(deque.getLast(), is(last));
    assertThat(deque.last, is(last));
    assertThat(deque, hasSize(SIZE));
    assertThat(deque.contains(last), is(true));
  }

  /* ---------------- Element -------------- */

  @Test(dataProvider = "empty", expectedExceptions = NoSuchElementException.class)
  public void element_whenEmpty(LinkedDeque<LinkedValue> deque) {
    deque.element();
  }

  @Test(dataProvider = "full")
  public void element_whenPopulated(AbstractLinkedDeque<LinkedValue> deque) {
    LinkedValue first = deque.first;
    assertThat(deque.element(), is(first));
    assertThat(deque.first, is(first));
    assertThat(deque, hasSize(SIZE));
    assertThat(deque.contains(first), is(true));
  }

  /* ---------------- Offer -------------- */

  @Test(dataProvider = "empty")
  public void offer_whenEmpty(LinkedDeque<LinkedValue> deque) {
    LinkedValue value = new LinkedValue(1);
    assertThat(deque.offer(value), is(true));
    assertThat(deque.peekFirst(), is(value));
    assertThat(deque.peekLast(), is(value));
    assertThat(deque, hasSize(1));
  }

  @Test(dataProvider = "full")
  public void offer_whenPopulated(LinkedDeque<LinkedValue> deque) {
    LinkedValue value = new LinkedValue(SIZE);
    assertThat(deque.offer(value), is(true));
    assertThat(deque.peekFirst(), is(not(value)));
    assertThat(deque.peekLast(), is(value));
    assertThat(deque, hasSize(SIZE + 1));
  }

  @Test(dataProvider = "full")
  public void offer_whenLinked(LinkedDeque<LinkedValue> deque) {
    assertThat(deque.offer(deque.peek()), is(false));
    assertThat(deque, hasSize(SIZE));
  }

  @Test(dataProvider = "empty")
  public void offerFirst_whenEmpty(LinkedDeque<LinkedValue> deque) {
    LinkedValue value = new LinkedValue(1);
    assertThat(deque.offerFirst(value), is(true));
    assertThat(deque.peekFirst(), is(value));
    assertThat(deque.peekLast(), is(value));
    assertThat(deque, hasSize(1));
  }

  @Test(dataProvider = "full")
  public void offerFirst_whenPopulated(LinkedDeque<LinkedValue> deque) {
    LinkedValue value = new LinkedValue(SIZE);
    assertThat(deque.offerFirst(value), is(true));
    assertThat(deque.peekFirst(), is(value));
    assertThat(deque.peekLast(), is(not(value)));
    assertThat(deque, hasSize(SIZE + 1));
  }

  @Test(dataProvider = "full")
  public void offerFirst_whenLinked(LinkedDeque<LinkedValue> deque) {
    assertThat(deque.offerFirst(deque.peek()), is(false));
    assertThat(deque, hasSize(SIZE));
  }

  @Test(dataProvider = "empty")
  public void offerLast_whenEmpty(LinkedDeque<LinkedValue> deque) {
    LinkedValue value = new LinkedValue(1);
    assertThat(deque.offerLast(value), is(true));
    assertThat(deque.peekFirst(), is(value));
    assertThat(deque.peekLast(), is(value));
    assertThat(deque, hasSize(1));
  }

  @Test(dataProvider = "full")
  public void offerLast_whenPopulated(LinkedDeque<LinkedValue> deque) {
    LinkedValue value = new LinkedValue(SIZE);
    assertThat(deque.offerLast(value), is(true));
    assertThat(deque.peekFirst(), is(not(value)));
    assertThat(deque.peekLast(), is(value));
    assertThat(deque, hasSize(SIZE + 1));
  }

  @Test(dataProvider = "full")
  public void offerLast_whenLinked(LinkedDeque<LinkedValue> deque) {
    assertThat(deque.offerLast(deque.peek()), is(false));
    assertThat(deque, hasSize(SIZE));
  }

  /* ---------------- Add -------------- */

  @Test(dataProvider = "empty")
  public void add_whenEmpty(LinkedDeque<LinkedValue> deque) {
    LinkedValue value = new LinkedValue(1);
    assertThat(deque.add(value), is(true));
    assertThat(deque.peekFirst(), is(value));
    assertThat(deque.peekLast(), is(value));
    assertThat(deque, hasSize(1));
  }

  @Test(dataProvider = "full")
  public void add_whenPopulated(LinkedDeque<LinkedValue> deque) {
    LinkedValue value = new LinkedValue(SIZE);
    assertThat(deque.add(value), is(true));
    assertThat(deque.peekFirst(), is(not(value)));
    assertThat(deque.peekLast(), is(value));
    assertThat(deque, hasSize(SIZE + 1));
  }

  @Test(dataProvider = "full")
  public void add_whenLinked(LinkedDeque<LinkedValue> deque) {
    assertThat(deque.add(deque.peek()), is(false));
  }

  @Test(dataProvider = "empty")
  public void addFirst_whenEmpty(LinkedDeque<LinkedValue> deque) {
    LinkedValue value = new LinkedValue(1);
    deque.addFirst(value);
    assertThat(deque.peekFirst(), is(value));
    assertThat(deque.peekLast(), is(value));
    assertThat(deque, hasSize(1));
  }

  @Test(dataProvider = "full")
  public void addFirst_whenPopulated(LinkedDeque<LinkedValue> deque) {
    LinkedValue value = new LinkedValue(SIZE);
    deque.addFirst(value);
    assertThat(deque.peekFirst(), is(value));
    assertThat(deque.peekLast(), is(not(value)));
    assertThat(deque, hasSize(SIZE + 1));
  }

  @Test(dataProvider = "full", expectedExceptions = IllegalArgumentException.class)
  public void addFirst_whenLinked(LinkedDeque<LinkedValue> deque) {
    deque.addFirst(deque.peek());
  }

  @Test(dataProvider = "empty")
  public void addLast_whenEmpty(LinkedDeque<LinkedValue> deque) {
    LinkedValue value = new LinkedValue(1);
    deque.addLast(value);
    assertThat(deque.peekFirst(), is(value));
    assertThat(deque.peekLast(), is(value));
    assertThat(deque, hasSize(1));
  }

  @Test(dataProvider = "full")
  public void addLast_whenPopulated(LinkedDeque<LinkedValue> deque) {
    LinkedValue value = new LinkedValue(SIZE);
    deque.addLast(value);
    assertThat(deque.peekFirst(), is(not(value)));
    assertThat(deque.peekLast(), is(value));
    assertThat(deque, hasSize(SIZE + 1));
  }

  @Test(dataProvider = "full", expectedExceptions = IllegalArgumentException.class)
  public void addLast_whenLinked(LinkedDeque<LinkedValue> deque) {
    deque.addLast(deque.peek());
  }

  @Test(dataProvider = "empty")
  public void addAll_withEmpty(LinkedDeque<LinkedValue> deque) {
    assertThat(deque.addAll(ImmutableList.<LinkedValue>of()), is(false));
    assertThat(deque, is(deeplyEmpty()));
  }

  @Test(dataProvider = "empty")
  public void addAll_withPopulated(LinkedDeque<LinkedValue> deque) {
    List<LinkedValue> expected = new ArrayList<>();
    populate(expected);
    assertThat(deque.addAll(expected), is(true));
    assertThat(deque, contains(expected.toArray(new LinkedValue[0])));
  }

  @Test(dataProvider = "full")
  @SuppressWarnings("ModifyingCollectionWithItself")
  public void addAll_withSelf(LinkedDeque<LinkedValue> deque) {
    assertThat(deque.addAll(deque), is(false));
  }

  /* ---------------- Poll -------------- */

  @Test(dataProvider = "empty")
  public void poll_whenEmpty(LinkedDeque<LinkedValue> deque) {
    assertThat(deque.poll(), is(nullValue()));
  }

  @Test(dataProvider = "full")
  public void poll_whenPopulated(LinkedDeque<LinkedValue> deque) {
    LinkedValue first = deque.peek();
    assertThat(deque.poll(), is(first));
    assertThat(deque, hasSize(SIZE - 1));
    assertThat(deque.contains(first), is(false));
  }

  @Test(dataProvider = "full")
  public void poll_toEmpty(LinkedDeque<LinkedValue> deque) {
    LinkedValue value;
    while ((value = deque.poll()) != null) {
      assertThat(deque.contains(value), is(false));
    }
    assertThat(deque, is(deeplyEmpty()));
  }

  @Test(dataProvider = "empty")
  public void pollFirst_whenEmpty(LinkedDeque<LinkedValue> deque) {
    assertThat(deque.pollFirst(), is(nullValue()));
  }

  @Test(dataProvider = "full")
  public void pollFirst_whenPopulated(LinkedDeque<LinkedValue> deque) {
    LinkedValue first = deque.peekFirst();
    assertThat(deque.pollFirst(), is(first));
    assertThat(deque, hasSize(SIZE - 1));
    assertThat(deque.contains(first), is(false));
  }

  @Test(dataProvider = "full")
  public void pollFirst_toEmpty(LinkedDeque<LinkedValue> deque) {
    LinkedValue value;
    while ((value = deque.pollFirst()) != null) {
      assertThat(deque.contains(value), is(false));
    }
    assertThat(deque, is(deeplyEmpty()));
  }

  @Test(dataProvider = "empty")
  public void pollLast_whenEmpty(LinkedDeque<LinkedValue> deque) {
    assertThat(deque.pollLast(), is(nullValue()));
  }

  @Test(dataProvider = "full")
  public void pollLast_whenPopulated(LinkedDeque<LinkedValue> deque) {
    LinkedValue last = deque.peekLast();
    assertThat(deque.pollLast(), is(last));
    assertThat(deque, hasSize(SIZE - 1));
    assertThat(deque.contains(last), is(false));
  }

  @Test(dataProvider = "full")
  public void pollLast_toEmpty(LinkedDeque<LinkedValue> deque) {
    LinkedValue value;
    while ((value = deque.pollLast()) != null) {
      assertThat(deque.contains(value), is(false));
    }
    assertThat(deque, is(deeplyEmpty()));
  }

  /* ---------------- Remove -------------- */

  @Test(dataProvider = "empty", expectedExceptions = NoSuchElementException.class)
  public void remove_whenEmpty(LinkedDeque<LinkedValue> deque) {
    deque.remove();
  }

  @Test(dataProvider = "full")
  public void remove_whenPopulated(LinkedDeque<LinkedValue> deque) {
    LinkedValue first = deque.peekFirst();
    assertThat(deque.remove(), is(first));
    assertThat(deque, hasSize(SIZE - 1));
    assertThat(deque.contains(first), is(false));
  }

  @Test(dataProvider = "full")
  public void remove_toEmpty(LinkedDeque<LinkedValue> deque) {
    while (!deque.isEmpty()) {
      LinkedValue value = deque.remove();
      assertThat(deque.contains(value), is(false));
    }
    assertThat(deque, is(deeplyEmpty()));
  }

  @Test(dataProvider = "empty")
  public void removeElement_notFound(LinkedDeque<LinkedValue> deque) {
    assertThat(deque.remove(new LinkedValue(0)), is(false));
  }

  @Test(dataProvider = "full")
  public void removeElement_whenFound(LinkedDeque<LinkedValue> deque) {
    LinkedValue first = deque.peekFirst();
    assertThat(deque.remove(first), is(true));
    assertThat(deque, hasSize(SIZE - 1));
    assertThat(deque.contains(first), is(false));
  }

  @Test(dataProvider = "full")
  public void removeElement_toEmpty(LinkedDeque<LinkedValue> deque) {
    while (!deque.isEmpty()) {
      LinkedValue value = deque.peek();
      assertThat(deque.remove(value), is(true));
      assertThat(deque.contains(value), is(false));
    }
    assertThat(deque, is(deeplyEmpty()));
  }

  @Test(dataProvider = "empty", expectedExceptions = NoSuchElementException.class)
  public void removeFirst_whenEmpty(LinkedDeque<LinkedValue> deque) {
    deque.removeFirst();
  }

  @Test(dataProvider = "full")
  public void removeFirst_whenPopulated(LinkedDeque<LinkedValue> deque) {
    LinkedValue first = deque.peekFirst();
    assertThat(deque.removeFirst(), is(first));
    assertThat(deque, hasSize(SIZE - 1));
    assertThat(deque.contains(first), is(false));
  }

  @Test(dataProvider = "full")
  public void removeFirst_toEmpty(LinkedDeque<LinkedValue> deque) {
    while (!deque.isEmpty()) {
      LinkedValue value = deque.removeFirst();
      assertThat(deque.contains(value), is(false));
    }
    assertThat(deque, is(deeplyEmpty()));
  }

  @Test(dataProvider = "empty", expectedExceptions = NoSuchElementException.class)
  public void removeLast_whenEmpty(LinkedDeque<LinkedValue> deque) {
    deque.removeLast();
  }

  @Test(dataProvider = "full")
  public void removeLast_whenPopulated(LinkedDeque<LinkedValue> deque) {
    LinkedValue last = deque.peekLast();
    assertThat(deque.removeLast(), is(last));
    assertThat(deque, hasSize(SIZE - 1));
    assertThat(deque.contains(last), is(false));
  }

  @Test(dataProvider = "full")
  public void removeLast_toEmpty(LinkedDeque<LinkedValue> deque) {
    while (!deque.isEmpty()) {
      LinkedValue value = deque.removeLast();
      assertThat(deque.contains(value), is(false));
    }
    assertThat(deque, is(deeplyEmpty()));
  }

  @Test(dataProvider = "empty")
  public void removeFirstOccurrence_notFound(LinkedDeque<LinkedValue> deque) {
    assertThat(deque.removeFirstOccurrence(new LinkedValue(0)), is(false));
  }

  @Test(dataProvider = "full")
  public void removeFirstOccurrence_whenFound(LinkedDeque<LinkedValue> deque) {
    LinkedValue first = deque.peekFirst();
    assertThat(deque.removeFirstOccurrence(first), is(true));
    assertThat(deque, hasSize(SIZE - 1));
    assertThat(deque.contains(first), is(false));
  }

  @Test(dataProvider = "full")
  public void removeFirstOccurrence_toEmpty(LinkedDeque<LinkedValue> deque) {
    while (!deque.isEmpty()) {
      LinkedValue value = deque.peek();
      assertThat(deque.removeFirstOccurrence(value), is(true));
      assertThat(deque.contains(value), is(false));
    }
    assertThat(deque, is(deeplyEmpty()));
  }

  @Test(dataProvider = "empty")
  public void removeLastOccurrence_notFound(LinkedDeque<LinkedValue> deque) {
    assertThat(deque.removeLastOccurrence(new LinkedValue(0)), is(false));
  }

  @Test(dataProvider = "full")
  public void removeLastOccurrence_whenFound(LinkedDeque<LinkedValue> deque) {
    LinkedValue first = deque.peekFirst();
    assertThat(deque.removeLastOccurrence(first), is(true));
    assertThat(deque, hasSize(SIZE - 1));
    assertThat(deque.contains(first), is(false));
  }

  @Test(dataProvider = "full")
  public void removeLastOccurrence_toEmpty(LinkedDeque<LinkedValue> deque) {
    while (!deque.isEmpty()) {
      LinkedValue value = deque.peek();
      assertThat(deque.removeLastOccurrence(value), is(true));
      assertThat(deque.contains(value), is(false));
    }
    assertThat(deque, is(deeplyEmpty()));
  }

  @Test(dataProvider = "empty")
  public void removeAll_withEmpty(LinkedDeque<LinkedValue> deque) {
    assertThat(deque.removeAll(ImmutableList.of()), is(false));
    assertThat(deque, is(deeplyEmpty()));
  }

  @Test(dataProvider = "full")
  public void remove_withPopulated(LinkedDeque<LinkedValue> deque) {
    LinkedValue first = deque.peekFirst();
    assertThat(deque.removeAll(ImmutableList.of(first)), is(true));
    assertThat(deque, hasSize(SIZE - 1));
    assertThat(deque.contains(first), is(false));
  }

  @Test(dataProvider = "full")
  public void removeAll_toEmpty(LinkedDeque<LinkedValue> deque) {
    assertThat(deque.removeAll(ImmutableList.copyOf(deque)), is(true));
    assertThat(deque, is(deeplyEmpty()));
  }

  /* ---------------- Stack -------------- */

  @Test(dataProvider = "empty")
  public void push_whenEmpty(LinkedDeque<LinkedValue> deque) {
    LinkedValue value = new LinkedValue(1);
    deque.push(value);
    assertThat(deque.peekFirst(), is(value));
    assertThat(deque.peekLast(), is(value));
    assertThat(deque, hasSize(1));
  }

  @Test(dataProvider = "full")
  public void push_whenPopulated(LinkedDeque<LinkedValue> deque) {
    LinkedValue value = new LinkedValue(SIZE);
    deque.push(value);
    assertThat(deque.peekFirst(), is(value));
    assertThat(deque.peekLast(), is(not(value)));
    assertThat(deque, hasSize(SIZE + 1));
  }

  @Test(dataProvider = "full", expectedExceptions = IllegalArgumentException.class)
  public void push_whenLinked(LinkedDeque<LinkedValue> deque) {
    deque.push(deque.peek());
  }

  @Test(dataProvider = "empty", expectedExceptions = NoSuchElementException.class)
  public void pop_whenEmpty(LinkedDeque<LinkedValue> deque) {
    deque.pop();
  }

  @Test(dataProvider = "full")
  public void pop_whenPopulated(LinkedDeque<LinkedValue> deque) {
    LinkedValue first = deque.peekFirst();
    assertThat(deque.pop(), is(first));
    assertThat(deque, hasSize(SIZE - 1));
    assertThat(deque.contains(first), is(false));
  }

  @Test(dataProvider = "full")
  public void pop_toEmpty(LinkedDeque<LinkedValue> deque) {
    while (!deque.isEmpty()) {
      LinkedValue value = deque.pop();
      assertThat(deque.contains(value), is(false));
    }
    assertThat(deque, is(deeplyEmpty()));
  }

  /* ---------------- Iterators -------------- */

  @Test(dataProvider = "empty", expectedExceptions = NoSuchElementException.class)
  public void iterator_noMoreElements(LinkedDeque<LinkedValue> deque) {
    deque.iterator().next();
  }

  @Test(dataProvider = "empty")
  public void iterator_whenEmpty(LinkedDeque<LinkedValue> deque) {
    assertThat(deque.iterator().peek(), is(nullValue()));
    assertThat(deque.iterator().hasNext(), is(false));
  }

  @Test(dataProvider = "full")
  public void iterator_whenWarmed(LinkedDeque<LinkedValue> deque) {
    List<LinkedValue> expected = new ArrayList<>();
    populate(expected);

    assertThat(deque.peek(), is(not(nullValue())));
    assertThat(elementsEqual(deque.iterator(), expected.iterator()), is(true));
  }

  @Test(dataProvider = "full")
  public void iterator_removal(LinkedDeque<LinkedValue> deque) {
    PeekingIterator<LinkedValue> iterator = deque.iterator();
    LinkedValue value = iterator.next();
    iterator.remove();

    int remaining = 0;
    while (iterator.hasNext()) {
      assertThat(iterator.next(), is(not(value)));
      remaining++;
    }
    assertThat(remaining, is(SIZE - 1));
    assertThat(deque, hasSize(SIZE - 1));
  }

  @Test(dataProvider = "empty", expectedExceptions = NoSuchElementException.class)
  public void descendingIterator_noMoreElements(LinkedDeque<LinkedValue> deque) {
    deque.descendingIterator().next();
  }

  @Test(dataProvider = "empty")
  public void descendingIterator_whenEmpty(LinkedDeque<LinkedValue> deque) {
    assertThat(deque.descendingIterator().peek(), is(nullValue()));
    assertThat(deque.descendingIterator().hasNext(), is(false));
  }

  @Test(dataProvider = "full")
  public void descendingIterator_whenWarmed(LinkedDeque<LinkedValue> deque) {
    List<LinkedValue> expected = new ArrayList<>();
    populate(expected);
    Collections.reverse(expected);

    assertThat(deque.descendingIterator().peek(), is(not(nullValue())));
    assertThat(elementsEqual(deque.descendingIterator(), expected.iterator()), is(true));
  }

  @Test(dataProvider = "full")
  public void descendingIterator_removal(LinkedDeque<LinkedValue> deque) {
    PeekingIterator<LinkedValue> iterator = deque.descendingIterator();
    LinkedValue value = iterator.next();
    iterator.remove();

    int remaining = 0;
    while (iterator.hasNext()) {
      assertThat(iterator.next(), is(not(value)));
      remaining++;
    }
    assertThat(remaining, is(SIZE - 1));
    assertThat(deque, hasSize(SIZE - 1));
  }

  @Test(dataProvider = "full")
  public void concat(LinkedDeque<LinkedValue> deque) {
    List<LinkedValue> expect = ImmutableList.copyOf(
        Iterators.concat(deque.iterator(), deque.descendingIterator()));
    Iterable<LinkedValue> actual = () -> PeekingIterator.concat(
        deque.iterator(), deque.descendingIterator());
    assertThat(actual, contains(expect.toArray(new LinkedValue[0])));
  }

  @Test(dataProvider = "empty", expectedExceptions = NoSuchElementException.class)
  public void concat_noMoreElements(LinkedDeque<LinkedValue> deque) {
    PeekingIterator.concat(deque.iterator(), deque.iterator()).next();
  }

  @Test(dataProvider = "full")
  public void comparing(LinkedDeque<LinkedValue> deque) {
    List<LinkedValue> expect = ImmutableList.copyOf(
        Iterators.concat(deque.iterator(), deque.descendingIterator()));
    PeekingIterator<LinkedValue> actual = PeekingIterator.comparing(
        deque.iterator(), deque.descendingIterator(), (a, b) -> 1);
    assertThat(actual.peek(), is(expect.get(0)));
    assertThat((Iterable<LinkedValue>) () -> actual, contains(expect.toArray(new LinkedValue[0])));
  }

  @Test(dataProvider = "full")
  public void comparing_uneven(LinkedDeque<LinkedValue> deque) {
    PeekingIterator<LinkedValue> empty = new AccessOrderDeque<LinkedValue>().iterator();
    PeekingIterator<?> left = PeekingIterator.comparing(deque.iterator(), empty, (a, b) -> 1);
    PeekingIterator<?> right = PeekingIterator.comparing(deque.iterator(), empty, (a, b) -> 1);

    assertThat(left.peek(), is(deque.getFirst()));
    assertThat(right.peek(), is(deque.getFirst()));
  }

  /* ---------------- Deque providers -------------- */

  @DataProvider(name = "empty")
  public Object[][] providesEmptyDeque() {
    return new Object[][] {
        { new AccessOrderDeque<LinkedValue>() },
        { new WriteOrderDeque<LinkedValue>() },
    };
  }

  @DataProvider(name = "full")
  public Object[][] providesWarmedDeque() {
    Deque<LinkedValue> accessOrder = new AccessOrderDeque<LinkedValue>();
    Deque<LinkedValue> writeOrder = new WriteOrderDeque<LinkedValue>();
    populate(accessOrder);
    populate(writeOrder);
    return new Object[][] { { accessOrder }, { writeOrder }};
  }

  void populate(Collection<LinkedValue> collection) {
    for (int i = 0; i < SIZE; i++) {
      collection.add(new LinkedValue(i));
    }
  }

  static final class LinkedValue implements AccessOrder<LinkedValue>, WriteOrder<LinkedValue> {
    LinkedValue prev;
    LinkedValue next;
    final int value;

    LinkedValue(int value) {
      this.value = value;
    }

    @Override
    public LinkedValue getPreviousInAccessOrder() {
      return prev;
    }

    @Override
    public void setPreviousInAccessOrder(LinkedValue prev) {
      this.prev = prev;
    }

    @Override
    public LinkedValue getNextInAccessOrder() {
      return next;
    }

    @Override
    public void setNextInAccessOrder(LinkedValue next) {
      this.next = next;
    }

    @Override
    public LinkedValue getPreviousInWriteOrder() {
      return prev;
    }

    @Override
    public void setPreviousInWriteOrder(LinkedValue prev) {
      this.prev = prev;
    }

    @Override
    public LinkedValue getNextInWriteOrder() {
      return next;
    }

    @Override
    public void setNextInWriteOrder(LinkedValue next) {
      this.next = next;
    }

    @Override
    public boolean equals(Object o) {
      return (o instanceof LinkedValue) && (value == ((LinkedValue) o).value);
    }

    @Override
    public int hashCode() {
      return value;
    }

    @Override
    public String toString() {
      return String.format("value=%s prev=%s, next=%s]", value,
          (prev == null) ? null : prev.value,
          (next == null) ? null : next.value);
    }
  }
}
