/*
   Copyright (c) 2012 LinkedIn Corp.

   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.
*/

/**
 * $Id: $
 */

package com.linkedin.r2.util;

import org.testng.Assert;
import org.testng.annotations.Test;

import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.Iterator;
import java.util.List;
import java.util.NoSuchElementException;
import java.util.Queue;
import java.util.Random;

/**
 * @author Steven Ihde
 * @version $Revision: $
 */

public class TestLinkedDeque
{

  @Test
  public void testAdd()
  {
    List<Integer> control = new ArrayList<>(Arrays.asList(1, 2, 3));
    LinkedDeque<Integer> q = new LinkedDeque<>(control);

    control.add(99);
    q.add(99);

    Assert.assertEquals(q, control);
  }

  @Test
  public void testAddLast()
  {
    List<Integer> control = new ArrayList<>(Arrays.asList(1, 2, 3));
    LinkedDeque<Integer> q = new LinkedDeque<>(control);

    control.add(99);
    q.addLast(99);

    Assert.assertEquals(q, control);
  }

  @Test
  public void testAddFirst()
  {
    List<Integer> control = new ArrayList<>(Arrays.asList(1, 2, 3));
    LinkedDeque<Integer> q = new LinkedDeque<>(control);

    control.add(0, 99);
    q.addFirst(99);

    Assert.assertEquals(q, control);
  }

  @Test
  public void testOffer()
  {
    List<Integer> control = new ArrayList<>(Arrays.asList(1, 2, 3));
    LinkedDeque<Integer> q = new LinkedDeque<>(control);

    control.add(99);
    Assert.assertTrue(q.offer(99));

    Assert.assertEquals(q, control);
  }

  @Test
  public void testOfferLast()
  {
    List<Integer> control = new ArrayList<>(Arrays.asList(1, 2, 3));
    LinkedDeque<Integer> q = new LinkedDeque<>(control);

    control.add(99);
    Assert.assertTrue(q.offerLast(99));

    Assert.assertEquals(q, control);
  }

  @Test
  public void testOfferFirst()
  {
    List<Integer> control = new ArrayList<>(Arrays.asList(1, 2, 3));
    LinkedDeque<Integer> q = new LinkedDeque<>(control);

    control.add(0, 99);
    Assert.assertTrue(q.offerFirst(99));

    Assert.assertEquals(q, control);
  }

  @Test
  public void testRemove()
  {
    List<Integer> control = new ArrayList<>(Arrays.asList(1, 2, 3));
    LinkedDeque<Integer> q = new LinkedDeque<>(control);

    Assert.assertEquals(q.remove(), control.remove(0));
    Assert.assertEquals(q, control);
  }

  @Test
  public void testRemoveFirst()
  {
    List<Integer> control = new ArrayList<>(Arrays.asList(1, 2, 3));
    LinkedDeque<Integer> q = new LinkedDeque<>(control);

    Assert.assertEquals(q.removeFirst(), control.remove(0));
    Assert.assertEquals(q, control);
  }

  @Test
  public void testRemoveLast()
  {
    List<Integer> control = new ArrayList<>(Arrays.asList(1, 2, 3));
    LinkedDeque<Integer> q = new LinkedDeque<>(control);

    Assert.assertEquals(q.removeLast(), control.remove(control.size() - 1));
    Assert.assertEquals(q, control);
  }

  @Test
  public void testPoll()
  {
    List<Integer> control = new ArrayList<>(Arrays.asList(1, 2, 3));
    LinkedDeque<Integer> q = new LinkedDeque<>(control);

    Assert.assertEquals(q.poll(), control.remove(0));
    Assert.assertEquals(q, control);
  }

  @Test
  public void testPollFirst()
  {
    List<Integer> control = new ArrayList<>(Arrays.asList(1, 2, 3));
    LinkedDeque<Integer> q = new LinkedDeque<>(control);

    Assert.assertEquals(q.pollFirst(), control.remove(0));
    Assert.assertEquals(q, control);
  }

  @Test
  public void testPollLast()
  {
    List<Integer> control = new ArrayList<>(Arrays.asList(1, 2, 3));
    LinkedDeque<Integer> q = new LinkedDeque<>(control);

    Assert.assertEquals(q.pollLast(), control.remove(control.size() - 1));
    Assert.assertEquals(q, control);
  }

  @Test
  public void testPeek()
  {
    List<Integer> control = new ArrayList<>(Arrays.asList(1, 2, 3));
    LinkedDeque<Integer> q = new LinkedDeque<>(control);

    Assert.assertEquals(q.peek(), control.get(0));
    Assert.assertEquals(q, control);
  }

  @Test
  public void testPeekFirst()
  {
    List<Integer> control = new ArrayList<>(Arrays.asList(1, 2, 3));
    LinkedDeque<Integer> q = new LinkedDeque<>(control);

    Assert.assertEquals(q.peekFirst(), control.get(0));
    Assert.assertEquals(q, control);
  }

  @Test
  public void testPeekLast()
  {
    List<Integer> control = new ArrayList<>(Arrays.asList(1, 2, 3));
    LinkedDeque<Integer> q = new LinkedDeque<>(control);

    Assert.assertEquals(q.peekLast(), control.get(control.size() - 1));
    Assert.assertEquals(q, control);
  }

  @Test
  public void testEmptyRemove()
  {
    LinkedDeque<Object> q = new LinkedDeque<>();
    try
    {
      q.remove();
      Assert.fail("remove on empty queue should fail");
    }
    catch (NoSuchElementException e)
    {
      // Expected
    }
  }

  @Test
  public void testEmptyRemoveFirst()
  {
    LinkedDeque<Object> q = new LinkedDeque<>();
    try
    {
      q.removeFirst();
      Assert.fail("removeFirst on empty queue should fail");
    }
    catch (NoSuchElementException e)
    {
      // Expected
    }
  }

  @Test
  public void testEmptyRemoveLast()
  {
    LinkedDeque<Object> q = new LinkedDeque<>();
    try
    {
      q.removeLast();
      Assert.fail("removeLast on empty queue should fail");
    }
    catch (NoSuchElementException e)
    {
      // Expected
    }
  }

  @Test
  public void testEmptyPoll()
  {
    LinkedDeque<Object> q = new LinkedDeque<>();
    Assert.assertNull(q.poll(), "poll on empty queue should return null");
  }

  @Test
  public void testEmptyPollFirst()
  {
    LinkedDeque<Object> q = new LinkedDeque<>();
    Assert.assertNull(q.pollFirst(), "pollFirst on empty queue should return null");
  }

  @Test
  public void testEmptyPollLast()
  {
    LinkedDeque<Object> q = new LinkedDeque<>();
    Assert.assertNull(q.pollLast(), "pollLast on empty queue should return null");
  }

  @Test
  public void testEmptyPeek()
  {
    LinkedDeque<Object> q = new LinkedDeque<>();
    Assert.assertNull(q.peek(), "peek on empty queue should return null");
  }

  @Test
  public void testEmptyPeekFirst()
  {
    LinkedDeque<Object> q = new LinkedDeque<>();
    Assert.assertNull(q.peekFirst(), "peekFirst on empty queue should return null");
  }

  @Test
  public void testEmptyPeekLast()
  {
    LinkedDeque<Object> q = new LinkedDeque<>();
    Assert.assertNull(q.peekLast(), "peekLast on empty queue should return null");
  }

  @Test
  public void testAddNull()
  {
    LinkedDeque<Object> q = new LinkedDeque<>();
    try
    {
      q.add(null);
      Assert.fail("add null should have failed");
    }
    catch (NullPointerException e)
    {
      // expected
    }
  }

  @Test
  public void testAddFirstNull()
  {
    LinkedDeque<Object> q = new LinkedDeque<>();
    try
    {
      q.addFirst(null);
      Assert.fail("addFirst null should have failed");
    }
    catch (NullPointerException e)
    {
      // expected
    }
  }

  @Test
  public void testAddLastNull()
  {
    LinkedDeque<Object> q = new LinkedDeque<>();
    try
    {
      q.addLast(null);
      Assert.fail("addLast null should have failed");
    }
    catch (NullPointerException e)
    {
      // expected
    }
  }

  @Test
  public void testOfferNull()
  {
    LinkedDeque<Object> q = new LinkedDeque<>();
    try
    {
      q.offer(null);
      Assert.fail("offer null should have failed");
    }
    catch (NullPointerException e)
    {
      // expected
    }
  }

  @Test
  public void testOfferFirstNull()
  {
    LinkedDeque<Object> q = new LinkedDeque<>();
    try
    {
      q.offerFirst(null);
      Assert.fail("offerFirst null should have failed");
    }
    catch (NullPointerException e)
    {
      // expected
    }
  }

  @Test
  public void testOfferLastNull()
  {
    LinkedDeque<Object> q = new LinkedDeque<>();
    try
    {
      q.offerLast(null);
      Assert.fail("offerLast null should have failed");
    }
    catch (NullPointerException e)
    {
      // expected
    }
  }

  @Test
  public void testForwardGeneral()
  {
    LinkedDeque<Object> q = new LinkedDeque<>();
    Queue<Object> control = new ArrayDeque<>();

    for (int i = 0; i < 10; i++)
    {
      for (int j = 0; j < i; j++)
      {
        Object o = new Object();
        q.add(o);
        control.add(o);
        Assert.assertEquals(q, control);
      }
      for (int j = 0; j < i; j++)
      {
        Object o = q.remove();
        Object o2 = control.remove();

        Assert.assertEquals(o, o2);
        Assert.assertEquals(q, control);
      }
    }
  }

  @Test
  public void testReverseGeneral()
  {
    LinkedDeque<Object> q = new LinkedDeque<>();
    Deque<Object> control = new ArrayDeque<>();

    for (int i = 0; i < 10; i++)
    {
      for (int j = 0; j < i; j++)
      {
        Object o = new Object();
        q.addFirst(o);
        control.addFirst(o);
        Assert.assertEquals(q, control);
      }
      for (int j = 0; j < i; j++)
      {
        Object o = q.removeLast();
        Object o2 = control.removeLast();

        Assert.assertEquals(o, o2);
        Assert.assertEquals(q, control);
      }
    }
  }

  @Test
  public void testEquals()
  {
    List<Integer> list = Arrays.asList(1, 2, 3);
    LinkedDeque<Integer> q = new LinkedDeque<>(list);
    Assert.assertEquals(q, list);
    Assert.assertEquals(new LinkedDeque<Integer>(), Collections.emptyList());
    Assert.assertNotSame(q, Collections.emptyList());
  }

  @Test
  public void testEarlyRemoveFails()
  {
    LinkedDeque<Integer> q = new LinkedDeque<>(Arrays.asList(1, 2, 3));
    try
    {
      q.iterator().remove();
    }
    catch (IllegalStateException e)
    {
      // Expected
    }
  }

  @Test
  public void testDoubleRemoveFails()
  {
    LinkedDeque<Integer> q = new LinkedDeque<>(Arrays.asList(1, 2, 3));
    Iterator<Integer> i = q.iterator();
    i.next();
    i.remove();
    try
    {
      i.remove();
    }
    catch (IllegalStateException e)
    {
      // Expected
    }
  }

  @Test
  public void testIteratorRemoveHead()
  {
    testIteratorRemoval(0, 3, true);
  }

  @Test
  public void testIteratorRemoveMiddle()
  {
    testIteratorRemoval(1, 3, true);
  }

  @Test
  public void testIteratorRemoveTail()
  {
    testIteratorRemoval(2, 3, true);
  }

  @Test
  public void testIteratorRemoval()
  {
    for (int i = 1; i < 10; i++)
    {
      for (int j = 0; j < i; j++)
      {
        testIteratorRemoval(j, i, true);
      }
    }
  }

  @Test
  public void testDescIteratorRemoveHead()
  {
    testIteratorRemoval(0, 3, false);
  }

  @Test
  public void testDescIteratorRemoveMiddle()
  {
    testIteratorRemoval(1, 3, false);
  }

  @Test
  public void testDescIteratorRemoveTail()
  {
    testIteratorRemoval(2, 3, false);
  }

  @Test
  public void testDescIteratorRemoval()
  {
    for (int i = 1; i < 10; i++)
    {
      for (int j = 0; j < i; j++)
      {
        testIteratorRemoval(j, i, false);
      }
    }
  }

  private void testIteratorRemoval(int target, int size, boolean ascending)
  {
    try
    {
      List<Integer> list = new ArrayList<>(size);
      for (int i = 0; i < size; i++)
      {
        list.add(i);
      }
      LinkedDeque<Integer> q = new LinkedDeque<>(list);
      Iterator<Integer> it = (ascending ? q.iterator() : q.descendingIterator());
      for (int i = 0; i < target + 1; i++)
      {
        it.next();
      }
      it.remove();

      list.remove(ascending ? target : size - target - 1);

      Assert.assertEquals(q, list, "Iterator " + (ascending ? "ascending" : "descending") +
              " removal of " + target + " failed with list size " + size);
    }
    catch (Exception e)
    {
      Assert.fail("Iterator " + (ascending ? "ascending" : "descending") + " removal of " +
                          target + " failed with list size " + size, e);
    }
  }

  // This tests the LinkedDeque-specific methods which remove nodes from the interior of the queue.
  @Test
  public void bigTest()
  {
    Random rand = new Random(9939393);

    List<Object> control = new ArrayList<>();

    List<LinkedDeque.Node<Object>> nodes = new ArrayList<>();
    LinkedDeque<Object> queue = new LinkedDeque<>();

    for (int i = 0; i < 100000; i++)
    {
      int r = rand.nextInt(10);
      if (r < 6 || control.isEmpty())
      {
        Object o = new Object();
        control.add(o);
        nodes.add(queue.addLastNode(o));
      }
      else
      {
        int index = rand.nextInt(control.size());
        Object o = control.remove(index);
        LinkedDeque.Node<Object> node = nodes.remove(index);
        Object o2 = queue.removeNode(node);
        Assert.assertEquals(o, o2, "objects were not equal");
      }
    }

    Assert.assertEquals(control.size(), nodes.size());
    Assert.assertEquals(control.size(), queue.size());

    int size = control.size();
    for (int i = 0; i < size; i++)
    {
      Object o = control.remove(0);
      Object o2 = queue.poll();
      Assert.assertEquals(o, o2);
    }
    Assert.assertTrue(control.isEmpty());
    Assert.assertTrue(queue.isEmpty());
  }
}
