/*
 * Copyright 2002-2016 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package org.springframework.core.convert.support;

import java.time.Duration;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.CompletableFuture;

import io.reactivex.Flowable;
import io.reactivex.Maybe;
import org.junit.Before;
import org.junit.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.core.publisher.FluxProcessor;
import reactor.core.publisher.Mono;
import rx.Completable;
import rx.Observable;
import rx.Single;

import org.springframework.core.ReactiveAdapter;
import org.springframework.core.ReactiveAdapterRegistry;
import org.springframework.core.ReactiveTypeDescriptor;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame;
import static org.junit.Assert.assertTrue;

/**
 * Unit tests for {@link ReactiveAdapterRegistry}.
 * @author Rossen Stoyanchev
 */
@SuppressWarnings("unchecked")
public class ReactiveAdapterRegistryTests {

	private ReactiveAdapterRegistry registry;


	@Before
	public void setUp() throws Exception {
		this.registry = new ReactiveAdapterRegistry();
	}


	@Test
	public void defaultAdapterRegistrations() throws Exception {

		// Reactor
		assertNotNull(getAdapter(Mono.class));
		assertNotNull(getAdapter(Flux.class));

		// Publisher
		assertNotNull(getAdapter(Publisher.class));

		// Completable
		assertNotNull(getAdapter(CompletableFuture.class));

		// RxJava 1
		assertNotNull(getAdapter(Observable.class));
		assertNotNull(getAdapter(Single.class));
		assertNotNull(getAdapter(Completable.class));

		// RxJava 2
		assertNotNull(getAdapter(Flowable.class));
		assertNotNull(getAdapter(io.reactivex.Observable.class));
		assertNotNull(getAdapter(io.reactivex.Single.class));
		assertNotNull(getAdapter(Maybe.class));
		assertNotNull(getAdapter(io.reactivex.Completable.class));
	}

	@Test
	public void getAdapterForReactiveSubType() throws Exception {

		ReactiveAdapter adapter1 = getAdapter(Flux.class);
		ReactiveAdapter adapter2 = getAdapter(FluxProcessor.class);

		assertSame(adapter1, adapter2);

		this.registry.registerReactiveType(
				ReactiveTypeDescriptor.multiValue(FluxProcessor.class, FluxProcessor::empty),
				o -> (FluxProcessor<?, ?>) o,
				FluxProcessor::from);

		ReactiveAdapter adapter3 = getAdapter(FluxProcessor.class);

		assertNotNull(adapter3);
		assertNotSame(adapter1, adapter3);
	}

	@Test
	public void publisherToFlux() throws Exception {
		List<Integer> sequence = Arrays.asList(1, 2, 3);
		Publisher<Integer> source = Flowable.fromIterable(sequence);
		Object target = getAdapter(Flux.class).fromPublisher(source);
		assertTrue(target instanceof Flux);
		assertEquals(sequence, ((Flux<Integer>) target).collectList().block(Duration.ofMillis(1000)));
	}

	// TODO: publisherToMono/CompletableFuture vs Single (ISE on multiple elements)?

	@Test
	public void publisherToMono() throws Exception {
		Publisher<Integer> source = Flowable.fromArray(1, 2, 3);
		Object target = getAdapter(Mono.class).fromPublisher(source);
		assertTrue(target instanceof Mono);
		assertEquals(new Integer(1), ((Mono<Integer>) target).block(Duration.ofMillis(1000)));
	}

	@Test
	public void publisherToCompletableFuture() throws Exception {
		Publisher<Integer> source = Flowable.fromArray(1, 2, 3);
		Object target = getAdapter(CompletableFuture.class).fromPublisher(source);
		assertTrue(target instanceof CompletableFuture);
		assertEquals(new Integer(1), ((CompletableFuture<Integer>) target).get());
	}

	@Test
	public void publisherToRxObservable() throws Exception {
		List<Integer> sequence = Arrays.asList(1, 2, 3);
		Publisher<Integer> source = Flowable.fromIterable(sequence);
		Object target = getAdapter(rx.Observable.class).fromPublisher(source);
		assertTrue(target instanceof rx.Observable);
		assertEquals(sequence, ((rx.Observable) target).toList().toBlocking().first());
	}

	@Test
	public void publisherToRxSingle() throws Exception {
		Publisher<Integer> source = Flowable.fromArray(1);
		Object target = getAdapter(rx.Single.class).fromPublisher(source);
		assertTrue(target instanceof rx.Single);
		assertEquals(new Integer(1), ((rx.Single<Integer>) target).toBlocking().value());
	}

	@Test
	public void publisherToRxCompletable() throws Exception {
		Publisher<Integer> source = Flowable.fromArray(1, 2, 3);
		Object target = getAdapter(rx.Completable.class).fromPublisher(source);
		assertTrue(target instanceof rx.Completable);
		assertNull(((rx.Completable) target).get());
	}

	@Test
	public void publisherToReactivexFlowable() throws Exception {
		List<Integer> sequence = Arrays.asList(1, 2, 3);
		Publisher<Integer> source = Flux.fromIterable(sequence);
		Object target = getAdapter(io.reactivex.Flowable.class).fromPublisher(source);
		assertTrue(target instanceof io.reactivex.Flowable);
		assertEquals(sequence, ((io.reactivex.Flowable) target).toList().blockingGet());
	}

	@Test
	public void publisherToReactivexObservable() throws Exception {
		List<Integer> sequence = Arrays.asList(1, 2, 3);
		Publisher<Integer> source = Flowable.fromIterable(sequence);
		Object target = getAdapter(io.reactivex.Observable.class).fromPublisher(source);
		assertTrue(target instanceof io.reactivex.Observable);
		assertEquals(sequence, ((io.reactivex.Observable) target).toList().blockingGet());
	}

	@Test
	public void publisherToReactivexSingle() throws Exception {
		Publisher<Integer> source = Flowable.fromArray(1);
		Object target = getAdapter(io.reactivex.Single.class).fromPublisher(source);
		assertTrue(target instanceof io.reactivex.Single);
		assertEquals(new Integer(1), ((io.reactivex.Single<Integer>) target).blockingGet());
	}

	@Test
	public void publisherToReactivexCompletable() throws Exception {
		Publisher<Integer> source = Flowable.fromArray(1, 2, 3);
		Object target = getAdapter(io.reactivex.Completable.class).fromPublisher(source);
		assertTrue(target instanceof io.reactivex.Completable);
		assertNull(((io.reactivex.Completable) target).blockingGet());
	}

	@Test
	public void rxObservableToPublisher() throws Exception {
		List<Integer> sequence = Arrays.asList(1, 2, 3);
		Object source = rx.Observable.from(sequence);
		Object target = getAdapter(rx.Observable.class).toPublisher(source);
		assertTrue("Expected Flux Publisher: " + target.getClass().getName(), target instanceof Flux);
		assertEquals(sequence, ((Flux<Integer>) target).collectList().block(Duration.ofMillis(1000)));
	}

	@Test
	public void rxSingleToPublisher() throws Exception {
		Object source = rx.Single.just(1);
		Object target = getAdapter(rx.Single.class).toPublisher(source);
		assertTrue("Expected Mono Publisher: " + target.getClass().getName(), target instanceof Mono);
		assertEquals(new Integer(1), ((Mono<Integer>) target).block(Duration.ofMillis(1000)));
	}

	@Test
	public void rxCompletableToPublisher() throws Exception {
		Object source = rx.Completable.complete();
		Object target = getAdapter(rx.Completable.class).toPublisher(source);
		assertTrue("Expected Mono Publisher: " + target.getClass().getName(), target instanceof Mono);
		((Mono<Void>) target).block(Duration.ofMillis(1000));
	}

	@Test
	public void reactivexFlowableToPublisher() throws Exception {
		List<Integer> sequence = Arrays.asList(1, 2, 3);
		Object source = io.reactivex.Flowable.fromIterable(sequence);
		Object target = getAdapter(io.reactivex.Flowable.class).toPublisher(source);
		assertTrue("Expected Flux Publisher: " + target.getClass().getName(), target instanceof Flux);
		assertEquals(sequence, ((Flux<Integer>) target).collectList().block(Duration.ofMillis(1000)));
	}

	@Test
	public void reactivexObservableToPublisher() throws Exception {
		List<Integer> sequence = Arrays.asList(1, 2, 3);
		Object source = io.reactivex.Observable.fromIterable(sequence);
		Object target = getAdapter(io.reactivex.Observable.class).toPublisher(source);
		assertTrue("Expected Flux Publisher: " + target.getClass().getName(), target instanceof Flux);
		assertEquals(sequence, ((Flux<Integer>) target).collectList().block(Duration.ofMillis(1000)));
	}

	@Test
	public void reactivexSingleToPublisher() throws Exception {
		Object source = io.reactivex.Single.just(1);
		Object target = getAdapter(io.reactivex.Single.class).toPublisher(source);
		assertTrue("Expected Mono Publisher: " + target.getClass().getName(), target instanceof Mono);
		assertEquals(new Integer(1), ((Mono<Integer>) target).block(Duration.ofMillis(1000)));
	}

	@Test
	public void reactivexCompletableToPublisher() throws Exception {
		Object source = io.reactivex.Completable.complete();
		Object target = getAdapter(io.reactivex.Completable.class).toPublisher(source);
		assertTrue("Expected Mono Publisher: " + target.getClass().getName(), target instanceof Mono);
		((Mono<Void>) target).block(Duration.ofMillis(1000));
	}

	@Test
	public void CompletableFutureToPublisher() throws Exception {
		CompletableFuture<Integer> future = new CompletableFuture();
		future.complete(1);
		Object target = getAdapter(CompletableFuture.class).toPublisher(future);
		assertTrue("Expected Mono Publisher: " + target.getClass().getName(), target instanceof Mono);
		assertEquals(new Integer(1), ((Mono<Integer>) target).block(Duration.ofMillis(1000)));
	}


	private ReactiveAdapter getAdapter(Class<?> reactiveType) {
		return this.registry.getAdapter(reactiveType);
	}

}
