import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.OptionalDouble;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;

public class StreamsAndMapsAndSystemOuts {

    public static void main(String[] args) {
        System.out.println("Map is transforming from lowercase to uppercase");
        List<String> myList = Arrays.asList("a1", "a2", "b1", "c2", "c1");
        myList.stream()
                .filter(s->s.startsWith("c"))
                .map(String::toUpperCase)
                .sorted()
                .forEach(System.out::println);

        System.out.println("Print first, using Arrays.asList(...).stream()");
        Arrays.asList("a1","a2","a3")
                .stream()
                .findFirst()
                .ifPresent(System.out::println);

        String[] s1 = {"b1","b2"};
        Arrays.asList(s1)
                .stream()
                .findFirst()
                .ifPresent(System.out::println);

        System.out.println("Better to use Stream.of");
        Stream.of("a1","a2","a3")
                .findFirst()
                .ifPresent(System.out::println);
        Stream.of(s1)
                .findFirst()
                .ifPresent(System.out::println);

        IntStream.range(1,4)
                .forEach(System.out::println);

        System.out.println("Map operation: n->2n+1");
        Arrays.stream(new int[] {1,2,3})
                .map(n->2*n+1)
                .forEach(System.out::println);

        System.out.println("average on {1,2,3} gives 2");
        int[] a1 = new int[] {1,2,3};
        OptionalDouble average = Arrays.stream(a1)
                .average();
        average.ifPresent(s->System.out.println("Average is: " + s));

        //Rather:
        Arrays.stream(a1)
                .average()
                .ifPresent(s->System.out.println("Average is: " + s));

        System.out.println("Sum of int array {1,2,3}: ");
        int sum = Arrays.stream(a1)
                .sum();
        System.out.println("Sum is an integer: " + sum);

        System.out.println("Make string operations, printing resulting strings");
        Stream.of ("a1", "b2","c3")
                .map(s->s.substring(0,1))
                .forEach(System.out::println);

        System.out.println("Transform Integer to int:");
        Stream.of ("a1", "b2","c3")
                .map(s->s.substring(1))
                .mapToInt(Integer::parseInt)
                .forEach(System.out::println);

        System.out.println("Converting double to ints");
        Stream.of(1.0, 2.0, 3.0)
                .mapToInt(Double::intValue)
                .forEach(System.out::println);

        System.out.println("Converting double to ints and making it to strings");
        Stream.of(1.0, 2.0, 3.0)
                .mapToInt(Double::intValue)
                .mapToObj(i->"a"+i)
                .forEach(System.out::println);

        System.out.println("An other way to construct a stream of strings from an int stream");
        IntStream.range(1,4)
                .mapToObj(i->"a"+i)
                .forEach(s->System.out.println("Making a println from a lambda " + s));

        System.out.println("A terminal operation is needed for anything to happen:");
        Stream.of("d2", "a2","b1","b3","c")
                .filter(s-> {
                    System.out.println("filter: " + s);
                    return true; //This is the filter part, the other thing is just printing
                })
                .forEach(s->System.out.println("forEach: " + s));

        System.out.println("Aborting operation when found what looking for:");
        boolean matched = Stream.of("d2","a2","b1","b3","c")
                .map(s-> {
                    System.out.println("map: " + s);
                    return s.toUpperCase();
                })
                .anyMatch(s-> {
                    System.out.println("anyMatch: " + s);
                    return s.startsWith("A");
                });
        System.out.println("Match found: " + matched);

        System.out.println("Investigating order:");
        Stream.of("d2","a2","b1","b3","c")
                .map(s->{
                    System.out.println("map:" + s);
                    return s.toUpperCase();
                })
                .filter(s-> {
                    System.out.println("filter: " + s);
                    return s.startsWith("A");
                })
                .forEach(s->System.out.println("*** forEach: " +s));
        System.out.println("Reducing number of times map is called:");
        Stream.of("d2","a2","b1","b3","c")
                .filter(s-> {
                    System.out.println("Filter: " + s);
                    return s.startsWith("a");
                })
                .map(s-> {
                    System.out.println("map: " + s);
                    return s.toUpperCase();
                })
                .forEach(s-> System.out.println("*** forEach " + s));
        System.out.println("Add sorting in a clever way - no sorting needed...");
        Stream.of("d2","a2","b1","b3","c")
                .filter(s-> {
                    System.out.println("Filter: " + s);
                    return s.startsWith("a");
                })
                .sorted((s2,s3)-> {
                    System.out.printf("Sort: %s; %s\n", s2,s3);
                    return s2.compareTo(s3);
                })
                .map(s-> {
                    System.out.println("map: " + s);
                    return s.toUpperCase();
                })
                .forEach(s-> System.out.println("*** forEach " + s));

        System.out.println("Reusing streams");
        Stream<String> stream = Stream.of("d2","a2","b1","b3","c")
                .filter(s-> {
                    System.out.println("Filter: " + s);
                    return s.startsWith("a");
                });
        boolean b = stream.anyMatch(s->true); //After this it is not possible to use the stream any more.
        System.out.println(b);

        Supplier<Stream<String>> streamSupplier =
                ()-> Stream.of("d2","a2","b1","b3","c")
                        .filter(s-> {
                            System.out.println("Filter: " + s);
                            return s.startsWith("a");
                        });
        boolean b1 = streamSupplier.get().anyMatch(s->true);
        boolean b2 = streamSupplier.get().noneMatch(s->true);
        System.out.println("Any match is " + b1 + ". None match is " + b2);

    }
}
