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.List;
import java.util.NoSuchElementException;
import java.util.Random;

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

public class FunUtils2Test {
  @Test
  public void mapperIterator() {
    Iterator<String> it = FunUtils2.mapperIterator(Arrays.asList(1, 4, 7), 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("4", 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
  public void mapperIteratorRemove() {
    List<String> list = new ArrayList<>(Arrays.asList("foo", "bar", "baz"));
    Iterator<String> iterator = FunUtils2.mapperIterator(list, FunUtils2.<String>identityMapper());
    iterator.next();
    iterator.remove();
    Assert.assertEquals(list, Arrays.asList("bar", "baz"));
  }
  
  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<>();
    ArrayList<String> list2 = new ArrayList<>();
    for(int i=0; i<10000; i++) {
      int value = random.nextInt(10000);
      list.add(value);
      list2.add(Integer.toString(value));
    }
    Iterable<String> iterable = FunUtils2.asFunIterable(list, new Mapper<String, Integer>() {
      @Override
      public String map(Integer element) {
        return Integer.toString(element);
      }
    });
    Assert.assertEquals(list2, 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);
      list2.add(-value);
    }
    FunIterable<Number> iterable = FunUtils2.<Integer, Number>asFunIterable(list, 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() {
    List<Integer> list = Collections.nCopies(10000, 2);
    ArrayList<Object> list2 = new ArrayList<>();
    FunUtils2.asFunIterable(list, new Mapper<Integer, Integer>() {  
      @Override
      public Integer map(Integer element) {
        return element * 2;
      }
    }).toList(list2);
    Assert.assertEquals(Collections.nCopies(10000, 4), list2);
  }
  
  @Test
  public void asFunIterableFilter() {
    ArrayList<Integer> list = new ArrayList<>();
    ArrayList<String> list2 = new ArrayList<>();
    for(int i=0; i<100; i++) {
      list.add(i);
      String text = Integer.toString(i);
      if (text.length() %3 == 0) {
        list2.add(text);
      }
    }
    FunIterable<String> funIterable = FunUtils2.asFunIterable(list, 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 element.length() % 3 == 0;
      }
    });
    Assert.assertEquals(list2, as(funIterable2, new ArrayList<String>()));
  }
  
  @Test
  public void asFunIterableMap() {
    ArrayList<Integer> list = new ArrayList<>();
    ArrayList<String> list2 = new ArrayList<>();
    for(int i=0; i<10000; i++) {
      list.add(i);
      list2.add('#' + Integer.toHexString(i));
    }
    FunIterable<String> funIterable = FunUtils2.asFunIterable(list, new Mapper<String, Integer>() { 
      @Override
      public String map(Integer element) {
        return Integer.toHexString(element);
      }
    });
    FunIterable<String> funIterable2 = funIterable.map(new Mapper<String,String>() {
      @Override
      public String map(String element) {
        return '#' + element;
      }
    });
    Assert.assertEquals(list2, as(funIterable2, new ArrayList<String>()));
  }
  
  @Test
  public void identityFilter() {
    Mapper<Integer, Integer> m = FunUtils2.identityMapper();
    Mapper<String, String> m2 = FunUtils2.identityMapper();
    Assert.assertEquals(2, (int)m.map(2));
    Assert.assertEquals("42", m2.map("42"));
    Assert.assertSame(m, m2);
  }
  
  @Test
  public void compose() {
    Mapper<Integer, Integer> mapper = FunUtils2.compose(new Mapper<String, Integer>() {
      @Override
      public String map(Integer element) {
        return Integer.toString(element);
      }
    },new Mapper<Integer, String>() {
      @Override
      public Integer map(String element) {
        return Integer.parseInt(element);
      }
    });
    
    Assert.assertEquals(123, (int)mapper.map(123));
  }
}
