import Observable from "zen-observable";

if(!Observable.combineLatest) {
    /**
     * Combines an array of observable streams into one observable stream where the value is an array latest values of each individual stream
     *
     * @param observables Array of observable streams to be combined as one (
     * @returns {Observable<Array>}
     */
    let combineLatest = function(observables){
        let observablesState = observables.map((observable, index) => ({
            index: index,
            observable: observable,
            subscription: undefined,
            value: undefined,
            returned: false,
            completed: false,
        }));
        let observers = new Set();

        return new Observable(observer => {
            // Subscribe to child observables (if this is the first observer)
            if (observers.size === 0) {
                for (let observableState of observablesState) {
                    observableState.subscription = observableState.observable.subscribe({
                        next: (val) => {
                            observableState.value = val;
                            observableState.returned = true;
                            if (!observablesState.find(oS => oS.returned === false)) {
                                // If all observables returned a value
                                let values = observablesState.map(x => x.value);
                                for (let observer of observers) observer.next(values);
                            }
                        },
                        error: (err) => {
                            for (let observer of observers) observer.error(err);
                        },
                        complete: () => {
                            observableState.completed = true;
                            if (!observablesState.find(oS => oS.completed === false)) {
                                // If all observables completed, mark combined observable as completed
                                let values = observablesState.map(x => x.value);
                                for (let observer of observers) observer.complete(values);

                                for (let oS of observablesState) {
                                    if (oS.subscription) {
                                        oS.subscription.unsubscribe();
                                        oS.subscription = undefined;
                                    }
                                }
                            }
                        }
                    });
                }
            }
            // Track observer
            observers.add(observer);

            // Return unsubscribe callback
            return () => {
                observers.delete(observer);
                if (observers.size === 0) {
                    // If no one is watching, Unbind
                    for (let oS of observablesState) {
                        if (oS.subscription) {
                            oS.subscription.unsubscribe();
                            oS.subscription = undefined;
                        }
                    }
                }
            }
        })
    };

    Observable.combineLatest = combineLatest;
}else{
    console.warn("Observable already has an implementation of combineLatest! (this was not tested, yet!)")
}

if(!Observable.prototype.first){
    /**
     * Return first value of a stream as a promise
     *
     * @param filterFn Optional filter function that receives next value as a parameter (return true to return as first element)
     * @returns {Promise<any>}
     */
    Observable.prototype.first = function(filterFn){
        return new Promise((resolve,reject)=>{
            let subscription = this.subscribe({
                next(value){
                    if(!filterFn || filterFn(value)){
                        subscription.unsubscribe();
                        resolve(value);
                    }
                },
                error(err){
                    reject(err);
                },
                complete(){
                    resolve(undefined);
                }
            })
        })
    }
}else{
    console.warn("Observable.prototype already has an implementation of first! (this was not tested, yet!)")
}

// Note/TODO: Append a slice-method to observable? functions replaces skip/take in RXjs and functions similar to slice in arrays? (note, don't know the length of observable stream so might not be possible to implement slice's interpretation of negative numbers)
//if(window && !window.Observable) window.Observable = Observable;
export {Observable};