package lambda;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;


public class LambdaExpression {

	
	
	public static void main(String[] args) {
	
	
		/**
		 * Exemple 1
		 */
		System.out.println("Exemple1");
		//f(x) = 3x+6, you can claim value's type but not necessary
		//Function<T,R>, T is the type of entering data
		//R is the type of the returning data
		//so f is a function which needs a double and will return a double
		Function<Double,Double> f = (Double x) -> 3 * x + 6;
		//g(x) = 4x^2
		Function<Double,Double> g = x -> 4 * Math.pow(x, 2);
		
		//f2(x) = 4(3x+6)^2
		Function<Double,Double> f2 = f.andThen(g);
		//g2(g) = 3*(4x^2)+6
		Function<Double,Double> g2 = f.compose(g);
		
		// 3 * 2 + 6 = 12.0
		System.out.println(f.apply(2.0));
		// 4 * (2^2) = 16.0
		System.out.println(g.apply(2.0));
		// 4( 3 * 2 + 6 )^ 2 = 576.0
		System.out.println(f2.apply(2.0));
		// 3 * (4 * (2^2))+ 6 = 54.0
		System.out.println(g2.apply(2.0));
		
		//f3 = f2 = 4(3x+6)^2, 
		//please look here, the parameter is a function,
		Function<Double,Double> f3 = f.andThen(x -> 4 * Math.pow(x, 2));
		//576 the same as before f.andThen(g)
		System.out.println(f3.apply(2.0));
		
		
		
		/**
		 * Exemple 2
		 */
		System.out.println("Exemple2");
		//a list 
		List<Integer> list = Arrays.asList(1,4,7,2,5,8,3,6,9,0);
		
		
		//This is a comparator, 
		Comparator<Integer> S2B = new Comparator<Integer>(){
			@Override
			public int compare(Integer o1, Integer o2) {
				return o1 -o2;
			}};
			
		//of course you can use l.sort() or Collection.sort()
		//but as these methods are void, it will change the original order
		//so I use stream, it means I create a new list, 
		//and this list has the same elements as original list, but sorted.
		Function<List<Integer>,List<Integer>> sortS2B = l -> l.stream().sorted(S2B).collect(Collectors.toList());
		Function<List<Integer>, List<Integer>> sortB2S = l -> l.stream().sorted(S2B.reversed()).collect(Collectors.toList());
		
		
		//Showing you the list by order increase 
		System.out.println(sortS2B.apply(list));
		//showing you the list by order decrease
		System.out.println(sortB2S.apply(list));
		//original list:
		System.out.println(list);
		
		//or you can just call l.get(0), I'm using stream, because stream is a new part of java 8
		//and by using findFirst, it will return an Optional<T> which will showed as 
		//an Optional<Integer> in my exemple, Optional<T>.orElse(x) means, if this value that in this
		//Optional is NOT null, then show you the value, if not, it will show you a "default value"
		//,so in my exemple the "default value" is -1
		//if you use l.get(0), it will be find in my exemple, but sometimes we have to check the value
		//so the statement has to be l -> {if(l.get(0)!= null) return l.get(0); else return -1;};
		Function<List<Integer>,Integer> first = l -> l.stream().findFirst().orElse(-1);
		
		//with a empty list, it will show you -1
		System.out.println(first.apply(new ArrayList<Integer>()));
		
		//I apply first on the original list 1,4,7,2,5,8,3,6,9,0, so it will show you 1
		System.out.println(first.apply(list));
		//I apply sortS2B ant then first on the list
		//it means sort the list, then find the first
		//so it will be 0
		System.out.println(sortS2B.andThen(first).apply(list));
		//it will find 9
		System.out.println(sortB2S.andThen(first).apply(list));
		
		/*
		 * Exemple 3
		 */
		
		System.out.println("Exemple3");
		
		//Now I'll show you the lambda expression...
		Function<List<Integer>,List<Integer>> sortS2B2 = l -> l.stream().sorted((x,y) -> x-y).collect(Collectors.toList());
		Function<List<Integer>,List<Integer>> sortB2S2 = l -> l.stream().sorted((x,y) -> y-x).collect(Collectors.toList());
		//Look the sorted part, the method compare(int x, int y) becomes (x,y) -> x-y, 
		//compare is a function, so we can pass this function as parameter
		
		System.out.println(sortS2B2.apply(list));
		System.out.println(sortB2S2.apply(list));
		
		//sort the list and take the first element
		System.out.println(sortS2B2.andThen(l -> l.stream().findFirst().orElse(-1)).apply(list));
		System.out.println(sortB2S2.andThen(l -> l.stream().findFirst().orElse(-1)).apply(list));
		
		/*
		 * Exemple 4
		 */
		
		System.out.println("Exemple 4");
		//I made a list of integer 0 to 199
		//IntStream is a stream which makes int
		//iterate(0, i -> i+1), from 0 and each element +1, 
		//or you can use IntStream.range(0,200) without limit, I just show you methods as more as possible
		//limit(200) I will take only 200,
		//boxed() int to integer
		//collect() toList
		List<Integer> t2t = IntStream.iterate(0, i -> i+1).limit(200).boxed().collect(Collectors.toList());
		
		
		//look what I've got
		System.out.println(t2t);
		
		//Now I'm going to make an ugly function to show you a little bit more
		//I made a bifunction which needs two list<Integer> as parameters, and return a String
		BiFunction<List<Integer>,List<Integer>,String> int2Char =  
				//(x,y) means the parameters,
				(x,y) -> x.stream()
				//new I made a stream from x, the first list
						.map(i -> i + y.stream().mapToInt(Integer::intValue).sum())
				//each element, in the list x, I made it added the total sum of list y
						.map(i -> (char)i.intValue())
						//and I made it return the char of this int
						.collect(Collectors.toList())
						//catch all char in a list
						.stream().map(i -> i.toString())
						//a stream for chars, and each char become to String
						.reduce((acc, e) -> acc  + e).get();
						//get the string final
						
		//What an ugly exemple, I suppose that you will realize the same thing in you life
				//this one just an exemple, I've made it for fun
		
		//show you the final string												
		System.out.println(int2Char.apply(t2t, list));	
		
		
		
	}

}
