package com.codepoetics.protonpack.collectors;

import org.junit.Test;

import java.util.List;
import java.util.Optional;
import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
import java.util.stream.IntStream;

import static java.util.stream.Collectors.toList;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.equalTo;

public class CompletableFuturesTest {

    private static final Random random = new Random();

    @Test
    public void collectsValuesFromCompletableFutures() throws ExecutionException, InterruptedException {
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        CompletableFuture<List<Integer>> integers = IntStream.range(0, 1000)
                .mapToObj(i -> CompletableFuture.supplyAsync(() -> {
                    try {
                        Thread.sleep(random.nextInt(100));
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    return i;
                }, threadPool))
                .collect(CompletableFutures.toFutureList());

        assertThat(
                integers.get(),
                equalTo(IntStream.range(0, 1000).mapToObj(Integer::valueOf).collect(toList())));
    }

    @Test
    public void failsFastOnAnyFutureFailure() throws ExecutionException, InterruptedException {
        ExecutorService threadPool = Executors.newFixedThreadPool(10);
        IllegalStateException expectedException = new IllegalStateException("19! Aaargh!");
        CompletableFuture<List<Integer>> integers = IntStream.range(0, 1000)
                .mapToObj(i -> CompletableFuture.supplyAsync(() -> {
                    try {
                        Thread.sleep(random.nextInt(100));
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    if (i == 19) {
                        throw expectedException;
                    }
                    return i;
                }, threadPool))
                .collect(CompletableFutures.toFutureList());

        AtomicReference<Throwable> exc = new AtomicReference<>();
        integers.handle((success, failure) -> { exc.set(failure.getCause()); return null; }).get();

        assertThat(exc.get(), equalTo(expectedException));
    }

    @Test
    public void reducesFutureInts() throws ExecutionException, InterruptedException {
        ExecutorService threadPool = Executors.newFixedThreadPool(10);

        final CompletableFuture<Optional<Integer>> collected = IntStream.range(1, 1000)
                .parallel()
                .mapToObj(i -> CompletableFuture.supplyAsync(() -> {
                    try {
                        Thread.sleep(random.nextInt(100));
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    return i;
                }, threadPool))
                .collect(CompletableFutures.reducing((l, r) -> l + r));

        assertThat(collected.get().get(), equalTo(499500));
    }
}