package com.nurkiewicz.reactor;

import com.nurkiewicz.reactor.email.Email;
import com.nurkiewicz.reactor.email.Inbox;
import org.junit.After;
import org.junit.Ignore;
import org.junit.Test;
import org.reactivestreams.Publisher;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;

import java.util.List;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;

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

@Ignore
public class R023_CallbackToFlux {

	private static final Logger log = LoggerFactory.getLogger(R023_CallbackToFlux.class);

	private final Inbox inbox = new Inbox();

	@After
	public void closeInbox() throws Exception {
		inbox.close();
	}

	@Test
	public void oldSchoolCallbackApi() throws Exception {
		inbox.read("[email protected]", email ->
				log.info("You have e-mail\n{}", email)
		);
		TimeUnit.SECONDS.sleep(10);
	}

	/**
	 * TODO Receive first five messages from any inbox
	 * Hint: <code>emails</code> list must be thread safe
	 * Notice: how do you stop subscription?
	 */
	@Test
	public void getFirstFiveEmails() throws Exception {
		//given
		List<Email> emails = null;

		//when
		inbox.read("[email protected]", email ->
				log.info("You have e-mail\n{}", email)
		);
		inbox.read("[email protected]", email ->
				log.info("You have e-mail\n{}", email)
		);

		//then
		await().until(() -> emails, hasSize(5));
	}

	@Test
	public void convertCallbacksToStream() throws Exception {
		//given
		final Flux<Email> emails = Flux.create(sink ->
				inbox.read("[email protected]", sink::next));

		//when
		final List<Email> list = emails
				.take(5)
				.collectList()
				.block();

		//then
		assertThat(list).hasSize(5);
	}

	@Test
	public void testingUsingStepVerifier() throws Exception {
		//given
		final Flux<Email> emails = Flux.create(sink ->
				inbox.read("[email protected]", sink::next));

		//when
		emails
				.take(5)
				.as(StepVerifier::create)
				.expectNextCount(5)
				.verifyComplete();
	}

	/**
	 * TODO Merge two streams of messages into a single stream and {@link Flux#take(long)} the first 5.
	 * Hint Static {@link Flux#merge(Publisher[])}
	 */
	@Test
	public void mergeMessagesFromTwoInboxes() throws Exception {
		//given
		final Flux<Email> foo = Flux.create(sink ->
				inbox.read("[email protected]", sink::next));
		final Flux<Email> bar = Flux.create(sink ->
				inbox.read("[email protected]", sink::next));

		//when
		Flux<Email> merged = null; //TODO

		//then
		StepVerifier
				.create(merged)
				.expectNextCount(5)
				.verifyComplete();
	}

	@Test
	public void fluxDoesNotAcceptNull() throws Exception {
		Flux
				.create(sink -> inbox.read("[email protected]", sink::next))
				.blockLast();
	}

	/**
	 * TODO terminate the stream when <code>null</code> is received from callback.
	 * Hint: <code>sink.complete()</code>
	 */
	@Test
	public void handleNullAsEndOfStream() throws Exception {
		//when
		final Flux<Email> emails = Flux
				.create(sink ->
						inbox.read("[email protected]", e -> {
							if (e != null) {
								sink.next(e);
							} else {
								sink.complete();
							}
						}));

		//then
		emails
				.as(StepVerifier::create)
				.expectNextCount(2)
				.verifyComplete();
	}

	/**
	 * TODO use {@link Flux#as(Function)} operator with {@link #toList(Flux)} method
	 */
	@Test
	public void asOperator() throws Exception {
		//given
		final Flux<Email> foo = Flux.create(sink ->
				inbox.read("[email protected]", sink::next));

		//when
		final List<Email> emails = toList(foo);

		//then
		assertThat(emails).hasSize(3);
	}

	List<Email> toList(Flux<Email> input) {
		return input
				.take(3)
				.collectList()
				.block();
	}

	@Test
	public void fluxCreateIsNotCached() throws Exception {
		//given
		final Flux<Email> foo = Flux.create(sink -> {
			log.info("Subscribing to e-mails");
			inbox.read("[email protected]", sink::next);
		});

		//when
		foo.subscribe();
		foo.subscribe();

		//then
	}

}