package io.amaro.extension.box.util.list;

import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.junit.Assert.*;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;

import static org.hamcrest.collection.IsEmptyCollection.empty;
import static org.hamcrest.core.Is.is;
import static org.hamcrest.collection.IsIterableContainingInOrder.contains;
import static org.hamcrest.collection.IsCollectionWithSize.hasSize;
import static org.hamcrest.core.Every.everyItem;

import org.junit.BeforeClass;
import org.junit.FixMethodOrder;
import org.junit.Test;
import org.junit.runners.MethodSorters;

@FixMethodOrder(MethodSorters.NAME_ASCENDING)
public class ListExtensionsTest {
  
  private static final List<Integer> EMPTY_LIST = Collections.emptyList();

  private static final List<Integer> INT_LIST_12 = new ArrayList<>();
  private static final List<Integer> INT_LIST_12_PART1 = new ArrayList<>();
  private static final List<Integer> INT_LIST_12_PART2 = new ArrayList<>();
  private static final List<Object> GENERIC_STRING_LIST = new ArrayList<>();
  private static final List<Object> INT_STRING_LIST = new ArrayList<>();
  private static final List<Object> LIST_WITH_NULLS = new ArrayList<>();
  private static final List<Integer> LIST_WITH_DUPLICATES = new ArrayList<>();
  private static final List<Integer> ONLY_DUPLICATES = new ArrayList<>();
  
  private ListExtensions<Integer> extensions;
  
  @BeforeClass
  public static void loadTestData() {
    for(int i = 0; i <= 11; i++) {
      INT_LIST_12.add(i);
      
      if(i <= 5) {
        INT_LIST_12_PART1.add(i);
      } else {
        INT_LIST_12_PART2.add(i);
      }
    }
    
    GENERIC_STRING_LIST.add("Lorem");
    GENERIC_STRING_LIST.add("Ipsum");
    
    INT_STRING_LIST.add("1");
    INT_STRING_LIST.add(2);
    INT_STRING_LIST.add("3");
    INT_STRING_LIST.add(4);
    
    LIST_WITH_NULLS.add("abc");
    LIST_WITH_NULLS.add(null);
    LIST_WITH_NULLS.add(123);
    LIST_WITH_NULLS.add(null);
    
    LIST_WITH_DUPLICATES.add(100);
    LIST_WITH_DUPLICATES.add(100);
    LIST_WITH_DUPLICATES.add(101);
    LIST_WITH_DUPLICATES.add(102);
    LIST_WITH_DUPLICATES.add(103);
    LIST_WITH_DUPLICATES.add(104);
    LIST_WITH_DUPLICATES.add(104);
    LIST_WITH_DUPLICATES.add(104);
    
    ONLY_DUPLICATES.add(5);
    ONLY_DUPLICATES.add(5);
    ONLY_DUPLICATES.add(null);
    ONLY_DUPLICATES.add(15);
    ONLY_DUPLICATES.add(15);
    ONLY_DUPLICATES.add(null);
  }
  
  @Test
  public void testPartition() {
    extensions = new ListExtensionsImpl<Integer>(null);
    try {
      extensions.partition(1);
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    extensions = new ListExtensionsImpl<Integer>(EMPTY_LIST);
    assertEquals(0, extensions.partition(10).size());
    
    extensions = new ListExtensionsImpl<Integer>(INT_LIST_12);
    try {
      extensions.partition(-5);
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    extensions = new ListExtensionsImpl<Integer>(INT_LIST_12);
    try {
      extensions.partition(0);
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    extensions = new ListExtensionsImpl<Integer>(INT_LIST_12);
    List<List<Integer>> partitions = extensions.partition(6);
    assertEquals(INT_LIST_12_PART1, partitions.get(0));
    assertEquals(INT_LIST_12_PART2, partitions.get(1));
  }

  @Test
  public void testToListOf() {
    ListExtensionsImpl<Integer> extensions = new ListExtensionsImpl<Integer>(null);
    try {
      extensions.toListOf(Object.class);
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    extensions = new ListExtensionsImpl<Integer>(EMPTY_LIST);
    try {
      extensions.toListOf(null);
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    extensions = new ListExtensionsImpl<Integer>(EMPTY_LIST);
    assertThat(extensions.toListOf(Object.class), empty());
    
    ListExtensionsImpl<?> genericExtensions = new ListExtensionsImpl<Object>(GENERIC_STRING_LIST);
    List<String> stringList = genericExtensions.toListOf(String.class); 
    assertThat(stringList, hasSize(2));
    assertThat(stringList, contains("Lorem", "Ipsum"));
    
    ListExtensionsImpl<?> intListExtensions = new ListExtensionsImpl<Integer>(INT_LIST_12);
    List<Object> objectList = intListExtensions.toListOf(Object.class); 
    assertThat(objectList, hasSize(12));
    assertThat(objectList, contains(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11));
    
    try {
    	genericExtensions.toListOf(Integer.class);
    } catch (Exception e) {
    	assertThat(e, instanceOf(ClassCastException.class));
    }
  }
  
  @Test
  public void testGetAllFromType() {
  	ListExtensionsImpl<?> extensions = new ListExtensionsImpl<Integer>(null);
    try {
      extensions.getAllFromType(Object.class);
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    extensions = new ListExtensionsImpl<Integer>(EMPTY_LIST);
    try {
      extensions.getAllFromType(null);
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    extensions = new ListExtensionsImpl<Object>(INT_STRING_LIST);
    List<String> stringElements = extensions.getAllFromType(String.class);
    
    assertThat(stringElements, hasSize(2));
    assertThat(stringElements, contains("1", "3"));
    
    List<Integer> intElements = extensions.getAllFromType(Integer.class);
    assertThat(intElements, hasSize(2));
    assertThat(intElements, contains(2, 4));
    
    List<double[]> doubleArrayElements = extensions.getAllFromType(double[].class);
    assertThat(doubleArrayElements, empty());
  }
  
  @Test
  public void testFrequencyOf() {
  	ListExtensionsImpl<Integer> extensions = new ListExtensionsImpl<Integer>(null);
    try {
      extensions.frequencyOf(100);
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    extensions = new ListExtensionsImpl<Integer>(EMPTY_LIST);
    assertThat(extensions.frequencyOf(2), is(0L));
    
    extensions = new ListExtensionsImpl<Integer>(INT_LIST_12);
    assertThat(extensions.frequencyOf(null), is(0L));
    
    extensions = new ListExtensionsImpl<Integer>(INT_LIST_12);
    assertThat(extensions.frequencyOf(7), is(1L));
    
    ListExtensionsImpl<?> extensionsWithNull = new ListExtensionsImpl<Object>(LIST_WITH_NULLS);
    assertThat(extensionsWithNull.frequencyOf(null), is(2L));
  }
  
  @Test
  public void testSelect() {
    ListExtensionsImpl<Integer> extensions = new ListExtensionsImpl<Integer>(null);
    try {
      extensions.select((x) -> x == 123);
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    ListExtensionsImpl<Object> stringExtensions = new ListExtensionsImpl<Object>(INT_STRING_LIST);
    try {
      stringExtensions.select(null);
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    extensions = new ListExtensionsImpl<Integer>(INT_LIST_12);
    List<Integer> values = extensions.select((x) -> x%2 == 0);
    assertThat(values, contains(0, 2, 4, 6, 8, 10));
    
    values = extensions.select((x) -> x < 5);
    assertThat(values, contains(0, 1, 2, 3, 4));
    
    extensions = new ListExtensionsImpl<Integer>(EMPTY_LIST);
    values = extensions.select((x) -> x < 0);
    assertThat(values, empty());
  }
  
  @Test
  public void testTransform() {
    ListExtensionsImpl<Integer> extensions = new ListExtensionsImpl<Integer>(null);
    try {
      extensions.transform((x) -> x.toString());
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    ListExtensionsImpl<Object> stringExtensions = new ListExtensionsImpl<Object>(INT_STRING_LIST);
    try {
      stringExtensions.transform(null);
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    extensions = new ListExtensionsImpl<>(INT_LIST_12);
    List<String> listOfStringNumbers = extensions.transform((x) -> x.toString());
    assertThat(listOfStringNumbers, everyItem(instanceOf(String.class)));
    assertThat(listOfStringNumbers, contains("0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11"));
    
    extensions = new ListExtensionsImpl<>(EMPTY_LIST);
    List<Double> listOfDoubleStrings = extensions.transform((x) -> Double.valueOf(x));
    assertThat(listOfDoubleStrings, empty());
  }
  
  @Test
  public void testFirst() {
    ListExtensionsImpl<Integer> extensions = new ListExtensionsImpl<Integer>(null);
    try {
      extensions.first(1);
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    extensions = new ListExtensionsImpl<Integer>(INT_LIST_12);
    try {
      extensions.first(0);
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    List<Integer> firstFour = extensions.first(4);
    assertThat(firstFour, hasSize(4));
    assertThat(firstFour, contains(0, 1, 2, 3));
    
    List<Integer> allItems = extensions.first(100);
    assertThat(allItems, hasSize(12));
    assertThat(allItems, contains(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11));
    
    extensions = new ListExtensionsImpl<>(EMPTY_LIST);
    allItems = extensions.first(5);
    assertThat(allItems, empty());
  }
  
  @Test
  public void testLast() {
    ListExtensionsImpl<Integer> extensions = new ListExtensionsImpl<Integer>(null);
    try {
      extensions.last(1);
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    extensions = new ListExtensionsImpl<Integer>(INT_LIST_12);
    try {
      extensions.last(0);
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    List<Integer> lastFour = extensions.last(4);
    assertThat(lastFour, hasSize(4));
    assertThat(lastFour, contains(8, 9, 10, 11));
    
    List<Integer> allItems = extensions.last(100);
    assertThat(allItems, hasSize(12));
    assertThat(allItems, contains(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11));
    
    extensions = new ListExtensionsImpl<>(EMPTY_LIST);
    allItems = extensions.last(5);
    assertThat(allItems, empty());
  }
  
  @Test
  public void testRemoveDuplicates() {
    ListExtensionsImpl<Integer> extensions = new ListExtensionsImpl<Integer>(null);
    try {
      extensions.removeDuplicates();
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    extensions = new ListExtensionsImpl<>(EMPTY_LIST);
    List<Integer> noItems = extensions.removeDuplicates();
    assertThat(noItems, empty());
    
    ListExtensionsImpl<Integer> objExtensions = new ListExtensionsImpl<>(LIST_WITH_DUPLICATES);
    List<Integer> withoutDups = objExtensions.removeDuplicates();
    assertThat(withoutDups, hasSize(5));
    assertThat(withoutDups, contains(100, 101, 102, 103, 104));
    
    extensions = new ListExtensionsImpl<>(ONLY_DUPLICATES);
    withoutDups = extensions.removeDuplicates();
    assertThat(withoutDups, hasSize(3));
    assertThat(withoutDups, contains(5, null, 15));
  }
  
  @Test
  public void testCompact() {
    ListExtensionsImpl<Integer> extensions = new ListExtensionsImpl<Integer>(null);
    try {
      extensions.compact();
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    extensions = new ListExtensionsImpl<>(EMPTY_LIST);
    List<Integer> noItems = extensions.compact();
    assertThat(noItems, empty());
    
    extensions = new ListExtensionsImpl<>(ONLY_DUPLICATES);
    List<Integer> noNulls = extensions.compact();
    assertThat(noNulls, hasSize(4));
    assertThat(noNulls, contains(5, 5, 15, 15));
    
    extensions = new ListExtensionsImpl<>(INT_LIST_12);
    List<Integer> allItems = extensions.compact();
    assertThat(allItems, hasSize(12));
    assertThat(allItems, contains(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11));
  }
  
  @Test
  public void testJoin() {
    ListExtensionsImpl<Integer> extensions = new ListExtensionsImpl<Integer>(null);
    try {
      extensions.join();
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    extensions = new ListExtensionsImpl<>(ONLY_DUPLICATES);
    String join = extensions.join();
    assertThat(join, is("551515"));
    
    extensions = new ListExtensionsImpl<>(EMPTY_LIST);
    join = extensions.join();
    assertThat(join, is(""));
    
    extensions = new ListExtensionsImpl<>(INT_LIST_12);
    join = extensions.join();
    assertThat(join, is("01234567891011"));
  }
  
  @Test
  public void testJoinWithStringSeparator() {
    ListExtensionsImpl<Integer> extensions = new ListExtensionsImpl<Integer>(null);
    try {
      extensions.join("-");
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    extensions = new ListExtensionsImpl<Integer>(INT_LIST_12);
    try {
      extensions.join(null);
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    extensions = new ListExtensionsImpl<>(ONLY_DUPLICATES);
    String join = extensions.join("-");
    assertThat(join, is("5-5-15-15"));
    
    extensions = new ListExtensionsImpl<>(EMPTY_LIST);
    join = extensions.join("@");
    assertThat(join, is(""));
    
    extensions = new ListExtensionsImpl<>(INT_LIST_12);
    join = extensions.join("-|-");
    assertThat(join, is("0-|-1-|-2-|-3-|-4-|-5-|-6-|-7-|-8-|-9-|-10-|-11"));
  }
  
  @Test
  public void testJoinWithCharSeparator() {
    ListExtensionsImpl<Integer> extensions = new ListExtensionsImpl<Integer>(null);
    try {
      extensions.join('.');
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    extensions = new ListExtensionsImpl<>(ONLY_DUPLICATES);
    String join = extensions.join(':');
    assertThat(join, is("5:5:15:15"));
    
    extensions = new ListExtensionsImpl<>(EMPTY_LIST);
    join = extensions.join('\n');
    assertThat(join, is(""));
    
    extensions = new ListExtensionsImpl<>(INT_LIST_12);
    join = extensions.join('\t');
    assertThat(join, is("0\t1\t2\t3\t4\t5\t6\t7\t8\t9\t10\t11"));
  }
  
  @Test
  public void testReverse() {
    ListExtensionsImpl<Integer> extensions = new ListExtensionsImpl<Integer>(null);
    try {
      extensions.reverse();
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    extensions = new ListExtensionsImpl<>(EMPTY_LIST);
    List<Integer> reversed = extensions.reverse();
    assertThat(reversed, empty());
    
    extensions = new ListExtensionsImpl<>(INT_LIST_12);
    List<Integer> allItems = extensions.reverse();
    assertThat(allItems, hasSize(12));
    assertThat(allItems, contains(11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0));
  }
  
  @Test
  public void testValuesAt() {
    ListExtensionsImpl<Integer> extensions = new ListExtensionsImpl<Integer>(null);
    try {
      extensions.valuesAt(1, 2, 3);
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    extensions = new ListExtensionsImpl<Integer>(INT_LIST_12);
    try {
      extensions.valuesAt(null);
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    extensions = new ListExtensionsImpl<>(INT_LIST_12);
    List<Integer> allItems = extensions.valuesAt(0, 5, 9);
    assertThat(allItems, hasSize(3));
    assertThat(allItems, contains(0, 5, 9));
    
    allItems = extensions.valuesAt(17);
    assertThat(allItems, empty());
    
    allItems = extensions.valuesAt(-15);
    assertThat(allItems, empty());
    
    allItems = extensions.valuesAt(-15, 0, 1, 2, 4, 17);
    assertThat(allItems, hasSize(4));
    assertThat(allItems, contains(0, 1, 2, 4));
  }
  
  @Test
  public void testToSet() {
    ListExtensionsImpl<Integer> extensions = new ListExtensionsImpl<Integer>(null);
    try {
      extensions.toSet();
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    extensions = new ListExtensionsImpl<Integer>(EMPTY_LIST);
    Set<Integer> intSet = extensions.toSet();
    assertThat(intSet, empty());
    
    extensions = new ListExtensionsImpl<Integer>(INT_LIST_12);
    intSet = extensions.toSet();
    assertThat(intSet, hasSize(12));
    assertThat(intSet, contains(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11));
  }
  
  @Test
  public void testMapByKey() {
    ListExtensionsImpl<Integer> extensions = new ListExtensionsImpl<Integer>(null);
    try {
      extensions.mapByKey((x) -> x.hashCode());
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    extensions = new ListExtensionsImpl<Integer>(EMPTY_LIST);
    try {
      extensions.mapByKey(null);
    } catch (Exception e) {
      assertThat(e, instanceOf(IllegalArgumentException.class));
    }
    
    Map<String, List<Integer>> stringKeyMap = extensions.mapByKey((x) -> x.toString());
    assertThat(stringKeyMap.entrySet(), empty());
    
    extensions = new ListExtensionsImpl<>(INT_LIST_12);
    stringKeyMap = extensions.mapByKey((x) -> x <= 5 ? "small" : "big");
    assertThat(stringKeyMap.keySet(), hasSize(2));
    assertThat(stringKeyMap.get("small"), contains(0, 1, 2, 3, 4, 5));
    assertThat(stringKeyMap.get("big"), contains(6, 7, 8, 9, 10, 11));
  }
}
