(function (paw, utils) {
    function Deferred() {
        var self = this;
        this.resolve = null;
        this.reject = null;

        this.promise = new Promise(function (resolve, reject) {
            self.resolve = resolve;
            self.reject = reject;
        });
    }

    function AsyncUtils() {

    }

    AsyncUtils.prototype.defer = function () {
        return new Deferred();
    };

    AsyncUtils.prototype.promise = function () {
        let d = new Deferred();
        d.resolve();
        return d.promise;
    };

    AsyncUtils.prototype.reject = function () {
        let d = new Deferred();
        d.reject();
        return d.promise;
    };
    AsyncUtils.prototype.when = function (p) {
        if (paw.utils.value.notSet(p) || paw.utils.value.notFunction(p.then)) {
            return this.promise();
        }
        return p;
    };


    AsyncUtils.prototype.getFile = function (settings) {

        if (paw.utils.value.notSet(settings.url)) {
            throw "AsyncUtils: URL not set!";
        }

        var xhr = new XMLHttpRequest();
        xhr.open('GET', settings.url, true);
        xhr.responseType = 'blob';

        let d = paw.utils.async.defer();

        xhr.onload = function () {
            if (xhr.status === 200) {
                d.resolve(xhr.response);
            } else {
                console.error('Request failed with status:', xhr.status);
                d.reject(xhr.response);
            }
        };

        xhr.onerror = function () {
            console.error('Request failed');
            d.reject(xhr.response);
        };

        xhr.send();
        return d.promise;
    };

    AsyncUtils.prototype.delay = function (ms) {
        let d = new Deferred();
        setTimeout(function () {
            d.resolve();
        }, ms);
        return d.promise;
    };

    AsyncUtils.prototype.request = function (type, url, payload) {
        var d = this.defer();
        $.ajax({
            type: type,
            url: paw.utils.value.isObject(url) ? url.url + qparams(url.params) : url,
            data: payload,
            success: function (data) {
                d.resolve(data);
            },
            error: function () {
                d.reject();
            }
        });
        return d.promise;
    };

    AsyncUtils.prototype.get = function (url) {
        return this.request('get', url);
    };

    AsyncUtils.prototype.post = function (url, payload) {
        return this.request('post', url, payload);
    };

    AsyncUtils.prototype.put = function (url, payload) {
        return this.request('put', url, payload);
    };

    AsyncUtils.prototype.delete = function (url, payload) {
        return this.request('delete', url, payload);
    };

    AsyncUtils.prototype.asyncAction = function (request, $elt, $linkedActions) {
        var self = this;
        $elt.attr('disabled', true);
        this.actionIndicator($elt, true);

        var linkedActionsPreviousStates = [];
        if (paw.utils.value.isSet($linkedActions)) {
            utils.iterable.iterate($linkedActions, function ($action) {
                if ($action.is(':disabled')) {
                    linkedActionsPreviousStates.push('disabled');
                    return;
                }
                linkedActionsPreviousStates.push('enabled');
                $action.attr('disabled', true);
            });
        }

        return this.when(request()).finally(function () {
            // prevent double clicking
            return self.delay(200).then(function () {
                self.actionIndicator($elt, false);
                $elt.removeAttr('disabled');
                if (paw.utils.value.isSet($linkedActions)) {
                    utils.iterable.iterate($linkedActions, function ($action, i) {
                        var prevState = linkedActionsPreviousStates[i];
                        if (prevState === 'enabled') {
                            $action.removeAttr('disabled');
                        }
                    });
                }
            });
        });
        return d.promise;
    };

    AsyncUtils.prototype.actionIndicator = function ($elt, isLoading) {
        if (utils.value.notSet(isLoading)) {
            isLoading = true;
        }
        var indic = $elt.attr('data-loader-indic');
        if ((isLoading && utils.value.isSet(indic)) || (!isLoading && utils.value.notSet(indic))) {
            return;
        }
        var $icon = $elt.find('> i');
        if ($icon.length === 0) {
            $elt.attr('data-loader-no-icon', true);
            $icon = $('<i class="fa fa-spinner fa-spin fa-fw"></i>')
            $elt.prepend($icon);
        }
        if (utils.value.notSet(indic)) {
            indic = $icon.attr('class');
            $elt.attr('data-loader-indic', indic);
            $icon.attr('class', 'fa fa-spinner fa-spin fa-fw');
            return;
        }
        $icon.attr('class', indic);
        $elt.removeAttr('data-loader-indic');
        if (utils.value.isSet($elt.attr('data-loader-no-icon'))) {
            $icon.remove();
            $elt.removeAttr('data-loader-no-icon');
        }
    };

    paw.utils.async = new AsyncUtils();
})(paw, paw.utils);