/**
 * Created by ndyumin on 09.09.2016.
 */

const symbolObservable = require('symbol-observable');

function extract(arr, obj) {
    return arr.filter(item => Object.keys(obj).every(key => item[key] === obj[key]));
}

function removeItem(arr, item) {
    const result = arr.slice(0);
    const index = result.indexOf(item);
    result.splice(index, 1);
    return result;
}

function notifyAll(observers, state) {
    observers.forEach(o => o.next(state));
}

function fromCallbacks(next, error, complete) {
    return {
        next,
        error,
        complete
    };
}

function data(initial) {
    let state = initial;
    let subscriptions = [];
    let observers = [];

    function _unsubscribeAndRemove(observable, reducer) {
        const toBeRemoved = extract(subscriptions, {observable, reducer});
        toBeRemoved.forEach(subscription => subscription.unsubscribe());
        subscriptions = toBeRemoved.reduce(removeItem, subscriptions);
    }

    return {
        [symbolObservable]: function() {
            return this;
        },

        reduce (observable, reducer) {
            const subscription = observable.subscribe({
                next(v) {
                    state = reducer(state, v);
                    notifyAll(observers, state);
                },
                error(e) {

                },
                complete() {
                    _unsubscribeAndRemove(observable, reducer);
                }
            });

            subscriptions.push({
                observable,
                reducer,
                subscription
            });

            return this;
        },

        remove(observable, reducer) {
            _unsubscribeAndRemove(observable, reducer);
        },

        subscribe(observer) {
            if (typeof observer === 'function') {
                observer = fromCallbacks(...arguments);
            }

            observers.push(observer);
            observer.next(state);
            return {
                unsubscribe() {
                    observers = removeItem(observers, observer);
                }
            };
        }
    };
}

function actions(initial) {
    const events = [];
    let subscribers = [];

    function project() {
        return events.reduce(function(state, event) {
            return event.reduce(state, event.update);
        }, initial);
    }

    return {
        subscribe(observer) {
            if (typeof observer === 'function') {
                observer = fromCallbacks(...arguments);
            }
            subscribers.push(observer);
            return {
                unsubscribe() {
                    subscribers = removeItem(subscribers, observer);
                }
            };
        },
        dispatch(event) {
            events.push(event);
            notifyAll(subscribers, project())
        },
        createEvent(reduce) {
            return update =>
                this.dispatch({
                    reduce,
                    update
                });
        }
    };
}

actions.createEvent = function(reduce) {
    return function(update) {
        return {
            reduce,
            update
        };
    }
};

module.exports = {
    data,
    actions
};
@@ -2211,30 +2211,75 @@
 %5B%5D;%0A
-%0A    function project(
+    let snapshots = %5B%5D;%0A%0A    function projectFrom(initial, sequence
 ) %7B%0A
@@ -2293,22 +2293,24 @@
  return 
-events
+sequence
 .reduce(
@@ -2416,32 +2416,430 @@
 %0A    %7D%0A%0A    
-return %7B
+function project() %7B%0A        const latestSnapshot = snapshots%5Bsnapshots.length - 1%5D;%0A        if (events.length === snapshots.length) %7B%0A            return latestSnapshot;%0A        %7D else %7B%0A            const sequence = events.slice(snapshots.length);%0A            return snapshots%5Bevents.length - 1%5D = projectFrom(latestSnapshot %7C%7C initial, sequence);%0A        %7D%0A    %7D%0A%0A    return %7B%0A        _project: project%60%60,
 %0A        sub

add snapshots