import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class Lambdas {

	public static void main(String[] args) {

//				Lambdas.intro();

//				Lambdas.multipleParameters();

//				Lambdas.comparators();

				Lambdas.streams();

	}

	public static void intro() {
		//1.5
		List<String> myList = Arrays.asList("element1","element2","element3");
		for (String element : myList) {
			System.out.println (element);
		}










		//1.8, iterable interface - Consumers
		//Parallelisierbar
		myList.forEach(new Consumer<String>() {
			public void accept(String element) {
				System.out.println(element);
			}
		});









		//Lamdas
		myList.forEach((String element) -> System.out.println(element));
		//Da nur ein Parameter muss Typ nicht unbedingt angegeben werde
		myList.forEach(element -> System.out.println(element));


		//Auch für mehrere Parameter möglich! ---> multipleParameters()
	}

	interface IntOp { int apply(int arg1, int arg2); }
	interface DoubleOp { double apply(double arg1, double arg2); }
	static int calcInt(int i1, int i2, IntOp op) { return op.apply(i1, i2); }
	static double calcDouble(double d1, double d2, DoubleOp op) {return op.apply(d1, d2); }

	public static void multipleParameters() {
		int i0 = calcInt(1, 2, new IntOp() { // before Java 8…
			@Override
			public int apply(int i, int j) {
				return i + j;
			}
		});
		System.out.println(i0);











		int i1 = calcInt(1, 2, (i, j) -> i + j); // i1 == 3
		System.out.println(i1);
		int i2 = calcInt(1, 2, (i, j) -> i - j); // i2 == -1
		System.out.println(i2);
		double d = calcDouble(1.1, 2.2, (i, j) -> i + j); // d == 3.3
		//Actually, not quite - it's 3.3000000000000003 :)
		System.out.println(d);











		int i3 = calcInt(1, 2, Integer::sum); //
		int i4 = calcInt(1, 2, (i, j) -> Integer.sum(i, j));


	}

	public static void comparators() {
		//Auch gut für Comparatoren!
		ArrayList<Student> students = new ArrayList<>();
		students.add(new Student(95621, "Jennifer", "Köhlerr", 25, 30));
		students.add(new Student(75821, "Martin", "Schultheiss", 27, 29));
		students.add(new Student(58122, "Christian", "Schulz", 17, 18));
		students.add(new Student(58125, "Peter", "Decker", 21, 25));

		Comparator<Student> byExamPointsOld = new Comparator<Student>() {
			@Override
			public int compare(Student s1, Student s2) {
				return s1.getExamPoints().compareTo(s2.getExamPoints());
			}
		};

		Comparator<Student> byExamPoints = (Student s1, Student s2) -> s1.getExamPoints().compareTo(s2.getExamPoints());

		Collections.sort(students, byExamPoints);
		students.forEach(s -> System.out.println(s.toString()));









		//Kürzer
		Collections.sort(students, (Student s1, Student s2) -> s1.getExamPoints().compareTo(s2.getExamPoints()));









		//Man braucht auch kein Collections.sort mehr!

		students.sort((Student s1, Student s2) -> s1.getExamPoints().compareTo(s2.getExamPoints()));









		//Can easily reverse!
		students.sort(byExamPoints.reversed());










		//Chain comparators!
		Comparator<Student> byFirstName = (Student s1, Student s2) -> s1.getFirstName().compareTo(s2.getFirstName());
		Comparator<Student> byLastName = (Student s1, Student s2) -> s1.getLastName().compareTo(s2.getLastName());
		students.sort(byFirstName.thenComparing(byLastName));









		//Other option:
		Collections.sort(students, Comparator.comparing(Student::getFirstName)
				.thenComparing(Student::getLastName));
	}



	public static void streams() {
		System.out.println("--------");

		List<String> myList = Arrays.asList("Markus", "Tom",
				"Christoph","Christian", "Susanne", "Tanja", 
				"Sebastian", "Julia", "Markus");
		//Streams --> Pipelining

		myList.stream()
		.forEach(it -> System.out.println(it));









		System.out.println("--------");

		myList.stream()
		.filter(it -> it.length() > 4)
		.forEach(it -> System.out.println(it));









		System.out.println("--------");
		myList.stream()
		.filter(it -> it.length() > 4)
		.distinct()
		.forEach(it -> System.out.println(it));









		System.out.println("--------");
		myList.stream()
		.filter(it -> it.length() > 4)
		.distinct()
		.sorted()
		.forEach(it -> System.out.println(it));		









		System.out.println("--------");
		System.out.println(myList.stream()
				.filter(it -> it.length() > 4)
				.distinct()
				.sorted()
				.count());










		System.out.println("--------");
		System.out.println(myList.stream()
				.filter(it -> it.length() > 4)
				.distinct()
				.sorted()
				.findFirst());	










		System.out.println("--------");
		for (int i = 0; i <= 20; i++) {
			System.out.println(myList.stream()
					.filter(it -> it.length() > 4)
					.findAny());	
		}










		//Unterschiede: peeking oder closing?
		//peeking: weitere Operationen möglich: map(), sorted(), ...
		//closing: count(), max(), ...

		//auch map/reduce-Operationen möglich!

		Stream.of("one", "two", "three", "four")
		.filter(e -> e.length() > 3)
		.peek(e -> System.out.println("Filtered value: " + e)) //Note:This method exists mainly to support debugging, where you want to see the elements as they flow past a certain point in a pipeline: 
		.map(String::toUpperCase) // oder: .map(it -> it.toUpperCase())
		.peek(e -> System.out.println("Mapped value: " + e))
		.collect(Collectors.toList());

	}


}
