"use strict";
var __extends = (this && this.__extends) || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var base_1 = require("./base");
var support_1 = require("./support");
var error_1 = require("./error");
var Request = (function (_super) {
    __extends(Request, _super);
    function Request(options) {
        var _this = _super.call(this, options) || this;
        _this.middleware = [];
        _this.opened = false;
        _this.aborted = false;
        _this.uploaded = 0;
        _this.downloaded = 0;
        _this.timeout = (options.timeout | 0);
        _this.method = (options.method || 'GET').toUpperCase();
        _this.body = options.body;
        _this.events = options.events || Object.create(null);
        _this.transport = Object.assign({}, options.transport);
        _this.use(options.use || _this.transport.use);
        _this._promise = Promise.resolve().then(function () { return exec(_this); });
        _this.once('abort', function () {
            if (_this.completed === 1) {
                return;
            }
            _this.aborted = true;
        });
        return _this;
    }
    Request.prototype.error = function (message, code, original) {
        return new error_1.default(message, code, original, this);
    };
    Request.prototype.then = function (onFulfilled, onRejected) {
        return this._promise.then(onFulfilled, onRejected);
    };
    Request.prototype.catch = function (onRejected) {
        return this._promise.then(null, onRejected);
    };
    Request.prototype.exec = function (cb) {
        this.then(function (res) { return cb(null, res); }, cb);
    };
    Request.prototype.toOptions = function () {
        return {
            url: this.url,
            method: this.method,
            body: this.body,
            transport: this.transport,
            timeout: this.timeout,
            rawHeaders: this.rawHeaders,
            use: this.middleware,
            events: this.events
        };
    };
    Request.prototype.toJSON = function () {
        return {
            url: this.url,
            method: this.method,
            headers: this.headers,
            body: this.body,
            timeout: this.timeout
        };
    };
    Request.prototype.clone = function () {
        return new Request(this.toOptions());
    };
    Request.prototype.use = function (fn) {
        if (Array.isArray(fn)) {
            (_a = this.middleware).push.apply(_a, fn);
        }
        else {
            this.middleware.push(fn);
        }
        return this;
        var _a;
    };
    Request.prototype.on = function (event, fn) {
        if (Object.prototype.hasOwnProperty.call(this.events, event)) {
            this.events[event].push(fn);
        }
        else {
            this.events[event] = [fn];
        }
        return this;
    };
    Request.prototype.off = function (event, fn) {
        if (Object.prototype.hasOwnProperty.call(this.events, event)) {
            var list = this.events[event];
            var index = -1;
            for (var i = 0; i < list.length; i++) {
                if (list[i] === fn || list[i].listener === fn) {
                    index = i;
                    break;
                }
            }
            if (index > -1) {
                if (list.length === 1) {
                    delete this.events[event];
                }
                else {
                    support_1.splice(this.events[event], index);
                }
            }
        }
        return this;
    };
    Request.prototype.once = function (event, fn) {
        return this.on(event, wrapOnce(this, event, fn));
    };
    Request.prototype.emit = function (event) {
        var args = [];
        for (var _i = 1; _i < arguments.length; _i++) {
            args[_i - 1] = arguments[_i];
        }
        if (!Object.prototype.hasOwnProperty.call(this.events, event)) {
            return this;
        }
        var listeners = this.events[event];
        if (listeners.length === 1) {
            args.length === 0 ? listeners[0].call(this) : listeners[0].apply(this, args);
        }
        else {
            for (var _a = 0, _b = listeners.slice(); _a < _b.length; _a++) {
                var listener = _b[_a];
                args.length === 0 ? listener.call(this) : listener.apply(this, args);
            }
        }
        return this;
    };
    Request.prototype.abort = function () {
        return this.emit('abort');
    };
    Request.prototype.handle = function () {
        var _this = this;
        this.opened = true;
        if (/^https?\:\/*(?:[~#\\\?;\:]|$)/.test(this.url)) {
            return Promise.reject(this.error("Refused to connect to invalid URL \"" + this.url + "\"", 'EINVALID'));
        }
        var timeout = this.timeout;
        var timer;
        var result = new Promise(function (resolve, reject) {
            if (timeout > 0) {
                timer = setTimeout(function () {
                    reject(_this.error("Timeout of " + timeout + "ms exceeded", 'ETIMEOUT'));
                    _this.abort();
                }, timeout);
            }
            _this.once('abort', function () {
                _this.emit('progress');
                reject(_this.error('Request aborted', 'EABORT'));
                if (_this.transport.abort) {
                    _this.transport.abort(_this);
                }
            });
            Promise.resolve(_this.transport.open(_this)).then(function (res) { return resolve(res); }, function (err) { return reject(err); });
        });
        if (timeout > 0) {
            result.then(function () { return clearTimeout(timer); }, function () { return clearTimeout(timer); });
        }
        return result;
    };
    Object.defineProperty(Request.prototype, "completed", {
        get: function () {
            return (this.uploaded + this.downloaded) / 2;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Request.prototype, "completedBytes", {
        get: function () {
            return this.uploadedBytes + this.downloadedBytes;
        },
        enumerable: true,
        configurable: true
    });
    Object.defineProperty(Request.prototype, "totalBytes", {
        get: function () {
            return this.uploadLength + this.downloadLength;
        },
        enumerable: true,
        configurable: true
    });
    Request.prototype._setUploadedBytes = function (bytes, uploaded) {
        if (bytes !== this.uploadedBytes) {
            this.uploaded = uploaded || bytes / this.uploadLength;
            this.uploadedBytes = bytes;
            this.emit('progress');
        }
    };
    Request.prototype._setDownloadedBytes = function (bytes, downloaded) {
        if (bytes !== this.downloadedBytes) {
            this.downloaded = downloaded || bytes / this.downloadLength;
            this.downloadedBytes = bytes;
            this.emit('progress');
        }
    };
    return Request;
}(base_1.Base));
exports.Request = Request;
function wrapOnce(target, event, fn) {
    var fired = false;
    var g = function () {
        var args = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            args[_i] = arguments[_i];
        }
        if (!fired) {
            fired = true;
            target.off(event, fn);
            args.length === 0 ? fn.call(target) : fn.apply(target, args);
        }
    };
    g.listener = fn;
    return g;
}
function exec(req) {
    var index = -1;
    function dispatch(pos) {
        if (pos <= index) {
            throw new TypeError('`next()` called multiple times');
        }
        if (req.aborted) {
            return Promise.reject(req.error('Request aborted', 'EABORT'));
        }
        index = pos;
        var fn = req.middleware[pos] || (function () { return req.handle(); });
        return new Promise(function (resolve) {
            return resolve(fn(req, function next() {
                return dispatch(pos + 1);
            }));
        });
    }
    return dispatch(0);
}
//# sourceMappingURL=request.js.map