package com.nurkiewicz.reactor;

import org.junit.Ignore;
import org.junit.Test;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import reactor.core.publisher.Mono;

import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;

import static org.assertj.core.api.Assertions.assertThat;

@Ignore
public class R020_MonoSubscribing {

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

	@Test
	public void noWorkHappensWithoutSubscription() throws Exception {
		//given
		AtomicBoolean flag = new AtomicBoolean();

		//when
		log.info("About to create Mono");
		Mono.fromCallable(() -> {
			log.info("Doing hard work");
			flag.set(true);
			return 42;
		});
		log.info("Mono was created");

		//then
		assertThat(flag).isFalse();
	}

	/**
	 * Notice on which thread everything runs
	 */
	@Test
	public void blockTriggersWork() throws Exception {
		//given
		AtomicBoolean flag = new AtomicBoolean();

		//when
		log.info("About to create Mono");
		final Mono<Integer> work = Mono.fromCallable(() -> {
			log.info("Doing hard work");
			flag.set(true);
			return 42;
		});
		log.info("Mono was created");
		final Integer result = work.block();
		log.info("Work is done");

		//then
		assertThat(flag).isTrue();
		assertThat(result).isEqualTo(42);
	}

	@Test
	public void subscriptionTriggersWork() throws Exception {
		//given
		log.info("About to create Mono");

		//when
		final Mono<Integer> work = Mono.fromCallable(() -> {
			log.info("Doing hard work");
			return 42;
		});

		//then
		log.info("Mono was created");

		work.subscribe(i -> log.info("Received {}", i));

		log.info("Work is done");
	}

	@Test
	public void subscriptionOfManyNotifications() throws Exception {
		//given
		log.info("About to create Mono");

		//when
		final Mono<Integer> work = Mono.fromCallable(() -> {
			log.info("Doing hard work");
			return 42;
		});

		//then
		log.info("Mono was created");

		work.subscribe(
				i -> log.info("Received {}", i),
				ex -> log.error("Opps!", ex),
				() -> log.info("Mono completed")
		);

		log.info("Work is done");
	}

	private final AtomicBoolean onNext = new AtomicBoolean();
	private final AtomicReference<Throwable> error = new AtomicReference<>();
	private final AtomicBoolean completed = new AtomicBoolean();

	/**
	 * TODO create a {@link Mono} that completes with an error
	 */
	@Test
	public void monoCompletingWithError() {
		//given

		//when
		final Mono<Integer> work = null;

		//then
		work.subscribe(
				i -> onNext.set(true),
				ex -> error.set(ex),
				() -> completed.set(true)
		);

		//Hint: you don't normally test streams like that! Don't get used to it
		assertThat(onNext).isFalse();
		assertThat(error.get())
				.isInstanceOf(IOException.class)
				.hasMessage("Simulated");
		assertThat(completed).isFalse();
	}

	/**
	 * TODO create a {@link Mono} that completes normally without emitting any value
	 */
	@Test
	public void monoCompletingWithoutAnyValue() throws Exception {
		//given

		//when
		final Mono<Integer> work = null;

		//then
		work.subscribe(
				i -> onNext.set(true),
				ex -> error.set(ex),
				() -> completed.set(true)
		);

		//Hint: you don't normally test streams like that! Don't get used to it
		assertThat(onNext).isFalse();
		assertThat(error).hasValue(null);
		assertThat(completed).isTrue();
	}

	/**
	 * TODO create a {@link Mono} that never completes
	 * What happens if you {@link Mono#block()} on such {@link Mono}?
	 * Hint: {@link Mono#never()}
	 */
	@Test
	public void monoThatNeverCompletesAtAll() throws Exception {
		//given

		//when
		final Mono<Integer> work = null;

		//then
		work.subscribe(
				i -> onNext.set(true),
				ex -> error.set(ex),
				() -> completed.set(true)
		);

		//Hint: you don't normally test streams like that! Don't get used to it
		assertThat(onNext).isFalse();
		assertThat(error).hasValue(null);
		assertThat(completed).isFalse();
	}

}