/*
 * Copyright 2016-present David Karnok
 *
 * 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 hu.akarnokd.rxjava3.joins;

import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.functions.Function;
import io.reactivex.rxjava3.plugins.RxJavaPlugins;

/**
 * Represents an observable that supports join operations.
 *
 * @param <T> the value type joined
 */
public final class JoinObservable<T> {

    private final Observable<T> o;

    private JoinObservable(Observable<T> o) {
        this.o = o;
    }

    /**
     * Creates a JoinObservable from a regular Observable.
     * @param <T> the value type
     * @param o the observable to wrap
     * @return the created JoinObservable instance
     */
    public static <T> JoinObservable<T> from(Observable<T> o) {
        return new JoinObservable<>(RxJavaPlugins.onAssembly(o));
    }

    /**
     * Returns a Pattern that matches when both Observables emit an item.
     * <p>
     * <img width="640" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/and_then_when.png" alt="">
     *
     * @param <T2> the value type of the right Observable
     * @param right
     *            an Observable to match with the source Observable
     * @return a Pattern object that matches when both Observables emit an item
     * @throws NullPointerException
     *             if {@code right} is null
     * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#wiki-and-then-and-when">RxJava Wiki: and()</a>
     * @see <a href="http://msdn.microsoft.com/en-us/library/hh229153.aspx">MSDN: Observable.And</a>
     */
    public <T2> Pattern2<T, T2> and(Observable<T2> right) {
        return JoinPatterns.and(o, right);
    }

    /**
     * Joins together the results from several patterns via their plans.
     * <p>
     * <img width="640" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/and_then_when.png" alt="">
     *
     * @param <R> the result type
     * @param plans
     *            a series of plans created by use of the {@link #then} Observer on patterns
     * @return an Observable that emits the results from matching several patterns
     * @throws NullPointerException
     *             if {@code plans} is null
     * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#wiki-and-then-and-when">RxJava Wiki: when()</a>
     * @see <a href="http://msdn.microsoft.com/en-us/library/hh229558.aspx">MSDN: Observable.When</a>
     */
    public static <R> JoinObservable<R> when(Iterable<? extends Plan<R>> plans) {
        if (plans == null) {
            throw new NullPointerException("plans");
        }
        return from(JoinPatterns.when(plans));
    }

    /**
     * Joins together the results from several patterns via their plans.
     * <p>
     * <img width="640" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/and_then_when.png" alt="">
     *
     * @param <R> the result type
     * @param plans
     *            a series of plans created by use of the {@link #then} Observer on patterns
     * @return an Observable that emits the results from matching several patterns
     * @throws NullPointerException
     *             if {@code plans} is null
     * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#wiki-and-then-and-when">RxJava Wiki: when()</a>
     * @see <a href="http://msdn.microsoft.com/en-us/library/hh229889.aspx">MSDN: Observable.When</a>
     */
    @SafeVarargs
    public static <R> JoinObservable<R> when(Plan<R>... plans) {
        return from(JoinPatterns.when(plans));
    }

    /**
     * Joins the results from a pattern via its plan.
     * <p>
     * <img width="640" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/and_then_when.png" alt="">
     *
     * @param <R> the result type
     * @param p1
     *            the plan to join, created by use of the {@link #then} Observer on a pattern
     * @return an Observable that emits the results from matching a pattern
     * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#wiki-and-then-and-when">RxJava Wiki: when()</a>
     * @see <a href="http://msdn.microsoft.com/en-us/library/hh229889.aspx">MSDN: Observable.When</a>
     */
    public static <R> JoinObservable<R> when(Plan<R> p1) {
        return from(JoinPatterns.when(p1));
    }

    /**
     * Joins together the results from two patterns via their plans.
     * <p>
     * <img width="640" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/and_then_when.png" alt="">
     *
     * @param <R> the result type
     * @param p1
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p2
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @return an Observable that emits the results from matching two patterns
     * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#wiki-and-then-and-when">RxJava Wiki: when()</a>
     * @see <a href="http://msdn.microsoft.com/en-us/library/hh229889.aspx">MSDN: Observable.When</a>
     */
    public static <R> JoinObservable<R> when(Plan<R> p1, Plan<R> p2) {
        return from(JoinPatterns.when(p1, p2));
    }

    /**
     * Joins together the results from three patterns via their plans.
     * <p>
     * <img width="640" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/and_then_when.png" alt="">
     *
     * @param <R> the result type
     * @param p1
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p2
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p3
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @return an Observable that emits the results from matching three patterns
     * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#wiki-and-then-and-when">RxJava Wiki: when()</a>
     * @see <a href="http://msdn.microsoft.com/en-us/library/hh229889.aspx">MSDN: Observable.When</a>
     */
    public static <R> JoinObservable<R> when(Plan<R> p1, Plan<R> p2, Plan<R> p3) {
        return from(JoinPatterns.when(p1, p2, p3));
    }

    /**
     * Joins together the results from four patterns via their plans.
     * <p>
     * <img width="640" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/and_then_when.png" alt="">
     *
     * @param <R> the result type
     * @param p1
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p2
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p3
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p4
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @return an Observable that emits the results from matching four patterns
     * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#wiki-and-then-and-when">RxJava Wiki: when()</a>
     * @see <a href="http://msdn.microsoft.com/en-us/library/hh229889.aspx">MSDN: Observable.When</a>
     */
    public static <R> JoinObservable<R> when(Plan<R> p1, Plan<R> p2, Plan<R> p3, Plan<R> p4) {
        return from(JoinPatterns.when(p1, p2, p3, p4));
    }

    /**
     * Joins together the results from five patterns via their plans.
     * <p>
     * <img width="640" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/and_then_when.png" alt="">
     *
     * @param <R> the result type
     * @param p1
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p2
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p3
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p4
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p5
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @return an Observable that emits the results from matching five patterns
     * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#wiki-and-then-and-when">RxJava Wiki: when()</a>
     * @see <a href="http://msdn.microsoft.com/en-us/library/hh229889.aspx">MSDN: Observable.When</a>
     */
    public static <R> JoinObservable<R> when(Plan<R> p1, Plan<R> p2, Plan<R> p3, Plan<R> p4, Plan<R> p5) {
        return from(JoinPatterns.when(p1, p2, p3, p4, p5));
    }

    /**
     * Joins together the results from six patterns via their plans.
     * <p>
     * <img width="640" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/and_then_when.png" alt="">
     *
     * @param <R> the result type
     * @param p1
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p2
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p3
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p4
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p5
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p6
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @return an Observable that emits the results from matching six patterns
     * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#wiki-and-then-and-when">RxJava Wiki: when()</a>
     * @see <a href="http://msdn.microsoft.com/en-us/library/hh229889.aspx">MSDN: Observable.When</a>
     */
    public static <R> JoinObservable<R> when(Plan<R> p1, Plan<R> p2, Plan<R> p3, Plan<R> p4, Plan<R> p5, Plan<R> p6) {
        return from(JoinPatterns.when(p1, p2, p3, p4, p5, p6));
    }

    /**
     * Joins together the results from seven patterns via their plans.
     * <p>
     * <img width="640" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/and_then_when.png" alt="">
     *
     * @param <R> the result type
     * @param p1
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p2
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p3
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p4
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p5
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p6
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p7
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @return an Observable that emits the results from matching seven patterns
     * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#wiki-and-then-and-when">RxJava Wiki: when()</a>
     * @see <a href="http://msdn.microsoft.com/en-us/library/hh229889.aspx">MSDN: Observable.When</a>
     */
    public static <R> JoinObservable<R> when(Plan<R> p1, Plan<R> p2, Plan<R> p3, Plan<R> p4, Plan<R> p5, Plan<R> p6, Plan<R> p7) {
        return from(JoinPatterns.when(p1, p2, p3, p4, p5, p6, p7));
    }

    /**
     * Joins together the results from eight patterns via their plans.
     * <p>
     * <img width="640" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/and_then_when.png" alt="">
     *
     * @param <R> the result type
     * @param p1
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p2
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p3
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p4
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p5
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p6
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p7
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p8
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @return an Observable that emits the results from matching eight patterns
     * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#wiki-and-then-and-when">RxJava Wiki: when()</a>
     * @see <a href="http://msdn.microsoft.com/en-us/library/hh229889.aspx">MSDN: Observable.When</a>
     */
    public static <R> JoinObservable<R> when(Plan<R> p1, Plan<R> p2, Plan<R> p3, Plan<R> p4, Plan<R> p5, Plan<R> p6, Plan<R> p7, Plan<R> p8) {
        return from(JoinPatterns.when(p1, p2, p3, p4, p5, p6, p7, p8));
    }

    /**
     * Joins together the results from nine patterns via their plans.
     * <p>
     * <img width="640" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/and_then_when.png" alt="">
     *
     * @param <R> the result type
     * @param p1
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p2
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p3
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p4
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p5
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p6
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p7
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p8
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @param p9
     *            a plan, created by use of the {@link #then} Observer on a pattern
     * @return an Observable that emits the results from matching nine patterns
     * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#wiki-and-then-and-when">RxJava Wiki: when()</a>
     * @see <a href="http://msdn.microsoft.com/en-us/library/hh229889.aspx">MSDN: Observable.When</a>
     */
    public static <R> JoinObservable<R> when(Plan<R> p1, Plan<R> p2, Plan<R> p3, Plan<R> p4, Plan<R> p5, Plan<R> p6, Plan<R> p7, Plan<R> p8, Plan<R> p9) {
        return from(JoinPatterns.when(p1, p2, p3, p4, p5, p6, p7, p8, p9));
    }

    /**
     * Matches when the Observable has an available item and projects the item by invoking the selector
     * function.
     * <p>
     * <img width="640" src="https://raw.github.com/wiki/ReactiveX/RxJava/images/rx-operators/and_then_when.png" alt="">
     *
     * @param <R> the result type
     * @param selector
     *            selector that will be invoked for items emitted by the source Observable
     * @return a {@link Plan} that produces the projected results, to be fed (with other Plans) to the {@link #when} Observer
     * @throws NullPointerException
     *             if {@code selector} is null
     * @see <a href="https://github.com/ReactiveX/RxJava/wiki/Combining-Observables#wiki-and-then-and-when">RxJava Wiki: then()</a>
     * @see <a href="http://msdn.microsoft.com/en-us/library/hh211662.aspx">MSDN: Observable.Then</a>
     */
    public <R> Plan<R> then(Function<? super T, ? extends R> selector) {
        return JoinPatterns.then(o, selector);
    }

    public Observable<T> toObservable() {
        return o;
    }
}