/*
 * 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.core.publisher;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;

import org.assertj.core.api.Assertions;
import org.junit.Test;
import org.reactivestreams.Subscription;

import reactor.core.CoreSubscriber;
import reactor.core.Scannable;
import reactor.test.publisher.FluxOperatorTest;
import reactor.test.subscriber.AssertSubscriber;
import reactor.test.util.RaceTestUtils;
import reactor.util.context.Context;

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

public class FluxScanTest extends FluxOperatorTest<String, String> {

	@Override
	protected Scenario<String, String> defaultScenarioOptions(Scenario<String, String> defaultOptions) {
		return defaultOptions.receive(3, i -> item(0));
	}

	@Override
	protected List<Scenario<String, String>> scenarios_operatorSuccess() {
		return Arrays.asList(
				scenario(f -> f.scan((a, b) -> a))
		);
	}

	@Override
	protected List<Scenario<String, String>> scenarios_operatorError() {
		return Arrays.asList(
				scenario(f -> f.scan((a, b) -> {
					throw exception();
				})).receiveValues(item(0)),

				scenario(f -> f.scan((a, b) -> null))
						.receiveValues(item(0))
		);
	}

	@Test(expected = NullPointerException.class)
	public void sourceNull() {
		new FluxScan<>(null, (a, b) -> a);
	}

	@Test(expected = NullPointerException.class)
	public void accumulatorNull() {
		Flux.never().scan(null);
	}

	@Test
	public void normal() {
		AssertSubscriber<Integer> ts = AssertSubscriber.create();

		Flux.range(1, 10)
		    .scan((a, b) -> b)
		    .subscribe(ts);

		ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
		  .assertComplete()
		  .assertNoError();
	}

	@Test
	public void normalBackpressured() {
		AssertSubscriber<Integer> ts = AssertSubscriber.create(0);

		Flux.range(1, 10)
		    .scan((a, b) -> b)
		    .subscribe(ts);

		ts.assertNoValues()
		  .assertNoError()
		  .assertNotComplete();

		ts.request(2);

		ts.assertValues(1, 2)
		  .assertNoError()
		  .assertNotComplete();

		ts.request(8);

		ts.assertValues(1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
		  .assertComplete()
		  .assertNoError();
	}

	@Test
	public void accumulatorThrows() {
		AssertSubscriber<Integer> ts = AssertSubscriber.create();

		Flux.range(1, 10)
		    .scan((a, b) -> {
			    throw new RuntimeException("forced failure");
		    })
		    .subscribe(ts);

		ts.assertValues(1)
		  .assertNotComplete()
		  .assertError(RuntimeException.class)
		  .assertErrorMessage("forced failure");
	}

	@Test
	public void accumulatorReturnsNull() {
		AssertSubscriber<Integer> ts = AssertSubscriber.create();

		Flux.range(1, 10)
		    .scan((a, b) -> null)
		    .subscribe(ts);

		ts.assertValues(1)
		  .assertNotComplete()
		  .assertError(NullPointerException.class);
	}

	@Test
	public void onNextAndCancelRaceDontPassNullToAccumulator() {
		AtomicBoolean accumulatorCheck = new AtomicBoolean(true);
		final AssertSubscriber<Integer> testSubscriber = AssertSubscriber.create();

		FluxScan.ScanSubscriber<Integer> sub =
				new FluxScan.ScanSubscriber<>(testSubscriber, (accumulated, next) -> {
					if (accumulated == null || next == null) {
						accumulatorCheck.set(false);
					}
					return next;
				});

		sub.onSubscribe(Operators.emptySubscription());

		for (int i = 0; i < 1000; i++) {
			RaceTestUtils.race(sub::cancel, () -> sub.onNext(1));

			testSubscriber.assertNoError();
			assertThat(accumulatorCheck).as("no NPE due to onNext/cancel race in round " + i).isTrue();
		}
	}

	@Test
	public void noRetainValueOnComplete() {
		final AssertSubscriber<Object> testSubscriber = AssertSubscriber.create();

		FluxScan.ScanSubscriber<Integer> sub =
				new FluxScan.ScanSubscriber<>(testSubscriber,
						(current, next) -> current + next);

		sub.onSubscribe(Operators.emptySubscription());

		sub.onNext(1);
		sub.onNext(2);
		assertThat(sub.value).isEqualTo(3);

		sub.onComplete();
		assertThat(sub.value).isNull();

		testSubscriber.assertNoError();
	}

	@Test
	public void noRetainValueOnError() {
		final AssertSubscriber<Object> testSubscriber = AssertSubscriber.create();

		FluxScan.ScanSubscriber<Integer> sub =
				new FluxScan.ScanSubscriber<>(testSubscriber,
						(current, next) -> current + next);

		sub.onSubscribe(Operators.emptySubscription());

		sub.onNext(1);
		sub.onNext(2);
		assertThat(sub.value).isEqualTo(3);

		sub.onError(new RuntimeException("boom"));
		assertThat(sub.value).isNull();

		testSubscriber.assertErrorMessage("boom");
	}

	@Test
    public void scanSubscriber() {
        CoreSubscriber<Integer> actual = new LambdaSubscriber<>(null, e -> {}, null, null);
        FluxScan.ScanSubscriber<Integer> test = new FluxScan.ScanSubscriber<>(actual, (i, j) -> i + j);
        Subscription parent = Operators.emptySubscription();
        test.onSubscribe(parent);

        Assertions.assertThat(test.scan(Scannable.Attr.PARENT)).isSameAs(parent);
        Assertions.assertThat(test.scan(Scannable.Attr.ACTUAL)).isSameAs(actual);
        test.value = 5;
        Assertions.assertThat(test.scan(Scannable.Attr.BUFFERED)).isEqualTo(1);

        Assertions.assertThat(test.scan(Scannable.Attr.TERMINATED)).isFalse();
        test.onComplete();
        Assertions.assertThat(test.scan(Scannable.Attr.TERMINATED)).isTrue();
    }
}