package fr.umlv.queue;
 
import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.NoSuchElementException;

import org.junit.Test;

public class ResizeableFifoTest {
  @Test
  public void shouldResizeWhenAddingMoreThanCapacityElements() {
    ResizeableFifo<String> fifo = new ResizeableFifo<>(1);
    fifo.offer("foo");
    fifo.offer("bar");
    assertEquals(2, fifo.size());
    assertEquals("foo", fifo.poll());
    assertEquals("bar", fifo.poll());
    assertEquals(0, fifo.size());
  }
  
  @Test
  public void shouldKeepElementsInOrderWhenResizing() {
    ResizeableFifo<String> fifo = new ResizeableFifo<>(2);
    fifo.offer("foo");
    fifo.poll();
    fifo.offer("bar");
    fifo.offer("baz");
    fifo.offer("bat");
    assertEquals(3, fifo.size());
    assertEquals("bar", fifo.poll());
    assertEquals("baz", fifo.poll());
    assertEquals("bat", fifo.poll());
    assertEquals(0, fifo.size());
  }
  
  @Test
  public void shouldResizeALot() {
    ResizeableFifo<Integer> fifo = new ResizeableFifo<>(1);
    fifo.offer(-1);
    fifo.poll();
    for(int i = 0; i < 10_000; i++) {
      fifo.offer(i);
    }
    for(int i = 0; i < 10_000; i++) {
      assertEquals(i, (int)fifo.poll());
    }
  }
  
  // --- FIFO test
  
  @Test(expected=IllegalArgumentException.class)
  public void shouldGetAnErrorWhenCapacityIsNonPositive() {
    new ResizeableFifo<Object>(-3);
  }
  
  @Test(expected=IllegalArgumentException.class)
  public void shouldGetAnErrorWhenCapacityIsNull() {
    new ResizeableFifo<Object>(0);
  }

  @Test
  public void shouldGetNullWhenPollingFromEmptyFifo() {
    ResizeableFifo<Object> fifo = new ResizeableFifo<>(1);
    assertNull(fifo.poll());
  }

  @Test(expected=NullPointerException.class)
  public void shouldGetAnErrorWhenOfferingNull() {
    ResizeableFifo<Object> fifo = new ResizeableFifo<>(234);
    fifo.offer(null);
  }
  
  @Test
  public void shouldGetOfferedValueWhenPolling() {
    ResizeableFifo<Integer> fifo = new ResizeableFifo<>(2);
    fifo.offer(9);
    assertEquals(9, (int)fifo.poll());
    fifo.offer(2);
    fifo.offer(37);
    assertEquals(2, (int)fifo.poll());
    fifo.offer(12);
    assertEquals(37, (int)fifo.poll());
    assertEquals(12, (int)fifo.poll());
  }
  
  @Test
  public void shouldGetOfferedValueWhenPollingWithMixedTypes() {
    ResizeableFifo<Object> fifo = new ResizeableFifo<>(20);
    for(int i = 0; i < 20; i++) {
      fifo.offer(i);
    }
    assertEquals(0, (int)fifo.poll());
    fifo.offer("foo");
    for(int i = 1; i < 20; i++) {
      assertEquals(i, (int)fifo.poll());
    }
    assertEquals("foo", fifo.poll());
  }

  @Test
  public void shoulgGetACorrectSize() {
    ResizeableFifo<String> fifo = new ResizeableFifo<>(2);
    assertEquals(0, fifo.size());
    fifo.offer("foo");
    assertEquals(1, fifo.size());
    fifo.offer("bar");
    assertEquals(2, fifo.size());
    fifo.poll();
    assertEquals(1, fifo.size());
    fifo.poll();
    assertEquals(0, fifo.size());
  }
  
  @Test
  public void shouldAnswerZeroWhenAskedForTheSizeOfAnEmptyFifo() {
    ResizeableFifo<Object> fifo = new ResizeableFifo<>(1);
    assertEquals(0, fifo.size());
  }
  
  @Test
  public void shouldAnswerOneWhenAskedForTheSizeAfterOneOffer() {
    ResizeableFifo<String> fifo = new ResizeableFifo<>(1);
    fifo.offer("dooh");
    assertEquals(1, fifo.size());
  }
  
  @Test
  public void shouldFindFifoEmptyOnlyAfterRemovingAllElement() {
    ResizeableFifo<String> fifo = new ResizeableFifo<>(2);
    assertTrue(fifo.isEmpty());
    fifo.offer("oof");
    assertFalse(fifo.isEmpty());
    fifo.offer("rab");
    assertFalse(fifo.isEmpty());
    fifo.poll();
    fifo.poll();
    assertTrue(fifo.isEmpty());
  }
 
  
  @Test
  public void shouldPrintEmptyFifo() {
    ResizeableFifo<Object> fifo = new ResizeableFifo<>(23);
    assertEquals("[]", fifo.toString());
  }
  
  @Test
  public void shouldPrintFifoWithOneElement() {
    ResizeableFifo<String> fifo = new ResizeableFifo<>(23);
    fifo.offer("joe");
    assertEquals("[joe]", fifo.toString());
  }
  
  @Test
  public void shouldPrintFifoWithTwoElements() {
    ResizeableFifo<String> fifo = new ResizeableFifo<>(23);
    fifo.offer("jane");
    fifo.offer("doe");
    assertEquals("[jane, doe]", fifo.toString());
  }
  
  @Test
  public void shouldBeAbleToAddMoreThanCapacityAfterRemoval() {
    ResizeableFifo<String> fifo = new ResizeableFifo<>(2);
    fifo.offer("foo");
    fifo.poll();
    fifo.offer("1");
    fifo.offer("2");
    assertEquals("[1, 2]", fifo.toString());
  }
  
  @Test
  public void shouldNotAffectFifoWhenPrinting() {
    ResizeableFifo<Integer> fifo = new ResizeableFifo<>(200);
    ArrayList<Integer> list = new ArrayList<>();
    for(int i = 0; i < 100; i++) {
      fifo.offer(i);
      list.add(i);
    }
    assertEquals(list.toString(), fifo.toString());
    for(int i = 0; i < 100; i++) {
      assertEquals(i, (int)fifo.poll());
    }
  }
  
  @Test
  public void shouldPrintFifoInTheSameWayAsAList() {
    ResizeableFifo<Integer> fifo = new ResizeableFifo<>(99);
    ArrayList<Integer> list = new ArrayList<>();
    for(int i = 0; i < 99; i++) {
      fifo.offer(i);
      list.add(i);
    }
    assertEquals(list.toString(), fifo.toString());
  }
  
  @Test
  public void shouldGetTheRightTypeOfIterator() {
    ResizeableFifo<String> fifo = new ResizeableFifo<>(1);
    Iterator<String> it = fifo.iterator();
    assertNotNull(it);
  }
  
  @Test(expected = NoSuchElementException.class)
  public void shouldGetAnErrorWhenAskingNextWhenDoesNotHaveNext() {
    ResizeableFifo<String> fifo = new ResizeableFifo<>(1);
    fifo.offer("bar");
    fifo.poll();
    Iterator<String> it = fifo.iterator();
    it.next();
  }
  
  @Test
  public void shouldNotGetSideEffectsWhenUsingIteratorHasNext() {
    ResizeableFifo<Integer> fifo = new ResizeableFifo<>(3);
    fifo.offer(117);
    fifo.offer(440);
    Iterator<Integer> it = fifo.iterator();
    assertTrue(it.hasNext());
    assertTrue(it.hasNext());
    assertEquals(117, (int)it.next());
    assertTrue(it.hasNext());
    assertTrue(it.hasNext());
    assertEquals(440, (int)it.next());
    assertFalse(it.hasNext());
    assertFalse(it.hasNext());
  }
   
  @Test
  public void shouldIterateProperlyWhenTheNumberofOffersOvertakesOriginalCapacity() {
    ResizeableFifo<Integer> fifo = new ResizeableFifo<>(2);
    fifo.offer(42);
    fifo.poll();
    fifo.offer(55);
    fifo.offer(333);
    Iterator<Integer> it = fifo.iterator();
    assertTrue(it.hasNext());
    assertTrue(it.hasNext());
    assertEquals(55, (int)it.next());
    assertTrue(it.hasNext());
    assertTrue(it.hasNext());
    assertEquals(333, (int)it.next());
    assertFalse(it.hasNext());
    assertFalse(it.hasNext());
  }
  
  @Test
  public void shouldBeAbleToIterateTwice() {
    ResizeableFifo<Integer> fifo = new ResizeableFifo<>(1);
    fifo.offer(898);
    
    Iterator<Integer> it = fifo.iterator();
    assertTrue(it.hasNext());
    assertTrue(it.hasNext());
    assertEquals(898, (int)it.next());
    assertFalse(it.hasNext());
    assertFalse(it.hasNext());
    Iterator<Integer> it2 = fifo.iterator();
    assertTrue(it2.hasNext());
    assertTrue(it2.hasNext());
    assertEquals(898, (int)it2.next());
    assertFalse(it2.hasNext());
    assertFalse(it2.hasNext());
  }
  
  @Test
  public void shouldGetConsistentAnswersFromHasNextWhenEmpty() {
    ResizeableFifo<Object> fifo = new ResizeableFifo<>(1);
    Iterator<Object> it = fifo.iterator();
    assertFalse(it.hasNext());
    assertFalse(it.hasNext());
    assertFalse(it.hasNext());
  }
  
  @Test
  public void shouldGetConsistentAnswersFromHasNextWhenNotEmpty() {
    ResizeableFifo<Object> fifo = new ResizeableFifo<>(1);
    fifo.offer("one");
    Iterator<Object> it = fifo.iterator();
    assertTrue(it.hasNext());
    assertTrue(it.hasNext());
    assertTrue(it.hasNext());
  }
  
  @Test
  public void shouldIterateOverALargeNumberOfElements() {
    ResizeableFifo<Integer> fifo = new ResizeableFifo<>(10_000);
    for(int i=0; i<10_000; i++) {
      fifo.offer(i);
    }
    int i = 0;
    Iterator<Integer> it = fifo.iterator();
    while(it.hasNext()) {
      assertEquals(i++, (int)it.next());
    }
    assertEquals(10000, fifo.size());
  }
  
  @Test(expected=UnsupportedOperationException.class)
  public void shouldGetAnErrorWhenTryingToUseIteratorRemove() {
    ResizeableFifo<String> fifo = new ResizeableFifo<>(1);
    fifo.offer("foooo");
    fifo.iterator().remove();
  }
  
  @Test
  public void shouldBeAbleToUseImplicitForEachLoop() {
    ResizeableFifo<Integer> fifo = new ResizeableFifo<>(100);
    fifo.offer(222);
    fifo.poll();
    
    for(int i=0; i<100; i++) {
      fifo.offer(i);
    }
    int i = 0; 
    for(int value: fifo) {
      assertEquals(i++, value);
    }
    assertEquals(100, fifo.size());
  }
  
  // ---
  
  
  @Test
  public void shoulGetNullWhenPeekingFromAnEmptyFifo() {
    ResizeableFifo<String> fifo = new ResizeableFifo<>(1);
    assertNull(fifo.peek());
  }
  
  @Test
  public void shouldPeekCorrectlyFromNonEmptyFifo() {
    ResizeableFifo<Integer> fifo = new ResizeableFifo<>(1);
    fifo.offer(117);
    assertEquals(117, (int)fifo.peek());
    fifo.poll();
    fifo.offer(145);
    fifo.offer(541);
    assertEquals(145, (int)fifo.peek());
    fifo.poll();
    assertEquals(541, (int)fifo.peek());
  }
  
  @Test(expected=NoSuchElementException.class)
  public void shouldGetAnErrorWhenTryingToGetElementFromEmptyFifo() {
    ResizeableFifo<Integer> fifo = new ResizeableFifo<>(1);
    fifo.element();
  }
  
  @Test
  public void shouldAddAllElementsFromOneFifoToAnother() {
    ResizeableFifo<Integer> fifo = new ResizeableFifo<>(1);
    for(int i=0; i<3; i++) {
      fifo.offer(i);
    }
    
    ResizeableFifo<Object> fifo2 = new ResizeableFifo<>(1);
    fifo2.addAll(fifo);
    
    assertEquals(fifo2.size(), fifo.size());
    
    Iterator<Object> it2 = fifo2.iterator();
    Iterator<Integer> it = fifo.iterator();
    while(it2.hasNext()) {
      assertTrue(it.hasNext());
      assertEquals(it2.next(), it.next());
    }
  }
}
