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 FunUtils3Test {
  @Test
  public void mapFilterIterator() {
    Iterator<String> it = FunUtils3.mapFilterIterator(Arrays.asList(1, 3, 4, 7, 8),
        new Filter<Integer>() {
          @Override
          public boolean accept(Integer element) {
            return element %2 == 1;
          }
        },
        new Mapper<String, Integer>() {
          @Override
          public String map(Integer element) {
            return Integer.toString(element);
        }
    });
    
    Assert.assertTrue(it.hasNext());
    Assert.assertTrue(it.hasNext()); // twice
    Assert.assertEquals("1", it.next());
    Assert.assertTrue(it.hasNext());
    
    Assert.assertEquals("3", it.next());
    Assert.assertEquals("7", 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 mapperIteratorRemove() {
    Iterator<String> iterator = FunUtils3.mapFilterIterator(
        Collections.<String>emptyList(),
        FunUtils.<String>trueFilter(),
        FunUtils2.<String>identityMapper());
    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 = FunUtils3.asFunIterable(list, FunUtils.trueFilter(), FunUtils2.<Integer>identityMapper());
    Assert.assertEquals(list, as(iterable, new ArrayList<>()));
  }
  
  @Test
  public void asFunIterableForEach() {
    Random random = new Random(9);
    ArrayList<Integer> list = new ArrayList<>();
    ArrayList<Integer> list2 = new ArrayList<>();
    for(int i=0; i<10000; i++) {
      int value = random.nextInt(10000);
      list.add(value);
      if (value % 5 == 0) {
        list2.add(~value);
      }
    }
    FunIterable<Number> iterable = FunUtils3.<Integer, Number>asFunIterable(list, new Filter<Integer>() {
      @Override
      public boolean accept(Integer element) {
        return element % 5 == 0;
      }
    }, new Mapper<Integer, Integer>() {
      @Override
      public Integer map(Integer element) {
        return ~element;
      }
    });
    final ArrayList<Object> list3 = new ArrayList<>();
    iterable.forEach(new Block<Object>() {
      @Override
      public void apply(Object element) {
        list3.add(element);
      }
    });
    Assert.assertEquals(list2, list3);
  }
  
  @Test
  public void asFunIterableToList() {
    Random random = new Random(9);
    ArrayList<Integer> list = new ArrayList<>();
    ArrayList<String> list2 = new ArrayList<>();
    for(int i=0; i<10000; i++) {
      int value = random.nextInt(10000);
      list.add(value);
      if (value % 3 == 0) {
        list2.add(Integer.toHexString(value));
      }
    }
    ArrayList<Object> list3 = new ArrayList<>();
    FunUtils3.asFunIterable(list, new Filter<Integer>() {
      @Override
      public boolean accept(Integer element) {
        return element % 3 == 0;
      }
    }, new Mapper<String, Integer>() {
      @Override
      public String map(Integer element) {
        return Integer.toHexString(element);
      }
    }).toList(list3);
    Assert.assertEquals(list2, list3);
  }
  
  @Test
  public void asFunIterableFilter() {
    ArrayList<Integer> list = new ArrayList<>();
    ArrayList<String> list2 = new ArrayList<>();
    for(int i=0; i<10000; i++) {
      list.add(i);
      String text = Integer.toString(i);
      if (i % 10 == 0) {
        list2.add(text);
      }
    }
    FunIterable<String> funIterable = FunUtils3.asFunIterable(list, new Filter<Integer>() {
      @Override
      public boolean accept(Integer element) {
        return element % 5 == 0;
      }
    }, new Mapper<String, Integer>() { 
      @Override
      public String map(Integer element) {
        return Integer.toString(element);
      }
    });
    FunIterable<String> funIterable2 = funIterable.filter(new Filter<String>() {
      @Override
      public boolean accept(String element) {
        return Integer.parseInt(element) % 2 == 0;
      }
    });
    Assert.assertEquals(list2, as(funIterable2, new ArrayList<String>()));
  }
  
  @Test
  public void asFunIterableMap() {
    ArrayList<Integer> list = new ArrayList<>();
    ArrayList<Integer> list2 = new ArrayList<>();
    for(int i=0; i<10000; i++) {
      list.add(i);
      if (i % 7 == 0) {
        list2.add(i);
      }
    }
    
    FunIterable<String> funIterable = FunUtils3.asFunIterable(list, new Filter<Integer>() {
      @Override
      public boolean accept(Integer element) {
        return element % 7 == 0;
      }
    }, new Mapper<String, Integer>() { 
      @Override
      public String map(Integer element) {
        return Integer.toString(element);
      }
    });
    FunIterable<Integer> funIterable2 = funIterable.map(new Mapper<Integer,String>() {
      @Override
      public Integer map(String element) {
        return Integer.parseInt(element);
      }
    });
    Assert.assertEquals(list2, as(funIterable2, new ArrayList<Integer>()));
  }
  
  @Test
  public void nullchecksFilterAnd() {
    try {
      FunUtils.and(null, FunUtils.trueFilter());  
      Assert.fail();
    } catch(NullPointerException e) {
      // do nothing
    }
    try {
      FunUtils.and(FunUtils.trueFilter(), null);  
      Assert.fail();
    } catch(NullPointerException e) {
      // do nothing
    }
  }
  @Test(expected=NullPointerException.class)
  public void nullchecksAsFunIterable() {
    FunUtils.asFunIterable(null, FunUtils.trueFilter());
  }
  @Test(expected=NullPointerException.class)
  public void nullchecksAsFunIterableForEach() {
    FunUtils.asFunIterable(Collections.emptyList(), FunUtils.trueFilter()).forEach(null);
  }
  @Test(expected=NullPointerException.class)
  public void nullchecksAsFunIterableFilter() {
    FunUtils.asFunIterable(Collections.emptyList(), FunUtils.trueFilter()).filter(null);
  }
  @Test(expected=NullPointerException.class)
  public void nullchecksAsFunIterableMap() {
    FunUtils.asFunIterable(Collections.emptyList(), FunUtils.trueFilter()).map(null);
  }
  @Test(expected=NullPointerException.class)
  public void nullchecksAsFunIterableToList() {
    FunUtils.asFunIterable(Collections.emptyList(), FunUtils.trueFilter()).toList(null);
  }
  
  @Test
  public void nullchecks2MapperCompose() {
    try {
      FunUtils2.compose(null, FunUtils2.identityMapper());  
      Assert.fail();
    } catch(NullPointerException e) {
      // do nothing
    }
    try {
      FunUtils2.compose(FunUtils2.identityMapper(), null);  
      Assert.fail();
    } catch(NullPointerException e) {
      // do nothing
    }
  }
  @Test(expected=NullPointerException.class)
  public void nullchecks2AsFunIterable() {
    FunUtils2.asFunIterable(null, FunUtils2.identityMapper());
  }
  @Test(expected=NullPointerException.class)
  public void nullchecks2AsFunIterableForEach() {
    FunUtils2.asFunIterable(Collections.emptyList(), FunUtils2.identityMapper()).forEach(null);
  }
  @Test(expected=NullPointerException.class)
  public void nullchecks2AsFunIterableFilter() {
    FunUtils2.asFunIterable(Collections.emptyList(), FunUtils2.identityMapper()).filter(null);
  }
  @Test(expected=NullPointerException.class)
  public void nullchecks2AsFunIterableMap() {
    FunUtils2.asFunIterable(Collections.emptyList(), FunUtils2.identityMapper()).map(null);
  }
  @Test(expected=NullPointerException.class)
  public void nullchecks2AsFunIterableToList() {
    FunUtils2.asFunIterable(Collections.emptyList(), FunUtils2.identityMapper()).toList(null);
  }
  
  @Test(expected=NullPointerException.class)
  public void nullchecks3AsFunIterable() {
    FunUtils3.asFunIterable(null, FunUtils.trueFilter(), FunUtils2.identityMapper());
  }
  @Test(expected=NullPointerException.class)
  public void nullchecks3AsFunIterableForEach() {
    FunUtils3.asFunIterable(Collections.emptyList(), FunUtils.trueFilter(), FunUtils2.identityMapper()).forEach(null);
  }
  @Test(expected=NullPointerException.class)
  public void nullchecks3AsFunIterableFilter() {
    FunUtils3.asFunIterable(Collections.emptyList(), FunUtils.trueFilter(), FunUtils2.identityMapper()).filter(null);
  }
  @Test(expected=NullPointerException.class)
  public void nullchecks3AsFunIterableMap() {
    FunUtils3.asFunIterable(Collections.emptyList(), FunUtils.trueFilter(), FunUtils2.identityMapper()).map(null);
  }
  @Test(expected=NullPointerException.class)
  public void nullchecks3AsFunIterableToList() {
    FunUtils3.asFunIterable(Collections.emptyList(), FunUtils.trueFilter(), FunUtils2.identityMapper()).toList(null);
  }
}
