/*
 * Copyright (c) 2011-2017 Pivotal Software Inc, All Rights Reserved.
 *
 * 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
 *
 *       https://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 reactor.test.publisher;

import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.IntFunction;

import org.reactivestreams.Subscription;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;

public abstract class FluxOperatorTest<I, O>
		extends BaseOperatorTest<I, Flux<I>, O, Flux<O>> {

	public final Scenario<I, O> scenario(Function<Flux<I>, ? extends Flux<O>> scenario) {
		if (defaultEmpty) {
			return Scenario.create(scenario)
			               .applyAllOptions(defaultScenario.duplicate()
			                                               .receiverEmpty());
		}
		return Scenario.create(scenario)
		               .applyAllOptions(defaultScenario);
	}

	static public final class Scenario<I, O>
			extends OperatorScenario<I, Flux<I>, O, Flux<O>> {

		static <I, O> Scenario<I, O> create(Function<Flux<I>, ? extends Flux<O>> scenario) {
			return new Scenario<>(scenario, new Exception("scenario:"));
		}

		Scenario(Function<Flux<I>, ? extends Flux<O>> scenario, Exception stack) {
			super(scenario, stack);
		}

		@Override
		Scenario<I, O> duplicate() {
			return new Scenario<>(body, stack).applyAllOptions(this);
		}

		@Override
		public Scenario<I, O> shouldHitDropNextHookAfterTerminate(boolean shouldHitDropNextHookAfterTerminate) {
			super.shouldHitDropNextHookAfterTerminate(shouldHitDropNextHookAfterTerminate);
			return this;
		}

		@Override
		public Scenario<I, O> shouldHitDropErrorHookAfterTerminate(boolean shouldHitDropErrorHookAfterTerminate) {
			super.shouldHitDropErrorHookAfterTerminate(
					shouldHitDropErrorHookAfterTerminate);
			return this;
		}

		@Override
		public Scenario<I, O> shouldAssertPostTerminateState(boolean shouldAssertPostTerminateState) {
			super.shouldAssertPostTerminateState(shouldAssertPostTerminateState);
			return this;
		}

		@Override
		public Scenario<I, O> fusionMode(int fusionMode) {
			super.fusionMode(fusionMode);
			return this;
		}

		@Override
		public Scenario<I, O> fusionModeThreadBarrier(int fusionModeThreadBarrier) {
			super.fusionModeThreadBarrier(fusionModeThreadBarrier);
			return this;
		}

		@Override
		public Scenario<I, O> prefetch(int prefetch) {
			super.prefetch(prefetch);
			return this;
		}

		@Override
		public Scenario<I, O> producerEmpty() {
			super.producerEmpty();
			return this;
		}

		@Override
		public Scenario<I, O> producerNever() {
			super.producerNever();
			return this;
		}

		@Override
		public Scenario<I, O> producerError(RuntimeException e) {
			super.producerError(e);
			return this;
		}

		@Override
		public Scenario<I, O> droppedError(RuntimeException e) {
			super.droppedError(e);
			return this;
		}

		@Override
		public Scenario<I, O> droppedItem(I item) {
			super.droppedItem(item);
			return this;
		}

		@Override
		public Scenario<I, O> producer(int n, IntFunction<? extends I> producer) {
			super.producer(n, producer);
			return this;
		}

		@Override
		public final Scenario<I, O> receiverEmpty() {
			super.receiverEmpty();
			return this;
		}

		@Override
		@SafeVarargs
		public final Scenario<I, O> receive(Consumer<? super O>... receivers) {
			super.receive(receivers);
			return this;
		}

		@Override
		@SafeVarargs
		public final Scenario<I, O> receiveValues(O... receivers) {
			super.receiveValues(receivers);
			return this;
		}

		@Override
		public final Scenario<I, O> receive(int i, IntFunction<? extends O> receivers) {
			super.receive(i, receivers);
			return this;
		}

		@Override
		public Scenario<I, O> receiverDemand(long d) {
			super.receiverDemand(d);
			return this;
		}

		@Override
		public Scenario<I, O> description(String description) {
			super.description(description);
			return this;
		}

		@Override
		public Scenario<I, O> verifier(Consumer<StepVerifier.Step<O>> verifier) {
			super.verifier(verifier);
			return this;
		}

		@Override
		public Scenario<I, O> applyAllOptions(OperatorScenario<I, Flux<I>, O, Flux<O>> source) {
			super.applyAllOptions(source);
			return this;
		}
	}

	@Override
	Flux<O> conditional(Flux<O> output) {
		return output.filter(t -> true);
	}

	@Override
	protected Flux<O> doOnSubscribe(Flux<O> output,
			Consumer<? super Subscription> doOnSubscribe) {
		return output.doOnSubscribe(doOnSubscribe);
	}

	@Override
	Flux<I> hide(Flux<I> input) {
		return input.hide();
	}

	@Override
	@SuppressWarnings("unchecked")
	protected Flux<I> sourceScalar(OperatorScenario<I, Flux<I>, O, Flux<O>> scenario) {
		if(scenario.producerCount() == 0){
			return (Flux<I>)Flux.empty();
		}
		return Flux.just(scenario.producingMapper.apply(0));
	}

	@Override
	@SuppressWarnings("unchecked")
	protected Flux<I> sourceCallable(OperatorScenario<I, Flux<I>, O, Flux<O>> scenario) {
		if(scenario.producerCount() == 0){
			return (Flux<I>)Mono.fromRunnable(() -> {})
			                    .flux();
		}
		return (Flux<I>)Mono.fromCallable(() -> scenario.producingMapper.apply(0))
		                    .flux();
	}

	@Override
	Flux<I> withFluxSource(Flux<I> input) {
		return input;
	}

	@Override
	@SuppressWarnings("unchecked")
	protected final OperatorScenario<I, Flux<I>, O, Flux<O>> defaultScenarioOptions(
			OperatorScenario<I, Flux<I>, O, Flux<O>> defaultOptions) {
		Scenario<I, O> s = new Scenario<I, O>(null, null).applyAllOptions(defaultOptions)
		                                                 .producer(3,
				                                                 i -> (I) (i == 0 ?
						                                                 "test" :
						                                                 "test" + i))
		                                                 .receive(3,
				                                                 i -> (O) (i == 0 ?
						                                                 "test" :
						                                                 "test" + i))
		                                                 .droppedError(new RuntimeException("dropped"))
		                                                 .droppedItem((I)"dropped");
		this.defaultScenario = s;
		return defaultScenarioOptions(s);
	}

	@SuppressWarnings("unchecked")
	protected Scenario<I, O> defaultScenarioOptions(Scenario<I, O> defaultOptions) {
		return defaultOptions;
	}

	@Override
	protected List<Scenario<I, O>> scenarios_operatorSuccess() {
		return Collections.emptyList();
	}

	@Override
	protected List<Scenario<I, O>> scenarios_operatorError() {
		return Collections.emptyList();
	}

	@Override
	protected List<Scenario<I, O>> scenarios_errorFromUpstreamFailure() {
		return scenarios_operatorSuccess();
	}

	@Override
	protected List<Scenario<I, O>> scenarios_touchAndAssertState() {
		return scenarios_operatorSuccess();
	}

}