package lambda;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;

public class Dog {

	// dog types
	private enum Type {
		PUG, BOXER, LABRADOR, HUSKY
	}

	final String name;
	final double weight;
	final Type type;

	private Dog(String name, double weight, Type type) {
		this.name = name;
		this.weight = weight;
		this.type = type;
	}

	public static Dog newPug(String name, double weight) {
		return new Dog(name, weight, Type.PUG);
	}

	public static Dog newBoxer(String name, double weight) {
		return new Dog(name, weight, Type.BOXER);
	}

	public static Dog newLabrador(String name, double weight) {
		return new Dog(name, weight, Type.LABRADOR);
	}

	public static Dog newHusky(String name, double weight) {
		return new Dog(name, weight, Type.HUSKY);
	}

	@Override
	public String toString() {
		return "Dog [name=" + name + ", weight=" + weight + ", type=" + type + "]";
	}

	@Override
	public int hashCode() {
		final int prime = 31;
		int result = 1;
		result = prime * result + ((name == null) ? 0 : name.hashCode());
		result = prime * result + ((type == null) ? 0 : type.hashCode());
		long temp;
		temp = Double.doubleToLongBits(weight);
		result = prime * result + (int) (temp ^ (temp >>> 32));
		return result;
	}

	@Override
	public boolean equals(Object obj) {
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (getClass() != obj.getClass())
			return false;
		Dog other = (Dog) obj;
		if (name == null) {
			if (other.name != null)
				return false;
		} else if (!name.equals(other.name))
			return false;
		if (type != other.type)
			return false;
		if (Double.doubleToLongBits(weight) != Double.doubleToLongBits(other.weight))
			return false;
		return true;
	}


	public static void main(String[] args) {
		

		List<Dog> dogs = new ArrayList<>();

		dogs.add(Dog.newBoxer("boxer1", 15));
		dogs.add(Dog.newBoxer("boxer2", 16));
		dogs.add(Dog.newBoxer("boxer3", 13));
		dogs.add(Dog.newBoxer("boxer4", 14));
		dogs.add(Dog.newBoxer("boxer5", 15));

		dogs.add(Dog.newHusky("husky1", 18));
		dogs.add(Dog.newHusky("husky2", 19));
		dogs.add(Dog.newHusky("husky3", 17));
		dogs.add(Dog.newHusky("husky4", 18));
		dogs.add(Dog.newHusky("husky5", 20));

		dogs.add(Dog.newLabrador("labrador1", 17));
		dogs.add(Dog.newLabrador("labrador2", 18));
		dogs.add(Dog.newLabrador("labrador3", 19));
		dogs.add(Dog.newLabrador("labrador4", 20));
		dogs.add(Dog.newLabrador("labrador5", 17));

		dogs.add(Dog.newPug("pug1", 10));
		dogs.add(Dog.newPug("pug2", 11));
		dogs.add(Dog.newPug("pug3", 9));
		dogs.add(Dog.newPug("pug4", 11));
		dogs.add(Dog.newPug("pug5", 10));

		Collections.shuffle(dogs);

		System.out.println(dogs.size());
		
		

		/*
		 * Exemple 1
		 * 
		 * map/mapToDouble/forEach
		 */

		System.out.println("======Exemple1======");
		// size
		System.out.printf("count %d%n", dogs.stream().count());
		
		// max weight
		System.out.printf("max %f%n", dogs.stream().mapToDouble(dog -> dog.weight).max().orElse(-1));
		// min weight
		System.out.printf("min %f%n", dogs.stream().mapToDouble(dog -> dog.weight).min().orElse(-1));
		// average weight
		System.out.printf("average %f%n", dogs.stream().mapToDouble(dog -> dog.weight).average().orElse(-1));
		// ^^^^^ dogs.stream() makes a stream of objects, and mapToDouble means
		// I want convert objects to double,

		// if you want, you can, of cause use map in the place of mapToDouble,
		// the strength point of using mapToDouble is you can use .max() .min()
		// etc
		// by using map, you have to do it by yourself, there is an exemple
		System.out.printf("max by reduce %f%n", dogs.stream().map(dog -> {
			return dog.weight;
		}).reduce((x, y) -> {
			return x > y ? x : y;
		}).orElse(-1.0));

		dogs.stream().forEach(System.out::println);
		// same as
		// dogs.stream().forEach(dog -> System.out.println(dog));

		/*
		 * Exemple 2 filter/ collect
		 */

		System.out.println("======Exemple2======");
		// all pug in a list
		List<Dog> pugs = dogs.stream().filter(dog -> dog.type.equals(Dog.Type.PUG)).collect(Collectors.toList());

		// all lab in an ArrayList
		List<Dog> labs = dogs.stream().filter(dog -> dog.type.equals(Dog.Type.LABRADOR))
				.collect(Collectors.toCollection(ArrayList::new));

		// all boxer in a set
		Set<Dog> boxs = dogs.stream().filter(dog -> dog.type.equals(Dog.Type.BOXER)).collect(Collectors.toSet());

		// all husky in a map
		Map<String, Dog> husk = dogs.stream().filter(dog -> dog.type.equals(Dog.Type.HUSKY))
				.collect(Collectors.toMap(dog -> (dog.name), item -> item));

		System.out.println(pugs);
		System.out.println(labs);
		System.out.println(boxs);
		System.out.println(husk);

		/*
		 * Exemple 3 filter/collect/ sorted
		 */

		System.out.println("======Exemple3======");
		// I sorted them by name

		// all pug in a list
		pugs = dogs.stream().filter(dog -> dog.type.equals(Dog.Type.PUG))
				.sorted((x, y) -> x.name.compareToIgnoreCase(y.name)).collect(Collectors.toList());

		// all lab in an ArrayList
		labs = dogs.stream().filter(dog -> dog.type.equals(Dog.Type.LABRADOR))
				.sorted((x, y) -> x.name.compareToIgnoreCase(y.name)).collect(Collectors.toCollection(ArrayList::new));

		System.out.println(pugs);
		System.out.println(labs);

		
		// I sorted them by weight

		// all pug in a list
		pugs = dogs.stream().filter(dog -> dog.type.equals(Dog.Type.PUG))
				.sorted((x, y) -> Double.compare(x.weight, y.weight)).collect(Collectors.toList());

		// all lab in an ArrayList
		labs = dogs.stream().filter(dog -> dog.type.equals(Dog.Type.LABRADOR))
				.sorted((x, y) -> Double.compare(x.weight, y.weight)).collect(Collectors.toCollection(ArrayList::new));

		System.out.println(pugs);
		System.out.println(labs);
		
		/*
		 * Exemple 4
		 * flatMap
		 */
		
		//now I'm going to merge all these collections to a list
		
		
		
		List<Collection<Dog>> collections = Arrays.asList(pugs,labs, boxs, husk.values());
		
		//they are the same
		List<Dog> dogs2 = collections.stream().flatMap(collection -> collection.stream()).collect(Collectors.toList());
		collections.stream().flatMap(Collection::stream).collect(Collectors.toList());
		
		//now they are back to a list again
		System.out.println(dogs2);
		System.out.printf("%d%n", dogs2.size());
		//the different between map and flatMap, is map() returns only a value, flatMap can return 
		//a stream that has a lot of values, so in my exemple, flatMap(Stream::of) means
		//each collection became a stream, and collect will be applied in each element of those streams.
		
		
		//show you if they are the same
		System.out.println(dogs2.containsAll(dogs) && dogs.containsAll(dogs2));
		}

}
