/**
 * Copyright 2016 Netflix, Inc.
 *
 * 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 io.reactivex.internal.operators.flowable;

import org.junit.*;
import org.reactivestreams.*;

import io.reactivex.*;
import io.reactivex.exceptions.*;
import io.reactivex.functions.Cancellable;
import io.reactivex.processors.PublishProcessor;
import io.reactivex.subscribers.*;

public class FlowableFromSourceTest {

    PublishAsyncEmitter source;

    PublishAsyncEmitterNoCancel sourceNoCancel;

    TestSubscriber<Integer> ts;

    @Before
    public void before() {
        source = new PublishAsyncEmitter();
        sourceNoCancel = new PublishAsyncEmitterNoCancel();
        ts = new TestSubscriber<Integer>(0L);
    }


    @Test
    public void normalBuffered() {
        Flowable.create(source, BackpressureStrategy.BUFFER).subscribe(ts);

        source.onNext(1);
        source.onNext(2);
        source.onComplete();

        ts.request(1);

        ts.assertValue(1);

        Assert.assertEquals(0, source.requested());

        ts.request(1);

        ts.assertValues(1, 2);
        ts.assertNoErrors();
        ts.assertComplete();
    }

    @Test
    public void normalDrop() {
        Flowable.create(source, BackpressureStrategy.DROP).subscribe(ts);

        source.onNext(1);

        ts.request(1);

        ts.assertNoValues();

        source.onNext(2);
        source.onComplete();

        ts.assertValues(2);
        ts.assertNoErrors();
        ts.assertComplete();
    }

    @Test
    public void normalLatest() {
        Flowable.create(source, BackpressureStrategy.LATEST).subscribe(ts);

        source.onNext(1);

        source.onNext(2);
        source.onComplete();

        ts.assertNoValues();

        ts.request(1);

        ts.assertValues(2);
        ts.assertNoErrors();
        ts.assertComplete();
    }

    @Test
    public void normalMissing() {
        Flowable.create(source, BackpressureStrategy.MISSING).subscribe(ts);

        source.onNext(1);
        source.onNext(2);
        source.onComplete();

        ts.assertValues(1, 2);
        ts.assertNoErrors();
        ts.assertComplete();
    }

    @Test
    public void normalMissingRequested() {
        Flowable.create(source, BackpressureStrategy.MISSING).subscribe(ts);
        ts.request(2);

        source.onNext(1);
        source.onNext(2);
        source.onComplete();

        ts.assertValues(1, 2);
        ts.assertNoErrors();
        ts.assertComplete();
    }


    @Test
    public void normalError() {
        Flowable.create(source, BackpressureStrategy.ERROR).subscribe(ts);

        source.onNext(1);
        source.onNext(2);
        source.onComplete();

        ts.assertNoValues();
        ts.assertError(MissingBackpressureException.class);
        ts.assertNotComplete();

        Assert.assertEquals("create: could not emit value due to lack of requests", ts.errors().get(0).getMessage());
    }

    @Test
    public void errorBuffered() {
        Flowable.create(source, BackpressureStrategy.BUFFER).subscribe(ts);

        source.onNext(1);
        source.onNext(2);
        source.onError(new TestException());

        ts.request(1);

        ts.assertValue(1);

        ts.request(1);

        ts.assertValues(1, 2);
        ts.assertError(TestException.class);
        ts.assertNotComplete();
    }

    @Test
    public void errorLatest() {
        Flowable.create(source, BackpressureStrategy.LATEST).subscribe(ts);

        source.onNext(1);
        source.onNext(2);
        source.onError(new TestException());

        ts.assertNoValues();

        ts.request(1);

        ts.assertValues(2);
        ts.assertError(TestException.class);
        ts.assertNotComplete();
    }

    @Test
    public void errorMissing() {
        Flowable.create(source, BackpressureStrategy.MISSING).subscribe(ts);

        source.onNext(1);
        source.onNext(2);
        source.onError(new TestException());

        ts.request(1);

        ts.assertValues(1, 2);
        ts.assertError(TestException.class);
        ts.assertNotComplete();
    }

    @Test
    public void unsubscribedBuffer() {
        Flowable.create(source, BackpressureStrategy.BUFFER).subscribe(ts);
        ts.cancel();

        source.onNext(1);
        source.onNext(2);
        source.onError(new TestException());

        ts.request(1);

        ts.assertNoValues();
        ts.assertNoErrors();
        ts.assertNotComplete();
    }

    @Test
    public void unsubscribedLatest() {
        Flowable.create(source, BackpressureStrategy.LATEST).subscribe(ts);
        ts.cancel();

        source.onNext(1);
        source.onNext(2);
        source.onError(new TestException());

        ts.request(1);

        ts.assertNoValues();
        ts.assertNoErrors();
        ts.assertNotComplete();
    }

    @Test
    public void unsubscribedError() {
        Flowable.create(source, BackpressureStrategy.ERROR).subscribe(ts);
        ts.cancel();

        source.onNext(1);
        source.onNext(2);
        source.onError(new TestException());

        ts.request(1);

        ts.assertNoValues();
        ts.assertNoErrors();
        ts.assertNotComplete();
    }

    @Test
    public void unsubscribedDrop() {
        Flowable.create(source, BackpressureStrategy.DROP).subscribe(ts);
        ts.cancel();

        source.onNext(1);
        source.onNext(2);
        source.onError(new TestException());

        ts.request(1);

        ts.assertNoValues();
        ts.assertNoErrors();
        ts.assertNotComplete();
    }

    @Test
    public void unsubscribedMissing() {
        Flowable.create(source, BackpressureStrategy.MISSING).subscribe(ts);
        ts.cancel();

        source.onNext(1);
        source.onNext(2);
        source.onError(new TestException());

        ts.request(1);

        ts.assertNoValues();
        ts.assertNoErrors();
        ts.assertNotComplete();
    }

    @Test
    public void unsubscribedNoCancelBuffer() {
        Flowable.create(sourceNoCancel, BackpressureStrategy.BUFFER).subscribe(ts);
        ts.cancel();

        sourceNoCancel.onNext(1);
        sourceNoCancel.onNext(2);
        sourceNoCancel.onError(new TestException());

        ts.request(1);

        ts.assertNoValues();
        ts.assertNoErrors();
        ts.assertNotComplete();
    }

    @Test
    public void unsubscribedNoCancelLatest() {
        Flowable.create(sourceNoCancel, BackpressureStrategy.LATEST).subscribe(ts);
        ts.cancel();

        sourceNoCancel.onNext(1);
        sourceNoCancel.onNext(2);
        sourceNoCancel.onError(new TestException());

        ts.request(1);

        ts.assertNoValues();
        ts.assertNoErrors();
        ts.assertNotComplete();
    }

    @Test
    public void unsubscribedNoCancelError() {
        Flowable.create(sourceNoCancel, BackpressureStrategy.ERROR).subscribe(ts);
        ts.cancel();

        sourceNoCancel.onNext(1);
        sourceNoCancel.onNext(2);
        sourceNoCancel.onError(new TestException());

        ts.request(1);

        ts.assertNoValues();
        ts.assertNoErrors();
        ts.assertNotComplete();
    }

    @Test
    public void unsubscribedNoCancelDrop() {
        Flowable.create(sourceNoCancel, BackpressureStrategy.DROP).subscribe(ts);
        ts.cancel();

        sourceNoCancel.onNext(1);
        sourceNoCancel.onNext(2);
        sourceNoCancel.onError(new TestException());

        ts.request(1);

        ts.assertNoValues();
        ts.assertNoErrors();
        ts.assertNotComplete();
    }

    @Test
    public void unsubscribedNoCancelMissing() {
        Flowable.create(sourceNoCancel, BackpressureStrategy.MISSING).subscribe(ts);
        ts.cancel();

        sourceNoCancel.onNext(1);
        sourceNoCancel.onNext(2);
        sourceNoCancel.onError(new TestException());

        ts.request(1);

        ts.assertNoValues();
        ts.assertNoErrors();
        ts.assertNotComplete();
    }

    @Test
    public void deferredRequest() {
        Flowable.create(source, BackpressureStrategy.BUFFER).subscribe(ts);

        source.onNext(1);
        source.onNext(2);
        source.onComplete();

        ts.request(2);

        ts.assertValues(1, 2);
        ts.assertNoErrors();
        ts.assertComplete();
    }

    @Test
    public void take() {
        Flowable.create(source, BackpressureStrategy.BUFFER).take(2).subscribe(ts);

        source.onNext(1);
        source.onNext(2);
        source.onComplete();

        ts.request(2);

        ts.assertValues(1, 2);
        ts.assertNoErrors();
        ts.assertComplete();
    }

    @Test
    public void takeOne() {
        Flowable.create(source, BackpressureStrategy.BUFFER).take(1).subscribe(ts);
        ts.request(2);

        source.onNext(1);
        source.onNext(2);
        source.onComplete();

        ts.assertValues(1);
        ts.assertNoErrors();
        ts.assertComplete();
    }

    @Test
    public void requestExact() {
        Flowable.create(source, BackpressureStrategy.BUFFER).subscribe(ts);
        ts.request(2);

        source.onNext(1);
        source.onNext(2);
        source.onComplete();

        ts.assertValues(1, 2);
        ts.assertNoErrors();
        ts.assertComplete();
    }

    @Test
    public void takeNoCancel() {
        Flowable.create(sourceNoCancel, BackpressureStrategy.BUFFER).take(2).subscribe(ts);

        sourceNoCancel.onNext(1);
        sourceNoCancel.onNext(2);
        sourceNoCancel.onComplete();

        ts.request(2);

        ts.assertValues(1, 2);
        ts.assertNoErrors();
        ts.assertComplete();
    }

    @Test
    public void takeOneNoCancel() {
        Flowable.create(sourceNoCancel, BackpressureStrategy.BUFFER).take(1).subscribe(ts);
        ts.request(2);

        sourceNoCancel.onNext(1);
        sourceNoCancel.onNext(2);
        sourceNoCancel.onComplete();

        ts.assertValues(1);
        ts.assertNoErrors();
        ts.assertComplete();
    }

    @Test
    public void unsubscribeNoCancel() {
        Flowable.create(sourceNoCancel, BackpressureStrategy.BUFFER).subscribe(ts);
        ts.request(2);

        sourceNoCancel.onNext(1);

        ts.cancel();

        sourceNoCancel.onNext(2);

        ts.assertValues(1);
        ts.assertNoErrors();
        ts.assertNotComplete();
    }


    @Test
    public void unsubscribeInline() {
        TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>() {
            @Override
            public void onNext(Integer t) {
                super.onNext(t);
                cancel();
            }
        };

        Flowable.create(sourceNoCancel, BackpressureStrategy.BUFFER).subscribe(ts1);

        sourceNoCancel.onNext(1);

        ts1.assertValues(1);
        ts1.assertNoErrors();
        ts1.assertNotComplete();
    }

    @Test
    public void completeInline() {
        Flowable.create(sourceNoCancel, BackpressureStrategy.BUFFER).subscribe(ts);

        sourceNoCancel.onNext(1);
        sourceNoCancel.onComplete();

        ts.request(2);

        ts.assertValues(1);
        ts.assertNoErrors();
        ts.assertComplete();
    }

    @Test
    public void errorInline() {
        Flowable.create(sourceNoCancel, BackpressureStrategy.BUFFER).subscribe(ts);

        sourceNoCancel.onNext(1);
        sourceNoCancel.onError(new TestException());

        ts.request(2);

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

    @Test
    public void requestInline() {
        TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>(1L) {
            @Override
            public void onNext(Integer t) {
                super.onNext(t);
                request(1);
            }
        };

        Flowable.create(sourceNoCancel, BackpressureStrategy.BUFFER).subscribe(ts1);

        sourceNoCancel.onNext(1);
        sourceNoCancel.onNext(2);

        ts1.assertValues(1, 2);
        ts1.assertNoErrors();
        ts1.assertNotComplete();
    }

    @Test
    public void unsubscribeInlineLatest() {
        TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>() {
            @Override
            public void onNext(Integer t) {
                super.onNext(t);
                cancel();
            }
        };

        Flowable.create(sourceNoCancel, BackpressureStrategy.LATEST).subscribe(ts1);

        sourceNoCancel.onNext(1);

        ts1.assertValues(1);
        ts1.assertNoErrors();
        ts1.assertNotComplete();
    }

    @Test
    public void unsubscribeInlineExactLatest() {
        TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>(1L) {
            @Override
            public void onNext(Integer t) {
                super.onNext(t);
                cancel();
            }
        };

        Flowable.create(sourceNoCancel, BackpressureStrategy.LATEST).subscribe(ts1);

        sourceNoCancel.onNext(1);

        ts1.assertValues(1);
        ts1.assertNoErrors();
        ts1.assertNotComplete();
    }

    @Test
    public void completeInlineLatest() {
        Flowable.create(sourceNoCancel, BackpressureStrategy.LATEST).subscribe(ts);

        sourceNoCancel.onNext(1);
        sourceNoCancel.onComplete();

        ts.request(2);

        ts.assertValues(1);
        ts.assertNoErrors();
        ts.assertComplete();
    }

    @Test
    public void completeInlineExactLatest() {
        Flowable.create(sourceNoCancel, BackpressureStrategy.LATEST).subscribe(ts);

        sourceNoCancel.onNext(1);
        sourceNoCancel.onComplete();

        ts.request(1);

        ts.assertValues(1);
        ts.assertNoErrors();
        ts.assertComplete();
    }

    @Test
    public void errorInlineLatest() {
        Flowable.create(sourceNoCancel, BackpressureStrategy.LATEST).subscribe(ts);

        sourceNoCancel.onNext(1);
        sourceNoCancel.onError(new TestException());

        ts.request(2);

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

    @Test
    public void requestInlineLatest() {
        TestSubscriber<Integer> ts1 = new TestSubscriber<Integer>(1L) {
            @Override
            public void onNext(Integer t) {
                super.onNext(t);
                request(1);
            }
        };

        Flowable.create(sourceNoCancel, BackpressureStrategy.LATEST).subscribe(ts1);

        sourceNoCancel.onNext(1);
        sourceNoCancel.onNext(2);

        ts1.assertValues(1, 2);
        ts1.assertNoErrors();
        ts1.assertNotComplete();
    }

    static final class PublishAsyncEmitter implements FlowableOnSubscribe<Integer>, Subscriber<Integer> {

        final PublishProcessor<Integer> subject;

        FlowableEmitter<Integer> current;

        PublishAsyncEmitter() {
            this.subject = PublishProcessor.create();
        }

        long requested() {
            return current.requested();
        }

        @Override
        public void subscribe(final FlowableEmitter<Integer> t) {

            this.current = t;

            final ResourceSubscriber<Integer> as = new ResourceSubscriber<Integer>() {

                @Override
                public void onComplete() {
                    t.onComplete();
                }

                @Override
                public void onError(Throwable e) {
                    t.onError(e);
                }

                @Override
                public void onNext(Integer v) {
                    t.onNext(v);
                }

            };

            subject.subscribe(as);

            t.setCancellable(new Cancellable() {
                @Override
                public void cancel() throws Exception {
                    as.dispose();
                }
            });;
        }

        @Override
        public void onSubscribe(Subscription s) {
            s.request(Long.MAX_VALUE);
        }

        @Override
        public void onNext(Integer t) {
            subject.onNext(t);
        }

        @Override
        public void onError(Throwable e) {
            subject.onError(e);
        }

        @Override
        public void onComplete() {
            subject.onComplete();
        }
    }

    static final class PublishAsyncEmitterNoCancel implements FlowableOnSubscribe<Integer>, Subscriber<Integer> {

        final PublishProcessor<Integer> subject;

        PublishAsyncEmitterNoCancel() {
            this.subject = PublishProcessor.create();
        }

        @Override
        public void subscribe(final FlowableEmitter<Integer> t) {

            subject.subscribe(new Subscriber<Integer>() {

                @Override
                public void onSubscribe(Subscription s) {
                    s.request(Long.MAX_VALUE);
                }

                @Override
                public void onComplete() {
                    t.onComplete();
                }

                @Override
                public void onError(Throwable e) {
                    t.onError(e);
                }

                @Override
                public void onNext(Integer v) {
                    t.onNext(v);
                }

            });
        }

        @Override
        public void onSubscribe(Subscription s) {
            s.request(Long.MAX_VALUE);
        }

        @Override
        public void onNext(Integer t) {
            subject.onNext(t);
        }

        @Override
        public void onError(Throwable e) {
            subject.onError(e);
        }

        @Override
        public void onComplete() {
            subject.onComplete();
        }
    }

}