/*
 * This File is one of the examples from Java Application Development
 * Do not reproduce this code for others or use it in a commercial setting without prior permission from the author.
 */

package salesApplication;

import java.time.LocalTime;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.IntConsumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.IntStream;

/**
 *
 * @author Matthew Gregory
 * @website www.matthewgregory-author.com.au
 *
 */
public class TestParallel {
    public static List<String> letters = Arrays.asList("a","b","c","d","e","f","g","h","i","j");
    public static List<Integer> numbers = Arrays.asList(1,2,3,4,5,6,7,8,9,10); 
    public static int count = 0;
    
    //A lambda expression that prints out thread details and takes up time to 
    //illustrate parallel vrs sequential processing
    public static Consumer processStream = s -> {
            System.out.println(LocalTime.now() + " - value: " + s +
                                " - thread: " + Thread.currentThread().getName());
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
    };
    
    //A lambda expression that the elements in a stream by incrementing a global variable 
    //this illustrates the dangers of stateful operations
    public static IntConsumer countStream = i -> {
                            count++;//updating count hence making lambda stateful.
                            try {
                                Thread.sleep(200);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        };
    
    public static void main(String[] args) {
        //Reduction - concept
//        long result = numbers.stream().count();
//        System.out.println("Number of numbers: "+result);
        //Using reduce()
//        long count = numbers.stream().reduce(
//                                (a,b)->a+1
//                                //a represents the total we are calculating
//                                //b represents this element of the stream
//                                //In this case just add 1 to the total each time
//                                ).get();//This version of reduce generates an Optional object
//        System.out.println("Number of numbers: "+count);
        //Another simple example - sum
//        long sum = numbers.stream().reduce(0//start at 0
//                                ,(a,b)->a+b
//                                //In this case add b to the total each time
//                                );
//        System.out.println("Total of numbers: "+sum);
        //Yet another example - concatination 
//        String contents = letters.stream().reduce("{"//start with "{"
//                                ,(a,b)->{if (a.equals("{")){//don't put a ", " for first time
//                                                              return a+b;
//                                                          }
//                                                          else {
//                                                              return a+", "+b;
//                                                          }
//                                        }
//                                )
//                            +"}";// add a "}" to the end.
//        System.out.println("Content of letters: "+contents);


        //Parellising
        //Sequential: 
//        numbers.stream().forEach(processStream);
//        IntStream stream = IntStream.range(1, 1000);
//        count = 0;
//        stream.forEach(countStream);
//        System.out.println("Count of our integer stream is: "+ count);

        //Parallel:
//          numbers.stream().parallel().forEach(processStream);
        //Doesn't matter where the parallel() goes:
//        numbers.stream().forEach(processStream).parallel();
        //Making the stream parallel from the start
        Stream<Integer> numbersStream = numbers.parallelStream();
//        numbersStream.forEach(processStream);
        //Turning a parallel stream back into sequential:
//        numbersStream.sequential().forEach(processStream);
        //Testing if stream is parallel or not:
//        System.out.println("Numbers stream is parallel? "+numbersStream.isParallel());   
        
        //non-determinism
//        System.out.println("Element found (parallel): "+numbers.parallelStream().findAny().get());
//        System.out.println("Element found (sequential): "+numbers.stream().sequential().findAny().get());

        //Associative
//        String contents = letters.stream().reduce("{",(a,b)->{if (a.equals("{")){return a+b;}else{return a+", "+b;}})+"}";
//        String contentsParallel = letters.stream().parallel().reduce("{",(a,b)->{if (a.equals("{")){return a+b;}else{return a+", "+b;}})+"}";
//        System.out.println("Processing letters in sequence: "+contents);
//        System.out.println("Processing letters in parallel: "+contentsParallel);


        
        //Stateful and Stateless
//        IntStream stream = IntStream.range(1, 1000);
//        count = 0;
//        stream.parallel().forEach(countStream);
//        System.out.println("Count of our integer stream is: "+count);

        //Use the streams count() operation
//        IntStream stream = IntStream.range(1, 1000);
//        long count = stream.parallel().count();
//        System.out.println("Count of our integer stream is: "+count);       
    }
}
