import React from 'react';

const pusherStore = {
    stateConsumer: null,
    pushing: {},
    pushingIds: [],
    qty: {
        all: 0,
        doing: 0,
        success: 0,
        failed: 0,
    }
};

/**
 * @param key
 * @return {boolean} true when key wasn't existing till now
 */
const addPushingId = (key) => {
    if(-1 === pusherStore.pushingIds.indexOf(key)) {
        // key isn't existing till now
        pusherStore.pushingIds.push(key);
        return true;
    }

    return false;
};

/**
 * Create an unique 8char id
 * @return {string}
 */
const createPushingId = () => {
    let key = Math.floor((1 + Math.random()) * 0x1000000).toString(16).substring(1);
    if(addPushingId(key)) {
        // when the key was an unique return it
        return key;
    }

    // or create new ones until unique
    return createPushingId();
};

class Pusher {
    static provideStateConsumer(evt) {
        pusherStore.stateConsumer = evt;
        Pusher.updateState();
    }

    static unbindStateConsumer() {
        pusherStore.stateConsumer = null;
    }

    static updateState() {
        if(pusherStore.stateConsumer) {
            pusherStore.stateConsumer({...pusherStore.qty}, {...pusherStore.pushing});
        } else {
            console.error('Pusher: no stateConsumer provided');
        }
    }

    static addPushing(payload) {
        const id = pusherStore.qty.all + '#' + createPushingId();
        pusherStore.pushing[id] = payload;
        pusherStore.pushing[id].state = 'init';
        pusherStore.pushing[id].time = new Date();
        pusherStore.qty.all++;
        Pusher.updateState();
        return id;
    }

    static cancelPushing(id) {
        if(pusherStore.pushing[id]) {
            pusherStore.pushing[id].cancelled = true;
        }
        Pusher.updateState();
    }

    static execPushing(id) {
        let handle = (res, rej) => {
            rej()
        };
        if(pusherStore.pushing[id] && pusherStore.pushing[id].push) {
            handle = pusherStore.pushing[id].push;
        }

        pusherStore.pushing[id].state = 'begin';
        pusherStore.qty.doing++;
        Pusher.updateState();
        return (new Promise((resolve, reject) => {

            const res = (data) => {
                pusherStore.pushing[id].state = 'success';
                pusherStore.qty.success++;
                pusherStore.qty.doing--;
                Pusher.updateState();
                if(pusherStore.pushing[id].cancelled) {
                    reject({cancelled: true});
                    return;
                }
                resolve(data);
            };

            const rej = (data) => {
                pusherStore.pushing[id].state = 'failed';
                pusherStore.qty.failed++;
                pusherStore.qty.doing--;
                Pusher.updateState();
                if(pusherStore.pushing[id].cancelled) {
                    reject({cancelled: true});
                    return;
                }
                reject(data);
            };

            handle(res, rej);
        }));
    }
}

const withPusher = (WrappedComponent) => {
    return class PusherHOC extends React.Component {
        pushing = [];

        componentWillUnmount() {
            this.pushing.forEach(elem => {
                Pusher.cancelPushing(elem);
            });
            this.pushing = [];
        }

        addPushing = (payload) => {
            let id = Pusher.addPushing(payload);
            this.pushing.push(id);
            return Pusher.execPushing(id);
        };

        render() {
            return (
                <WrappedComponent {...this.props} addPushing={this.addPushing}/>
            );
        }
    };
};

export {Pusher, withPusher};