/*
 * 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.Arrays;
import java.util.List;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.function.Supplier;

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

import reactor.core.CoreSubscriber;
import reactor.core.Scannable;
import reactor.test.publisher.FluxOperatorTest;
import reactor.test.subscriber.AssertSubscriber;
import reactor.util.concurrent.Queues;

import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.Mockito.when;

public class FluxWindowTest extends FluxOperatorTest<String, Flux<String>> {

	@Override
	protected Scenario<String, Flux<String>> defaultScenarioOptions(Scenario<String, Flux<String>> defaultOptions) {
		return defaultOptions.shouldAssertPostTerminateState(false);
	}

	@Override
	protected List<Scenario<String, Flux<String>>> scenarios_operatorSuccess() {
		return Arrays.asList(
				scenario(f -> f.window(1))
						.receive(s -> s.buffer().subscribe(b -> assertThat(b).containsExactly(item(0))),
								s -> s.buffer().subscribe(b -> assertThat(b).containsExactly(item(1))),
								s -> s.buffer().subscribe(b -> assertThat(b).containsExactly(item(2)))),

				scenario(f -> f.window(1, 2))
						.receive(s -> s.buffer().subscribe(b -> assertThat(b).containsExactly(item(0))),
								s -> s.buffer().subscribe(b -> assertThat(b).containsExactly(item(2)))),

				scenario(f -> f.window(2, 1))
						.receive(s -> s.buffer().subscribe(b -> assertThat(b).containsExactly(item(0), item(1))),
								s -> s.buffer().subscribe(b -> assertThat(b).containsExactly(item(1), item(2))),
								s -> s.buffer().subscribe(b -> assertThat(b).containsExactly(item(2))))
		);
	}

	@Override
	protected List<Scenario<String, Flux<String>>> scenarios_errorFromUpstreamFailure() {
		return Arrays.asList(
				scenario(f -> f.window(1)),

				scenario(f -> f.window(1, 2)),

				scenario(f -> f.window(2, 1))
		);
	}

	// javac can't handle these inline and fails with type inference error
	final Supplier<Queue<Integer>>                         pqs = ConcurrentLinkedQueue::new;
	final Supplier<Queue<FluxIdentityProcessor<Integer>>> oqs = ConcurrentLinkedQueue::new;

	@Test(expected = NullPointerException.class)
	public void source1Null() {
		new FluxWindow<>(null, 1, pqs);
	}

	@Test(expected = NullPointerException.class)
	public void source2Null() {
		new FluxWindow<>(null, 1, 2, pqs, oqs);
	}

	@Test(expected = NullPointerException.class)
	public void processorQueue1Null() {
		new FluxWindow<>(Flux.never(), 1, null);
	}

	@Test(expected = NullPointerException.class)
	public void processorQueue2Null() {
		new FluxWindow<>(Flux.never(), 1, 1, null, oqs);
	}

	@Test(expected = NullPointerException.class)
	public void overflowQueueNull() {
		new FluxWindow<>(Flux.never(), 1, 1, pqs, null);
	}

	@Test(expected = IllegalArgumentException.class)
	public void size1Invalid() {
		Flux.never()
		    .window(0);
	}

	@Test(expected = IllegalArgumentException.class)
	public void size2Invalid() {
		Flux.never()
		    .window(0, 2);
	}

	@Test(expected = IllegalArgumentException.class)
	public void skipInvalid() {
		Flux.never()
		    .window(1, 0);
	}

	static <T> AssertSubscriber<T> toList(Publisher<T> windows) {
		AssertSubscriber<T> ts = AssertSubscriber.create();
		windows.subscribe(ts);
		return ts;
	}

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

		Flux.range(1, 10)
		    .window(3)
		    .subscribe(ts);

		ts.assertValueCount(4)
		  .assertComplete()
		  .assertNoError();

		toList(ts.values()
		         .get(0)).assertValues(1, 2, 3)
		                 .assertComplete()
		                 .assertNoError();

		toList(ts.values()
		         .get(1)).assertValues(4, 5, 6)
		                 .assertComplete()
		                 .assertNoError();

		toList(ts.values()
		         .get(2)).assertValues(7, 8, 9)
		                 .assertComplete()
		                 .assertNoError();

		toList(ts.values()
		         .get(3)).assertValues(10)
		                 .assertComplete()
		                 .assertNoError();
	}

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

		Flux.range(1, 10)
		    .window(3)
		    .subscribe(ts);

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

		ts.request(1);

		ts.assertValueCount(1)
		  .assertNotComplete()
		  .assertNoError();

		toList(ts.values()
		         .get(0)).assertValues(1, 2, 3)
		                 .assertComplete()
		                 .assertNoError();

		ts.request(1);

		ts.assertValueCount(2)
		  .assertNotComplete()
		  .assertNoError();

		toList(ts.values()
		         .get(1)).assertValues(4, 5, 6)
		                 .assertComplete()
		                 .assertNoError();

		ts.request(1);

		ts.assertValueCount(3)
		  .assertNotComplete()
		  .assertNoError();

		toList(ts.values()
		         .get(2)).assertValues(7, 8, 9)
		                 .assertComplete()
		                 .assertNoError();

		ts.request(1);

		ts.assertValueCount(4)
		  .assertComplete()
		  .assertNoError();

		toList(ts.values()
		         .get(3)).assertValues(10)
		                 .assertComplete()
		                 .assertNoError();
	}

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

		Flux.range(1, 9).window(3).subscribe(ts);

		ts.assertValueCount(3)
		  .assertComplete()
		  .assertNoError();

		toList(ts.values()
		         .get(0)).assertValues(1, 2, 3)
		                 .assertComplete()
		                 .assertNoError();

		toList(ts.values()
		         .get(1)).assertValues(4, 5, 6)
		                 .assertComplete()
		                 .assertNoError();

		toList(ts.values()
		         .get(2)).assertValues(7, 8, 9)
		                 .assertComplete()
		                 .assertNoError();
	}

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

		Flux.range(1, 10)
		    .window(2, 3)
		    .subscribe(ts);

		ts.assertValueCount(4)
		  .assertComplete()
		  .assertNoError();

		toList(ts.values()
		         .get(0)).assertValues(1, 2)
		                 .assertComplete()
		                 .assertNoError();

		toList(ts.values()
		         .get(1)).assertValues(4, 5)
		                 .assertComplete()
		                 .assertNoError();

		toList(ts.values()
		         .get(2)).assertValues(7, 8)
		                 .assertComplete()
		                 .assertNoError();

		toList(ts.values()
		         .get(3)).assertValues(10)
		                 .assertComplete()
		                 .assertNoError();
	}

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

		Flux.range(1, 10)
		    .window(2, 3)
		    .subscribe(ts);

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

		ts.request(1);

		ts.assertValueCount(1)
		  .assertNotComplete()
		  .assertNoError();

		toList(ts.values()
		         .get(0)).assertValues(1, 2)
		                 .assertComplete()
		                 .assertNoError();

		ts.request(1);

		ts.assertValueCount(2)
		  .assertNotComplete()
		  .assertNoError();

		toList(ts.values()
		         .get(1)).assertValues(4, 5)
		                 .assertComplete()
		                 .assertNoError();

		ts.request(1);

		ts.assertValueCount(3)
		  .assertNotComplete()
		  .assertNoError();

		toList(ts.values()
		         .get(2)).assertValues(7, 8)
		                 .assertComplete()
		                 .assertNoError();

		ts.request(1);

		ts.assertValueCount(4)
		  .assertComplete()
		  .assertNoError();

		toList(ts.values()
		         .get(3)).assertValues(10)
		                 .assertComplete()
		                 .assertNoError();
	}

	@SafeVarargs
	static <T> void expect(AssertSubscriber<Publisher<T>> ts, int index, T... values) {
		toList(ts.values()
		         .get(index)).assertValues(values)
		                     .assertComplete()
		                     .assertNoError();
	}

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

		Flux.range(1, 10)
		    .window(3, 1)
		    .subscribe(ts);

		ts.assertValueCount(10)
		  .assertComplete()
		  .assertNoError();

		expect(ts, 0, 1, 2, 3);
		expect(ts, 1, 2, 3, 4);
		expect(ts, 2, 3, 4, 5);
		expect(ts, 3, 4, 5, 6);
		expect(ts, 4, 5, 6, 7);
		expect(ts, 5, 6, 7, 8);
		expect(ts, 6, 7, 8, 9);
		expect(ts, 7, 8, 9, 10);
		expect(ts, 8, 9, 10);
		expect(ts, 9, 10);
	}

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

		Flux.range(1, 10)
		    .window(3, 1)
		    .subscribe(ts);

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

		for (int i = 0; i < 10; i++) {
			ts.request(1);

			ts.assertValueCount(i + 1)
			  .assertNoError();

			if (i == 9) {
				ts.assertComplete();
			}
			else {
				ts.assertNotComplete();
			}

			switch (i) {
				case 9:
					expect(ts, 9, 10);
					break;
				case 8:
					expect(ts, 8, 9, 10);
					break;
				case 7:
					expect(ts, 7, 8, 9, 10);
					break;
				case 6:
					expect(ts, 6, 7, 8, 9);
					break;
				case 5:
					expect(ts, 5, 6, 7, 8);
					break;
				case 4:
					expect(ts, 4, 5, 6, 7);
					break;
				case 3:
					expect(ts, 3, 4, 5, 6);
					break;
				case 2:
					expect(ts, 2, 3, 4, 5);
					break;
				case 1:
					expect(ts, 1, 2, 3, 4);
					break;
				case 0:
					expect(ts, 0, 1, 2, 3);
					break;
			}
		}
	}

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

		FluxIdentityProcessor<Integer> sp = Processors.more().multicastNoBackpressure();

		sp.window(2, 2)
		  .subscribe(ts);

		ts.assertValueCount(0)
		  .assertNotComplete()
		  .assertNoError();

		sp.onNext(1);
		sp.onError(new RuntimeException("forced failure"));

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

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

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

		FluxIdentityProcessor<Integer> sp = Processors.more().multicastNoBackpressure();

		sp.window(2, 3)
		  .subscribe(ts);

		ts.assertValueCount(0)
		  .assertNotComplete()
		  .assertNoError();

		sp.onNext(1);
		sp.onError(new RuntimeException("forced failure"));

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

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

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

		FluxIdentityProcessor<Integer> sp = Processors.more().multicastNoBackpressure();

		sp.window(1, 3)
		  .subscribe(ts);

		ts.assertValueCount(0)
		  .assertNotComplete()
		  .assertNoError();

		sp.onNext(1);
		sp.onNext(2);
		sp.onError(new RuntimeException("forced failure"));

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

		expect(ts, 0, 1);
	}

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

		FluxIdentityProcessor<Integer> sp = Processors.more().multicastNoBackpressure();

		sp.window(2, 1)
		  .subscribe(ts);

		ts.assertValueCount(0)
		  .assertNotComplete()
		  .assertNoError();

		sp.onNext(1);
		sp.onError(new RuntimeException("forced failure"));

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

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

	@Test
	public void windowWillSubdivideAnInputFlux() {
		Flux<Integer> numbers = Flux.just(1, 2, 3, 4, 5, 6, 7, 8);

		//"non overlapping windows"
		List<List<Integer>> res = numbers.window(2, 3)
		                                 .concatMap(Flux::buffer)
		                                 .buffer()
		                                 .blockLast();

		assertThat(res).containsExactly(
				Arrays.asList(1, 2),
				Arrays.asList(4, 5),
				Arrays.asList(7, 8));
	}

	@Test
	public void windowWillSubdivideAnInputFluxOverlap() {
		Flux<Integer> numbers = Flux.just(1, 2, 3, 4, 5, 6, 7, 8);

		//"non overlapping windows"
		List<List<Integer>> res = numbers.window(3, 2)
		                                 .concatMap(Flux::buffer)
		                                 .buffer()
		                                 .blockLast();

		assertThat(res).containsExactly(
				Arrays.asList(1, 2, 3),
				Arrays.asList(3, 4, 5),
				Arrays.asList(5, 6, 7),
				Arrays.asList(7, 8));
	}

	@Test
	public void windowWillRerouteAsManyElementAsSpecified(){
		assertThat(Flux.just(1, 2, 3, 4, 5)
		               .window(2)
		               .concatMap(Flux::collectList)
		               .collectList()
		               .block()).containsExactly(
				Arrays.asList(1, 2),
				Arrays.asList(3, 4),
				Arrays.asList(5));
	}

	@Test
    public void scanExactSubscriber() {
        CoreSubscriber<Flux<Integer>> actual = new LambdaSubscriber<>(null, e -> {}, null, null);
        FluxWindow.WindowExactSubscriber<Integer> test = new FluxWindow.WindowExactSubscriber<Integer>(actual,
        		123, Queues.unbounded());
        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);
        Assertions.assertThat(test.scan(Scannable.Attr.CAPACITY)).isEqualTo(123);

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

        Assertions.assertThat(test.scan(Scannable.Attr.CANCELLED)).isFalse();
        test.cancel();
        Assertions.assertThat(test.scan(Scannable.Attr.CANCELLED)).isTrue();
    }

	@Test
    public void scanOverlapSubscriber() {
        CoreSubscriber<Flux<Integer>> actual = new LambdaSubscriber<>(null, e -> {}, null, null);
        FluxWindow.WindowOverlapSubscriber<Integer> test = new FluxWindow.WindowOverlapSubscriber<Integer>(actual,
        		123, 3, Queues.unbounded(), Queues.<FluxIdentityProcessor<Integer>>unbounded().get());
        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);
        Assertions.assertThat(test.scan(Scannable.Attr.CAPACITY)).isEqualTo(123);
        test.requested = 35;
        Assertions.assertThat(test.scan(Scannable.Attr.REQUESTED_FROM_DOWNSTREAM)).isEqualTo(35);
        test.onNext(2);
        Assertions.assertThat(test.scan(Scannable.Attr.BUFFERED)).isEqualTo(1);

        Assertions.assertThat(test.scan(Scannable.Attr.TERMINATED)).isFalse();
        Assertions.assertThat(test.scan(Scannable.Attr.ERROR)).isNull();
        test.onError(new IllegalStateException("boom"));
        Assertions.assertThat(test.scan(Scannable.Attr.ERROR)).hasMessage("boom");
        Assertions.assertThat(test.scan(Scannable.Attr.TERMINATED)).isTrue();

        Assertions.assertThat(test.scan(Scannable.Attr.CANCELLED)).isFalse();
        test.cancel();
        Assertions.assertThat(test.scan(Scannable.Attr.CANCELLED)).isTrue();
    }

    @Test
    public void scanOverlapSubscriberSmallBuffered() {
	    @SuppressWarnings("unchecked")
	    Queue<FluxIdentityProcessor<Integer>> mockQueue = Mockito.mock(Queue.class);

        CoreSubscriber<Flux<Integer>> actual = new LambdaSubscriber<>(null, e -> {}, null, null);
        FluxWindow.WindowOverlapSubscriber<Integer> test = new FluxWindow.WindowOverlapSubscriber<Integer>(actual,
                3,3, Queues.unbounded(), mockQueue);

        when(mockQueue.size()).thenReturn(Integer.MAX_VALUE - 2);
        //size() is 1
        test.offer(Processors.unicast());

        assertThat(test.scan(Scannable.Attr.BUFFERED)).isEqualTo(Integer.MAX_VALUE - 1);
        assertThat(test.scan(Scannable.Attr.LARGE_BUFFERED)).isEqualTo(Integer.MAX_VALUE - 1L);
    }

    @Test
    public void scanOverlapSubscriberLargeBuffered() {
	    @SuppressWarnings("unchecked")
	    Queue<FluxIdentityProcessor<Integer>> mockQueue = Mockito.mock(Queue.class);

        CoreSubscriber<Flux<Integer>> actual = new LambdaSubscriber<>(null, e -> {}, null, null);
        FluxWindow.WindowOverlapSubscriber<Integer> test = new FluxWindow.WindowOverlapSubscriber<Integer>(actual,
                3, 3, Queues.unbounded(), mockQueue);

        when(mockQueue.size()).thenReturn(Integer.MAX_VALUE);
        //size() is 5
        test.offer(Processors.unicast());
        test.offer(Processors.unicast());
        test.offer(Processors.unicast());
        test.offer(Processors.unicast());
        test.offer(Processors.unicast());

        assertThat(test.scan(Scannable.Attr.BUFFERED)).isEqualTo(Integer.MIN_VALUE);
        assertThat(test.scan(Scannable.Attr.LARGE_BUFFERED)).isEqualTo(Integer.MAX_VALUE + 5L);
    }

    @Test
    public void scanSkipSubscriber() {
        CoreSubscriber<Flux<Integer>> actual = new LambdaSubscriber<>(null, e -> {}, null, null);
        FluxWindow.WindowSkipSubscriber<Integer> test = new FluxWindow.WindowSkipSubscriber<Integer>(actual,
        		123, 3, Queues.unbounded());
        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);
        Assertions.assertThat(test.scan(Scannable.Attr.CAPACITY)).isEqualTo(123);

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

        Assertions.assertThat(test.scan(Scannable.Attr.CANCELLED)).isFalse();
        test.cancel();
        Assertions.assertThat(test.scan(Scannable.Attr.CANCELLED)).isTrue();
    }
}