package io.smallrye.reactive.streams.stages;

import static org.assertj.core.api.Assertions.assertThat;
import static org.awaitility.Awaitility.await;

import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;

import org.eclipse.microprofile.reactive.streams.operators.PublisherBuilder;
import org.eclipse.microprofile.reactive.streams.operators.ReactiveStreams;
import org.eclipse.microprofile.reactive.streams.operators.spi.Graph;
import org.eclipse.microprofile.reactive.streams.operators.spi.Stage;
import org.junit.Test;

import io.reactivex.Flowable;
import io.reactivex.schedulers.Schedulers;
import io.smallrye.reactive.streams.Engine;

/**
 * Checks the behavior of the {@link ConcatStageFactory} class, especially the thread handling.
 *
 * @author <a href="http://escoffier.me">Clement Escoffier</a>
 */
public class ConcatStageFactoryTest extends StageTestBase {

    private final ConcatStageFactory factory = new ConcatStageFactory();

    @Test(expected = NullPointerException.class)
    public void testWithoutAStage() {
        factory.create(new Engine(), null);
    }

    @Test(expected = NullPointerException.class)
    public void testWithoutEngine() {
        Graph g1 = () -> Collections.singletonList((Stage.Of) () -> Arrays.asList(1, 2, 3));
        Graph g2 = () -> Collections.singletonList((Stage.Of) () -> Arrays.asList(1, 2, 3));
        factory.create(null, new Stage.Concat() {
            @Override
            public Graph getFirst() {
                return g1;
            }

            @Override
            public Graph getSecond() {
                return g2;
            }
        });
    }

    @Test
    public void testConcatenationWhenAllEmissionAreMadeFromMain() throws ExecutionException, InterruptedException {
        PublisherBuilder<Integer> f1 = ReactiveStreams.of(1, 2, 3);
        PublisherBuilder<Integer> f2 = ReactiveStreams.of(4, 5, 6);

        String currentThreadName = Thread.currentThread().getName();
        LinkedHashSet<String> threads = new LinkedHashSet<>();
        CompletionStage<List<Integer>> list = ReactiveStreams.concat(f1, f2)
                .peek(i -> threads.add(Thread.currentThread().getName()))
                .toList().run();
        await().until(() -> list.toCompletableFuture().isDone());

        List<Integer> ints = list.toCompletableFuture().get();
        assertThat(ints).containsExactly(1, 2, 3, 4, 5, 6);
        assertThat(threads).hasSize(1).contains(currentThreadName);
    }

    @Test
    public void testConcatenationWhenAllEmissionsAreMadeFromDifferentThreads() throws ExecutionException,
            InterruptedException {

        Flowable<Integer> firstStream = Flowable.fromArray(1, 2, 3).observeOn(Schedulers.io());
        Flowable<Integer> secondStream = Flowable.fromArray(4, 5, 6).observeOn(Schedulers.computation());

        LinkedHashSet<String> threads = new LinkedHashSet<>();
        CompletionStage<List<Integer>> list = ReactiveStreams.concat(
                ReactiveStreams.fromPublisher(firstStream),
                ReactiveStreams.fromPublisher(secondStream))
                .peek(i -> threads.add(Thread.currentThread().getName()))
                .toList().run();
        await().until(() -> list.toCompletableFuture().isDone());

        List<Integer> ints = list.toCompletableFuture().get();
        assertThat(ints).containsExactly(1, 2, 3, 4, 5, 6);
        assertThat(threads).hasSize(2);
    }

}