import { compose } from 'redux';
import AbstractStoreService from './AbstractStoreService';
import Deferred from '../../Utils/Async/Deferred';

function StoreObserver() {
    const listeners = [];
    let store;
    let currentState;
    let unsubscribe;

    /**
     * Stores the current state, calls all the listeners which has a new state value
     * @private
     */
    function handleChange() {
        const nextState = store.getState();
        const prevState = currentState;
        currentState = nextState;

        for (let listener of listeners) {
            const pv = listener.select(prevState);
            const cv = listener.select(currentState);
            if (!(listener.isEqualFunction ? listener.isEqualFunction(pv, cv) : pv === cv)) {
                listener.onChange(cv, pv, listener.remove);
            }
        }
    }

    /**
     * Sets the store and subscribes the handleChange method
     * @param store
     */
    this.setStore = theStore => {
        store = theStore;

        // the subscribe function returns the call which can remove the listener, it is stored for future usage
        unsubscribe = store.subscribe(handleChange);

        // set the initial currentState
        currentState = store.getState();
    };

    /**
     * Adds a listener to the StoreObserver
     * @param select
     * @param onChange
     * @param isEqualFunction
     */
    this.addListener = (select, onChange, isEqualFunction) => {
        const listener = {
            select,
            onChange,
            isEqualFunction,
        };
        listener.remove = () => {
            const index = listeners.indexOf(listener);
            if (index > -1) {
                listeners.splice(index, 1);
            }
        };
        listeners.push(listener);
        return listener;
    };

    /**
     * Disconnects from the store, so the added listener will not be notified anymore
     */
    this.dispose = () => {
        if (unsubscribe) {
            unsubscribe();
            currentState = null;
            store = null;
            unsubscribe = null;
        }
        // should the listeners list be cleared?
    };
}

export default class StoreService extends AbstractStoreService {
    store = null;
    storeObserver = new StoreObserver();
    storeDeferred = new Deferred();

    setStore(store) {
        this.store = store;
        this.storeDeferred.resolve(store);
        this.storeObserver.setStore(store);
    }

    dispatch(...args) {
        if (!this.store) {
            throw new Error('StoreService: store must be set first! Use the setStore(store) function!');
        }
        return this.store.dispatch(...args);
    }

    get state() {
        if (!this.store) {
            throw new Error('StoreService: store must be set first! Use the setStore(store) function!');
        }
        return this.store.getState();
    }

    observeStore(select, onChange, isEqualFunction) {
        this.storeObserver.addListener(select, onChange, isEqualFunction);
    }

    async addMiddleware(middleware) {
        const store = await this.storeDeferred.promise;
        const middlewareAPI = {
            getState: store.getState,
            dispatch: action => store.dispatch(action),
        };
        store.dispatch = compose(middleware(middlewareAPI))(store.dispatch);
    }
}
