package fr.umlv.funutil;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.NoSuchElementException;
import java.util.Random;

import org.junit.Assert;
import org.junit.Test;

public class FunUtilsTest {
  @Test
  public void filterIterator() {
    Iterator<Integer> it = FunUtils.filterIterator(Arrays.asList(1, 2, 4, 7, 8), new Filter<Integer>() {
      @Override
      public boolean accept(Integer element) {
        return element%2 == 0;
      }
    });
    
    Assert.assertTrue(it.hasNext());
    Assert.assertTrue(it.hasNext()); // twice
    Assert.assertEquals(2, (int)it.next());
    Assert.assertTrue(it.hasNext());
    
    Assert.assertEquals(4, (int)it.next());
    Assert.assertEquals(8, (int)it.next());  // don't call hasNext()
    
    Assert.assertFalse(it.hasNext());
    
    try {
      it.next();
      Assert.fail();
    } catch(NoSuchElementException e) {
      // ok
    }
  }
  
  @Test(expected=UnsupportedOperationException.class)
  public void filterIteratorRemove() {
    Iterator<Object> iterator = FunUtils.filterIterator(Collections.emptyList(), FunUtils.trueFilter());
    iterator.remove();
  }
  
  private static <E, C extends Collection<? super E>> C as(Iterable<? extends E> it, C c) {
    for(E element: it) {
      c.add(element);
    }
    return c;
  }
   
  @Test
  public void asFunIterableAsIterable() {
    Random random = new Random(0);
    ArrayList<Integer> list = new ArrayList<>();
    for(int i=0; i<10000; i++) {
      list.add(random.nextInt(10000));
    }
    Iterable<Integer> iterable = FunUtils.asFunIterable(list, FunUtils.<Integer>trueFilter());
    Assert.assertEquals(list, as(iterable, new ArrayList<>()));
  }
  
  @Test
  public void asFunIterableForEach() {
    Random random = new Random(9);
    ArrayList<Integer> list = new ArrayList<>();
    for(int i=0; i<10000; i++) {
      list.add(random.nextInt(10000));
    }
    FunIterable<Number> iterable = FunUtils.<Number>asFunIterable(list, new Filter<Number>() {
      @Override
      public boolean accept(Number number) {
        return number.intValue() % 2 == 1;
      }
    });
    
    iterable.forEach(new Block<Object>() {
      @Override
      public void apply(Object element) {
        if (((Integer)element) %2 == 0) {
          Assert.fail();
        }
      }
    });
  }
  
  @Test
  public void asFunIterableToList() {
    Random random = new Random(9);
    ArrayList<Integer> list = new ArrayList<>();
    ArrayList<Integer> list2 = new ArrayList<>();
    for(int i=0; i<10000; i++) {
      list.add(random.nextInt(10000));
      if (Integer.toHexString(i).contains("A")) {
        list2.add(i);
      }
    }
    ArrayList<Object> list3 = new ArrayList<>();
    FunUtils.asFunIterable(list, new Filter<Integer>() {
      @Override
      public boolean accept(Integer element) {
        return Integer.toHexString(element).contains("A");
      }
    }).toList(list3);
    Assert.assertEquals(list2, list3);
  }
  
  @Test
  public void asFunIterableFilter() {
    ArrayList<Integer> list = new ArrayList<>();
    ArrayList<Integer> list2 = new ArrayList<>();
    for(int i=0; i<10000; i++) {
      list.add(i);
      if (i % 6 == 0) {
        list2.add(i);
      }
    }
    FunIterable<Integer> funIterable = FunUtils.asFunIterable(list, new Filter<Integer>() { 
      @Override
      public boolean accept(Integer element) {
        return element % 2 == 0;
      }
    });
    FunIterable<Integer> funIterable2 = funIterable.filter(new Filter<Integer>() {
      @Override
      public boolean accept(Integer element) {
        return element % 3 == 0;
      }
    });
    Assert.assertEquals(list2, as(funIterable2, new ArrayList<Integer>()));
  }
  
  @Test
  public void trueFilter() {
    Filter<Integer> f = FunUtils.trueFilter();
    Filter<String> f2 = FunUtils.trueFilter();
    Assert.assertTrue(f.accept(42));
    Assert.assertTrue(f2.accept("42"));
    Assert.assertSame(f, f2);
  }
  
  @Test
  public void and() {
    Filter<String> and = FunUtils.and(FunUtils.<CharSequence>trueFilter(), FunUtils.<String>trueFilter());
    Assert.assertTrue(and.accept("foo"));
    
    try {
      FunUtils.and(new Filter<Object>() {
        @Override
        public boolean accept(Object element) {
          return false;
        }
      }, new Filter<Object>() {
        @Override
        public boolean accept(Object element) {
          throw new IllegalStateException();
        }
      }).accept("bar!");
    } catch(IllegalStateException e) {
      Assert.fail();
    }
  }
}
