import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.RecursiveTask;
import java.util.function.Function;

class Data {

    private final long value;

    public Data() {
        this.value = (int) (Math.random() * 500);
    }

    public long calculateValue() {
        // On simule un traitement "long", avec un sleep d'1 ms
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
        return this.value;
    }
}

class SumAll {

    /**
     * Implémentation iterative via une boucle for.
     */
    public static long iterate(List<Data> dataList) {
        long result = 0L;
        for (Data data : dataList) {
            result += data.calculateValue();
        }
        return result;
    }

    /**
     * Implémentation séquentielle en utilisant les Streams.
     */
    public static long stream(List<Data> dataList) {
        return dataList.stream()
                .mapToLong(Data::calculateValue)
                .reduce(0L, (a, b) -> a + b);
    }

    /**
     * Implémentation parallèle via l'API Fork/Join.
     */
    public static long forkJoin(List<Data> dataList) {
        return new DataSumTask(dataList).compute();
    }

    /**
     * Implémentation parallèle en utilisant les Streams.
     */
    public static long parallelStream(List<Data> dataList) {
        return dataList.parallelStream()
                .mapToLong(Data::calculateValue)
                .reduce(0L, (a, b) -> a + b);
    }

    /**
     * Implémentation parallèle via l'API Fork/Join.
     */
    static class DataSumTask extends RecursiveTask<Long> {
        private final List<Data> list;

        public DataSumTask(List<Data> list) {
            this.list = list;
        }

        @Override
        protected Long compute() {
            final int size = this.list.size();
            // Si la liste comporte moins de 10 éléments,
            // on considère que le traitement est "simple" :
            if (size < 10) {
                // On se contente alors d'effectuer le traitement itératif
                // via l'implémentation séquentielle précédente
                long result = 0L;
                for (Data data : this.list) {
                    result += data.calculateValue();
                }
                return result;
            }
           
            // Sinon on découpe la liste en deux :
            final int middle = size / 2;
            final List list1 = this.list.subList(0, middle);
            final List list2 = this.list.subList(middle, size);
           
           
            // On crée une première tâche pour la première liste :
            DataSumTask task1 = new DataSumTask(list1);
            // que l'on exécute dans un autre thread :
            task1.fork();
           
            // On crée une seconde tâche pour la seconde liste :
            DataSumTask task2 = new DataSumTask(list2);
            // que l'on execute directement
            // (sinon le thread courant n'aurait rien à faire)
            long result2 = task2.compute();
           
            // Une fois fini, on attend la fin de la première tâche :
            long result1 = task1.join();
           
            // Et on cumule le résultat des deux tâches :
            return result1 + result2;
        }
    }
}

public class Main {

    private static List<Data> generateData(int max) {
        final List<Data> list = new ArrayList<>();
        for (int i = 0; i < max; i++) {
            list.add(new Data());
        }
        return list;
    }

    private static void test(List<Data> dataList, String name, Function<List<Data>, ? extends Object> function) {
        long ms = System.currentTimeMillis();
        Object result = function.apply(dataList);
        ms = System.currentTimeMillis() - ms;
        System.out.printf("%-20.20s : [%s] in %6d ms%n", name, result, ms);
    }

    public static void main(String... args) throws Exception {
        List<Data> dataList = generateData(1000);

        System.out.println("Starting, using " + Runtime.getRuntime().availableProcessors() + " processors...");
        
        for (int i = 0; i < 5; i++) {
            test(dataList, "Itération", SumAll::iterate);
            test(dataList, "Stream", SumAll::stream);
            test(dataList, "Fork/Join", SumAll::forkJoin);
            test(dataList, "Parallel Stream", SumAll::parallelStream);
            System.out.println("-------------------------------");
        }
    }
}
