/*!
 * @pixi/loaders - v6.3.0
 * Compiled Wed, 23 Mar 2022 18:58:56 UTC
 *
 * @pixi/loaders is licensed under the MIT License.
 * http://www.opensource.org/licenses/mit-license
 */
import { Texture } from '@pixi/core';

/* jshint -W097 */
/**
 * @memberof PIXI
 */
var SignalBinding = /** @class */ (function () {
    /**
     * SignalBinding constructor.
     * @constructs SignalBinding
     * @param {Function} fn - Event handler to be called.
     * @param {Boolean} [once=false] - Should this listener be removed after dispatch
     * @param {object} [thisArg] - The context of the callback function.
     * @api private
     */
    // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
    function SignalBinding(fn, once, thisArg) {
        if (once === void 0) { once = false; }
        this._fn = fn;
        this._once = once;
        this._thisArg = thisArg;
        this._next = this._prev = this._owner = null;
    }
    SignalBinding.prototype.detach = function () {
        if (this._owner === null)
            { return false; }
        this._owner.detach(this);
        return true;
    };
    return SignalBinding;
}());
/**
 * @private
 */
function _addSignalBinding(self, node) {
    if (!self._head) {
        self._head = node;
        self._tail = node;
    }
    else {
        self._tail._next = node;
        node._prev = self._tail;
        self._tail = node;
    }
    node._owner = self;
    return node;
}
/**
 * @memberof PIXI
 */
var Signal = /** @class */ (function () {
    /**
     * MiniSignal constructor.
     * @example
     * let mySignal = new Signal();
     * let binding = mySignal.add(onSignal);
     * mySignal.dispatch('foo', 'bar');
     * mySignal.detach(binding);
     */
    function Signal() {
        this._head = this._tail = undefined;
    }
    /**
     * Return an array of attached SignalBinding.
     *
     * @param {Boolean} [exists=false] - We only need to know if there are handlers.
     * @returns {PIXI.SignalBinding[]|Boolean} Array of attached SignalBinding or Boolean if called with exists = true
     * @api public
     */
    Signal.prototype.handlers = function (exists) {
        if (exists === void 0) { exists = false; }
        var node = this._head;
        if (exists)
            { return !!node; }
        var ee = [];
        while (node) {
            ee.push(node);
            node = node._next;
        }
        return ee;
    };
    /**
     * Return true if node is a SignalBinding attached to this MiniSignal
     *
     * @param {PIXI.SignalBinding} node - Node to check.
     * @returns {Boolean} True if node is attache to mini-signal
     */
    Signal.prototype.has = function (node) {
        if (!(node instanceof SignalBinding)) {
            throw new Error('MiniSignal#has(): First arg must be a SignalBinding object.');
        }
        return node._owner === this;
    };
    /**
     * Dispaches a signal to all registered listeners.
     *
     * @returns {Boolean} Indication if we've emitted an event.
     */
    Signal.prototype.dispatch = function () {
        var arguments$1 = arguments;

        var args = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            args[_i] = arguments$1[_i];
        }
        var node = this._head;
        if (!node)
            { return false; }
        while (node) {
            if (node._once)
                { this.detach(node); }
            node._fn.apply(node._thisArg, args);
            node = node._next;
        }
        return true;
    };
    /**
     * Register a new listener.
     *
     * @param {Function} fn - Callback function.
     * @param {object} [thisArg] - The context of the callback function.
     * @returns {PIXI.SignalBinding} The SignalBinding node that was added.
     */
    Signal.prototype.add = function (fn, thisArg) {
        if (thisArg === void 0) { thisArg = null; }
        if (typeof fn !== 'function') {
            throw new Error('MiniSignal#add(): First arg must be a Function.');
        }
        return _addSignalBinding(this, new SignalBinding(fn, false, thisArg));
    };
    /**
     * Register a new listener that will be executed only once.
     *
     * @param {Function} fn - Callback function.
     * @param {object} [thisArg] - The context of the callback function.
     * @returns {PIXI.SignalBinding} The SignalBinding node that was added.
     */
    Signal.prototype.once = function (fn, thisArg) {
        if (thisArg === void 0) { thisArg = null; }
        if (typeof fn !== 'function') {
            throw new Error('MiniSignal#once(): First arg must be a Function.');
        }
        return _addSignalBinding(this, new SignalBinding(fn, true, thisArg));
    };
    /**
     * Remove binding object.
     *
     * @param {PIXI.SignalBinding} node - The binding node that will be removed.
     * @returns {Signal} The instance on which this method was called.
     * @api public */
    Signal.prototype.detach = function (node) {
        if (!(node instanceof SignalBinding)) {
            throw new Error('MiniSignal#detach(): First arg must be a SignalBinding object.');
        }
        if (node._owner !== this)
            { return this; } // todo: or error?
        if (node._prev)
            { node._prev._next = node._next; }
        if (node._next)
            { node._next._prev = node._prev; }
        if (node === this._head) { // first node
            this._head = node._next;
            if (node._next === null) {
                this._tail = null;
            }
        }
        else if (node === this._tail) { // last node
            this._tail = node._prev;
            this._tail._next = null;
        }
        node._owner = null;
        return this;
    };
    /**
     * Detach all listeners.
     *
     * @returns {Signal} The instance on which this method was called.
     */
    Signal.prototype.detachAll = function () {
        var node = this._head;
        if (!node)
            { return this; }
        this._head = this._tail = null;
        while (node) {
            node._owner = null;
            node = node._next;
        }
        return this;
    };
    return Signal;
}());

/**
 * function from npm package `parseUri`, converted to TS to avoid leftpad incident
 * @param {string} str
 * @param [opts] - options
 * @param {boolean} [opts.strictMode] - type of parser
 */
function parseUri(str, opts) {
    opts = opts || {};
    var o = {
        // eslint-disable-next-line max-len
        key: ['source', 'protocol', 'authority', 'userInfo', 'user', 'password', 'host', 'port', 'relative', 'path', 'directory', 'file', 'query', 'anchor'],
        q: {
            name: 'queryKey',
            parser: /(?:^|&)([^&=]*)=?([^&]*)/g
        },
        parser: {
            // eslint-disable-next-line max-len
            strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
            // eslint-disable-next-line max-len
            loose: /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*)(?::([^:@]*))?)?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
        }
    };
    var m = o.parser[opts.strictMode ? 'strict' : 'loose'].exec(str);
    var uri = {};
    var i = 14;
    while (i--)
        { uri[o.key[i]] = m[i] || ''; }
    uri[o.q.name] = {};
    uri[o.key[12]].replace(o.q.parser, function (_t0, t1, t2) {
        if (t1)
            { uri[o.q.name][t1] = t2; }
    });
    return uri;
}

// tests if CORS is supported in XHR, if not we need to use XDR
var useXdr = !!(globalThis.XDomainRequest && !('withCredentials' in (new XMLHttpRequest())));
var tempAnchor = null;
// some status constants
var STATUS_NONE = 0;
var STATUS_OK = 200;
var STATUS_EMPTY = 204;
var STATUS_IE_BUG_EMPTY = 1223;
var STATUS_TYPE_OK = 2;
// noop
function _noop() { }
/**
 * Quick helper to set a value on one of the extension maps. Ensures there is no
 * dot at the start of the extension.
 *
 * @ignore
 * @param map - The map to set on.
 * @param extname - The extension (or key) to set.
 * @param val - The value to set.
 */
function setExtMap(map, extname, val) {
    if (extname && extname.indexOf('.') === 0) {
        extname = extname.substring(1);
    }
    if (!extname) {
        return;
    }
    map[extname] = val;
}
/**
 * Quick helper to get string xhr type.
 *
 * @ignore
 * @param xhr - The request to check.
 * @return The type.
 */
function reqType(xhr) {
    return xhr.toString().replace('object ', '');
}
/**
 * Manages the state and loading of a resource and all child resources.
 *
 * Can be extended in `GlobalMixins.LoaderResource`.
 *
 * @memberof PIXI
 */
var LoaderResource = /** @class */ (function () {
    /**
     * @param {string} name - The name of the resource to load.
     * @param {string|string[]} url - The url for this resource, for audio/video loads you can pass
     *      an array of sources.
     * @param {object} [options] - The options for the load.
     * @param {string|boolean} [options.crossOrigin] - Is this request cross-origin? Default is to
     *      determine automatically.
     * @param {number} [options.timeout=0] - A timeout in milliseconds for the load. If the load takes
     *      longer than this time it is cancelled and the load is considered a failure. If this value is
     *      set to `0` then there is no explicit timeout.
     * @param {PIXI.LoaderResource.LOAD_TYPE} [options.loadType=LOAD_TYPE.XHR] - How should this resource
     *      be loaded?
     * @param {PIXI.LoaderResource.XHR_RESPONSE_TYPE} [options.xhrType=XHR_RESPONSE_TYPE.DEFAULT] - How
     *      should the data being loaded be interpreted when using XHR?
     * @param {PIXI.LoaderResource.IMetadata} [options.metadata] - Extra configuration for middleware
     *      and the Resource object.
     */
    function LoaderResource(name, url, options) {
        /**
         * The `dequeue` method that will be used a storage place for the async queue dequeue method
         * used privately by the loader.
         *
         * @private
         * @member {function}
         */
        this._dequeue = _noop;
        /**
         * Used a storage place for the on load binding used privately by the loader.
         *
         * @private
         * @member {function}
         */
        this._onLoadBinding = null;
        /**
         * The timer for element loads to check if they timeout.
         *
         * @private
         */
        this._elementTimer = 0;
        /**
         * The `complete` function bound to this resource's context.
         *
         * @private
         * @type {function}
         */
        this._boundComplete = null;
        /**
         * The `_onError` function bound to this resource's context.
         *
         * @private
         * @type {function}
         */
        this._boundOnError = null;
        /**
         * The `_onProgress` function bound to this resource's context.
         *
         * @private
         * @type {function}
         */
        this._boundOnProgress = null;
        /**
         * The `_onTimeout` function bound to this resource's context.
         *
         * @private
         * @type {function}
         */
        this._boundOnTimeout = null;
        this._boundXhrOnError = null;
        this._boundXhrOnTimeout = null;
        this._boundXhrOnAbort = null;
        this._boundXhrOnLoad = null;
        if (typeof name !== 'string' || typeof url !== 'string') {
            throw new Error('Both name and url are required for constructing a resource.');
        }
        options = options || {};
        this._flags = 0;
        // set data url flag, needs to be set early for some _determineX checks to work.
        this._setFlag(LoaderResource.STATUS_FLAGS.DATA_URL, url.indexOf('data:') === 0);
        this.name = name;
        this.url = url;
        this.extension = this._getExtension();
        this.data = null;
        this.crossOrigin = options.crossOrigin === true ? 'anonymous' : options.crossOrigin;
        this.timeout = options.timeout || 0;
        this.loadType = options.loadType || this._determineLoadType();
        // The type used to load the resource via XHR. If unset, determined automatically.
        this.xhrType = options.xhrType;
        // Extra info for middleware, and controlling specifics about how the resource loads.
        // Note that if you pass in a `loadElement`, the Resource class takes ownership of it.
        // Meaning it will modify it as it sees fit.
        this.metadata = options.metadata || {};
        // The error that occurred while loading (if any).
        this.error = null;
        // The XHR object that was used to load this resource. This is only set
        // when `loadType` is `LoaderResource.LOAD_TYPE.XHR`.
        this.xhr = null;
        // The child resources this resource owns.
        this.children = [];
        // The resource type.
        this.type = LoaderResource.TYPE.UNKNOWN;
        // The progress chunk owned by this resource.
        this.progressChunk = 0;
        // The `dequeue` method that will be used a storage place for the async queue dequeue method
        // used privately by the loader.
        this._dequeue = _noop;
        // Used a storage place for the on load binding used privately by the loader.
        this._onLoadBinding = null;
        // The timer for element loads to check if they timeout.
        this._elementTimer = 0;
        this._boundComplete = this.complete.bind(this);
        this._boundOnError = this._onError.bind(this);
        this._boundOnProgress = this._onProgress.bind(this);
        this._boundOnTimeout = this._onTimeout.bind(this);
        // xhr callbacks
        this._boundXhrOnError = this._xhrOnError.bind(this);
        this._boundXhrOnTimeout = this._xhrOnTimeout.bind(this);
        this._boundXhrOnAbort = this._xhrOnAbort.bind(this);
        this._boundXhrOnLoad = this._xhrOnLoad.bind(this);
        // Dispatched when the resource beings to load.
        this.onStart = new Signal();
        // Dispatched each time progress of this resource load updates.
        // Not all resources types and loader systems can support this event
        // so sometimes it may not be available. If the resource
        // is being loaded on a modern browser, using XHR, and the remote server
        // properly sets Content-Length headers, then this will be available.
        this.onProgress = new Signal();
        // Dispatched once this resource has loaded, if there was an error it will
        // be in the `error` property.
        this.onComplete = new Signal();
        // Dispatched after this resource has had all the *after* middleware run on it.
        this.onAfterMiddleware = new Signal();
    }
    /**
     * Sets the load type to be used for a specific extension.
     *
     * @static
     * @param {string} extname - The extension to set the type for, e.g. "png" or "fnt"
     * @param {PIXI.LoaderResource.LOAD_TYPE} loadType - The load type to set it to.
     */
    LoaderResource.setExtensionLoadType = function (extname, loadType) {
        setExtMap(LoaderResource._loadTypeMap, extname, loadType);
    };
    /**
     * Sets the load type to be used for a specific extension.
     *
     * @static
     * @param {string} extname - The extension to set the type for, e.g. "png" or "fnt"
     * @param {PIXI.LoaderResource.XHR_RESPONSE_TYPE} xhrType - The xhr type to set it to.
     */
    LoaderResource.setExtensionXhrType = function (extname, xhrType) {
        setExtMap(LoaderResource._xhrTypeMap, extname, xhrType);
    };
    Object.defineProperty(LoaderResource.prototype, "isDataUrl", {
        /**
         * When the resource starts to load.
         *
         * @memberof PIXI.LoaderResource
         * @callback OnStartSignal
         * @param {Resource} resource - The resource that the event happened on.
         */
        /**
         * When the resource reports loading progress.
         *
         * @memberof PIXI.LoaderResource
         * @callback OnProgressSignal
         * @param {Resource} resource - The resource that the event happened on.
         * @param {number} percentage - The progress of the load in the range [0, 1].
         */
        /**
         * When the resource finishes loading.
         *
         * @memberof PIXI.LoaderResource
         * @callback OnCompleteSignal
         * @param {Resource} resource - The resource that the event happened on.
         */
        /**
         * @memberof PIXI.LoaderResource
         * @typedef {object} IMetadata
         * @property {HTMLImageElement|HTMLAudioElement|HTMLVideoElement} [loadElement=null] - The
         *      element to use for loading, instead of creating one.
         * @property {boolean} [skipSource=false] - Skips adding source(s) to the load element. This
         *      is useful if you want to pass in a `loadElement` that you already added load sources to.
         * @property {string|string[]} [mimeType] - The mime type to use for the source element
         *      of a video/audio elment. If the urls are an array, you can pass this as an array as well
         *      where each index is the mime type to use for the corresponding url index.
         */
        /**
         * Stores whether or not this url is a data url.
         *
         * @readonly
         * @member {boolean}
         */
        get: function () {
            return this._hasFlag(LoaderResource.STATUS_FLAGS.DATA_URL);
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(LoaderResource.prototype, "isComplete", {
        /**
         * Describes if this resource has finished loading. Is true when the resource has completely
         * loaded.
         *
         * @readonly
         * @member {boolean}
         */
        get: function () {
            return this._hasFlag(LoaderResource.STATUS_FLAGS.COMPLETE);
        },
        enumerable: false,
        configurable: true
    });
    Object.defineProperty(LoaderResource.prototype, "isLoading", {
        /**
         * Describes if this resource is currently loading. Is true when the resource starts loading,
         * and is false again when complete.
         *
         * @readonly
         * @member {boolean}
         */
        get: function () {
            return this._hasFlag(LoaderResource.STATUS_FLAGS.LOADING);
        },
        enumerable: false,
        configurable: true
    });
    /**
     * Marks the resource as complete.
     *
     */
    LoaderResource.prototype.complete = function () {
        this._clearEvents();
        this._finish();
    };
    /**
     * Aborts the loading of this resource, with an optional message.
     *
     * @param {string} message - The message to use for the error
     */
    LoaderResource.prototype.abort = function (message) {
        // abort can be called multiple times, ignore subsequent calls.
        if (this.error) {
            return;
        }
        // store error
        this.error = new Error(message);
        // clear events before calling aborts
        this._clearEvents();
        // abort the actual loading
        if (this.xhr) {
            this.xhr.abort();
        }
        else if (this.xdr) {
            this.xdr.abort();
        }
        else if (this.data) {
            // single source
            if (this.data.src) {
                this.data.src = LoaderResource.EMPTY_GIF;
            }
            // multi-source
            else {
                while (this.data.firstChild) {
                    this.data.removeChild(this.data.firstChild);
                }
            }
        }
        // done now.
        this._finish();
    };
    /**
     * Kicks off loading of this resource. This method is asynchronous.
     *
     * @param {PIXI.LoaderResource.OnCompleteSignal} [cb] - Optional callback to call once the resource is loaded.
     */
    LoaderResource.prototype.load = function (cb) {
        var _this = this;
        if (this.isLoading) {
            return;
        }
        if (this.isComplete) {
            if (cb) {
                setTimeout(function () { return cb(_this); }, 1);
            }
            return;
        }
        else if (cb) {
            this.onComplete.once(cb);
        }
        this._setFlag(LoaderResource.STATUS_FLAGS.LOADING, true);
        this.onStart.dispatch(this);
        // if unset, determine the value
        if (this.crossOrigin === false || typeof this.crossOrigin !== 'string') {
            this.crossOrigin = this._determineCrossOrigin(this.url);
        }
        switch (this.loadType) {
            case LoaderResource.LOAD_TYPE.IMAGE:
                this.type = LoaderResource.TYPE.IMAGE;
                this._loadElement('image');
                break;
            case LoaderResource.LOAD_TYPE.AUDIO:
                this.type = LoaderResource.TYPE.AUDIO;
                this._loadSourceElement('audio');
                break;
            case LoaderResource.LOAD_TYPE.VIDEO:
                this.type = LoaderResource.TYPE.VIDEO;
                this._loadSourceElement('video');
                break;
            case LoaderResource.LOAD_TYPE.XHR:
            /* falls through */
            default:
                if (useXdr && this.crossOrigin) {
                    this._loadXdr();
                }
                else {
                    this._loadXhr();
                }
                break;
        }
    };
    /**
     * Checks if the flag is set.
     *
     * @param flag - The flag to check.
     * @return True if the flag is set.
     */
    LoaderResource.prototype._hasFlag = function (flag) {
        return (this._flags & flag) !== 0;
    };
    /**
     * (Un)Sets the flag.
     *
     * @param flag - The flag to (un)set.
     * @param value - Whether to set or (un)set the flag.
     */
    LoaderResource.prototype._setFlag = function (flag, value) {
        this._flags = value ? (this._flags | flag) : (this._flags & ~flag);
    };
    /**
     * Clears all the events from the underlying loading source.
     */
    LoaderResource.prototype._clearEvents = function () {
        clearTimeout(this._elementTimer);
        if (this.data && this.data.removeEventListener) {
            this.data.removeEventListener('error', this._boundOnError, false);
            this.data.removeEventListener('load', this._boundComplete, false);
            this.data.removeEventListener('progress', this._boundOnProgress, false);
            this.data.removeEventListener('canplaythrough', this._boundComplete, false);
        }
        if (this.xhr) {
            if (this.xhr.removeEventListener) {
                this.xhr.removeEventListener('error', this._boundXhrOnError, false);
                this.xhr.removeEventListener('timeout', this._boundXhrOnTimeout, false);
                this.xhr.removeEventListener('abort', this._boundXhrOnAbort, false);
                this.xhr.removeEventListener('progress', this._boundOnProgress, false);
                this.xhr.removeEventListener('load', this._boundXhrOnLoad, false);
            }
            else {
                this.xhr.onerror = null;
                this.xhr.ontimeout = null;
                this.xhr.onprogress = null;
                this.xhr.onload = null;
            }
        }
    };
    /**
     * Finalizes the load.
     */
    LoaderResource.prototype._finish = function () {
        if (this.isComplete) {
            throw new Error('Complete called again for an already completed resource.');
        }
        this._setFlag(LoaderResource.STATUS_FLAGS.COMPLETE, true);
        this._setFlag(LoaderResource.STATUS_FLAGS.LOADING, false);
        this.onComplete.dispatch(this);
    };
    /**
     * Loads this resources using an element that has a single source,
     * like an HTMLImageElement.
     * @private
     * @param type - The type of element to use.
     */
    LoaderResource.prototype._loadElement = function (type) {
        if (this.metadata.loadElement) {
            this.data = this.metadata.loadElement;
        }
        else if (type === 'image' && typeof globalThis.Image !== 'undefined') {
            this.data = new Image();
        }
        else {
            this.data = document.createElement(type);
        }
        if (this.crossOrigin) {
            this.data.crossOrigin = this.crossOrigin;
        }
        if (!this.metadata.skipSource) {
            this.data.src = this.url;
        }
        this.data.addEventListener('error', this._boundOnError, false);
        this.data.addEventListener('load', this._boundComplete, false);
        this.data.addEventListener('progress', this._boundOnProgress, false);
        if (this.timeout) {
            this._elementTimer = setTimeout(this._boundOnTimeout, this.timeout);
        }
    };
    /**
     * Loads this resources using an element that has multiple sources,
     * like an HTMLAudioElement or HTMLVideoElement.
     * @param type - The type of element to use.
     */
    LoaderResource.prototype._loadSourceElement = function (type) {
        if (this.metadata.loadElement) {
            this.data = this.metadata.loadElement;
        }
        else if (type === 'audio' && typeof globalThis.Audio !== 'undefined') {
            this.data = new Audio();
        }
        else {
            this.data = document.createElement(type);
        }
        if (this.data === null) {
            this.abort("Unsupported element: " + type);
            return;
        }
        if (this.crossOrigin) {
            this.data.crossOrigin = this.crossOrigin;
        }
        if (!this.metadata.skipSource) {
            // support for CocoonJS Canvas+ runtime, lacks document.createElement('source')
            if (navigator.isCocoonJS) {
                this.data.src = Array.isArray(this.url) ? this.url[0] : this.url;
            }
            else if (Array.isArray(this.url)) {
                var mimeTypes = this.metadata.mimeType;
                for (var i = 0; i < this.url.length; ++i) {
                    this.data.appendChild(this._createSource(type, this.url[i], Array.isArray(mimeTypes) ? mimeTypes[i] : mimeTypes));
                }
            }
            else {
                var mimeTypes = this.metadata.mimeType;
                this.data.appendChild(this._createSource(type, this.url, Array.isArray(mimeTypes) ? mimeTypes[0] : mimeTypes));
            }
        }
        this.data.addEventListener('error', this._boundOnError, false);
        this.data.addEventListener('load', this._boundComplete, false);
        this.data.addEventListener('progress', this._boundOnProgress, false);
        this.data.addEventListener('canplaythrough', this._boundComplete, false);
        this.data.load();
        if (this.timeout) {
            this._elementTimer = setTimeout(this._boundOnTimeout, this.timeout);
        }
    };
    /**
     * Loads this resources using an XMLHttpRequest.
     */
    LoaderResource.prototype._loadXhr = function () {
        // if unset, determine the value
        if (typeof this.xhrType !== 'string') {
            this.xhrType = this._determineXhrType();
        }
        var xhr = this.xhr = new XMLHttpRequest();
        // send credentials when crossOrigin with credentials requested
        if (this.crossOrigin === 'use-credentials') {
            xhr.withCredentials = true;
        }
        // set the request type and url
        xhr.open('GET', this.url, true);
        xhr.timeout = this.timeout;
        // load json as text and parse it ourselves. We do this because some browsers
        // *cough* safari *cough* can't deal with it.
        if (this.xhrType === LoaderResource.XHR_RESPONSE_TYPE.JSON
            || this.xhrType === LoaderResource.XHR_RESPONSE_TYPE.DOCUMENT) {
            xhr.responseType = LoaderResource.XHR_RESPONSE_TYPE.TEXT;
        }
        else {
            xhr.responseType = this.xhrType;
        }
        xhr.addEventListener('error', this._boundXhrOnError, false);
        xhr.addEventListener('timeout', this._boundXhrOnTimeout, false);
        xhr.addEventListener('abort', this._boundXhrOnAbort, false);
        xhr.addEventListener('progress', this._boundOnProgress, false);
        xhr.addEventListener('load', this._boundXhrOnLoad, false);
        xhr.send();
    };
    /**
     * Loads this resources using an XDomainRequest. This is here because we need to support IE9 (gross).
     */
    LoaderResource.prototype._loadXdr = function () {
        // if unset, determine the value
        if (typeof this.xhrType !== 'string') {
            this.xhrType = this._determineXhrType();
        }
        var xdr = this.xhr = new globalThis.XDomainRequest(); // eslint-disable-line no-undef
        // XDomainRequest has a few quirks. Occasionally it will abort requests
        // A way to avoid this is to make sure ALL callbacks are set even if not used
        // More info here: http://stackoverflow.com/questions/15786966/xdomainrequest-aborts-post-on-ie-9
        xdr.timeout = this.timeout || 5000; // XDR needs a timeout value or it breaks in IE9
        xdr.onerror = this._boundXhrOnError;
        xdr.ontimeout = this._boundXhrOnTimeout;
        xdr.onprogress = this._boundOnProgress;
        xdr.onload = this._boundXhrOnLoad;
        xdr.open('GET', this.url, true);
        // Note: The xdr.send() call is wrapped in a timeout to prevent an
        // issue with the interface where some requests are lost if multiple
        // XDomainRequests are being sent at the same time.
        // Some info here: https://github.com/photonstorm/phaser/issues/1248
        setTimeout(function () { return xdr.send(); }, 1);
    };
    /**
     * Creates a source used in loading via an element.
     * @param type - The element type (video or audio).
     * @param url - The source URL to load from.
     * @param [mime] - The mime type of the video
     * @return The source element.
     */
    LoaderResource.prototype._createSource = function (type, url, mime) {
        if (!mime) {
            mime = type + "/" + this._getExtension(url);
        }
        var source = document.createElement('source');
        source.src = url;
        source.type = mime;
        return source;
    };
    /**
     * Called if a load errors out.
     *
     * @param event - The error event from the element that emits it.
     */
    LoaderResource.prototype._onError = function (event) {
        this.abort("Failed to load element using: " + event.target.nodeName);
    };
    /**
     * Called if a load progress event fires for an element or xhr/xdr.
     * @param event - Progress event.
     */
    LoaderResource.prototype._onProgress = function (event) {
        if (event && event.lengthComputable) {
            this.onProgress.dispatch(this, event.loaded / event.total);
        }
    };
    /**
     * Called if a timeout event fires for an element.
     */
    LoaderResource.prototype._onTimeout = function () {
        this.abort("Load timed out.");
    };
    /**
     * Called if an error event fires for xhr/xdr.
     */
    LoaderResource.prototype._xhrOnError = function () {
        var xhr = this.xhr;
        this.abort(reqType(xhr) + " Request failed. Status: " + xhr.status + ", text: \"" + xhr.statusText + "\"");
    };
    /**
     * Called if an error event fires for xhr/xdr.
     */
    LoaderResource.prototype._xhrOnTimeout = function () {
        var xhr = this.xhr;
        this.abort(reqType(xhr) + " Request timed out.");
    };
    /**
     * Called if an abort event fires for xhr/xdr.
     */
    LoaderResource.prototype._xhrOnAbort = function () {
        var xhr = this.xhr;
        this.abort(reqType(xhr) + " Request was aborted by the user.");
    };
    /**
     * Called when data successfully loads from an xhr/xdr request.
     */
    LoaderResource.prototype._xhrOnLoad = function () {
        var xhr = this.xhr;
        var text = '';
        var status = typeof xhr.status === 'undefined' ? STATUS_OK : xhr.status; // XDR has no `.status`, assume 200.
        // responseText is accessible only if responseType is '' or 'text' and on older browsers
        if (xhr.responseType === '' || xhr.responseType === 'text' || typeof xhr.responseType === 'undefined') {
            text = xhr.responseText;
        }
        // status can be 0 when using the `file://` protocol so we also check if a response is set.
        // If it has a response, we assume 200; otherwise a 0 status code with no contents is an aborted request.
        if (status === STATUS_NONE && (text.length > 0 || xhr.responseType === LoaderResource.XHR_RESPONSE_TYPE.BUFFER)) {
            status = STATUS_OK;
        }
        // handle IE9 bug: http://stackoverflow.com/questions/10046972/msie-returns-status-code-of-1223-for-ajax-request
        else if (status === STATUS_IE_BUG_EMPTY) {
            status = STATUS_EMPTY;
        }
        var statusType = (status / 100) | 0;
        if (statusType === STATUS_TYPE_OK) {
            // if text, just return it
            if (this.xhrType === LoaderResource.XHR_RESPONSE_TYPE.TEXT) {
                this.data = text;
                this.type = LoaderResource.TYPE.TEXT;
            }
            // if json, parse into json object
            else if (this.xhrType === LoaderResource.XHR_RESPONSE_TYPE.JSON) {
                try {
                    this.data = JSON.parse(text);
                    this.type = LoaderResource.TYPE.JSON;
                }
                catch (e) {
                    this.abort("Error trying to parse loaded json: " + e);
                    return;
                }
            }
            // if xml, parse into an xml document or div element
            else if (this.xhrType === LoaderResource.XHR_RESPONSE_TYPE.DOCUMENT) {
                try {
                    if (globalThis.DOMParser) {
                        var domparser = new DOMParser();
                        this.data = domparser.parseFromString(text, 'text/xml');
                    }
                    else {
                        var div = document.createElement('div');
                        div.innerHTML = text;
                        this.data = div;
                    }
                    this.type = LoaderResource.TYPE.XML;
                }
                catch (e$1) {
                    this.abort("Error trying to parse loaded xml: " + e$1);
                    return;
                }
            }
            // other types just return the response
            else {
                this.data = xhr.response || text;
            }
        }
        else {
            this.abort("[" + xhr.status + "] " + xhr.statusText + ": " + xhr.responseURL);
            return;
        }
        this.complete();
    };
    /**
     * Sets the `crossOrigin` property for this resource based on if the url
     * for this resource is cross-origin. If crossOrigin was manually set, this
     * function does nothing.
     * @private
     * @param url - The url to test.
     * @param [loc=globalThis.location] - The location object to test against.
     * @return The crossOrigin value to use (or empty string for none).
     */
    // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
    LoaderResource.prototype._determineCrossOrigin = function (url, loc) {
        // data: and javascript: urls are considered same-origin
        if (url.indexOf('data:') === 0) {
            return '';
        }
        // A sandboxed iframe without the 'allow-same-origin' attribute will have a special
        // origin designed not to match globalThis.location.origin, and will always require
        // crossOrigin requests regardless of whether the location matches.
        if (globalThis.origin !== globalThis.location.origin) {
            return 'anonymous';
        }
        // default is globalThis.location
        loc = loc || globalThis.location;
        if (!tempAnchor) {
            tempAnchor = document.createElement('a');
        }
        // let the browser determine the full href for the url of this resource and then
        // parse with the node url lib, we can't use the properties of the anchor element
        // because they don't work in IE9 :(
        tempAnchor.href = url;
        var parsedUrl = parseUri(tempAnchor.href, { strictMode: true });
        var samePort = (!parsedUrl.port && loc.port === '') || (parsedUrl.port === loc.port);
        var protocol = parsedUrl.protocol ? parsedUrl.protocol + ":" : '';
        // if cross origin
        if (parsedUrl.host !== loc.hostname || !samePort || protocol !== loc.protocol) {
            return 'anonymous';
        }
        return '';
    };
    /**
     * Determines the responseType of an XHR request based on the extension of the
     * resource being loaded.
     *
     * @private
     * @return {PIXI.LoaderResource.XHR_RESPONSE_TYPE} The responseType to use.
     */
    LoaderResource.prototype._determineXhrType = function () {
        return LoaderResource._xhrTypeMap[this.extension] || LoaderResource.XHR_RESPONSE_TYPE.TEXT;
    };
    /**
     * Determines the loadType of a resource based on the extension of the
     * resource being loaded.
     *
     * @private
     * @return {PIXI.LoaderResource.LOAD_TYPE} The loadType to use.
     */
    LoaderResource.prototype._determineLoadType = function () {
        return LoaderResource._loadTypeMap[this.extension] || LoaderResource.LOAD_TYPE.XHR;
    };
    /**
     * Extracts the extension (sans '.') of the file being loaded by the resource.
     *
     * @param [url] - url to parse, `this.url` by default.
     * @return The extension.
     */
    LoaderResource.prototype._getExtension = function (url) {
        if (url === void 0) { url = this.url; }
        var ext = '';
        if (this.isDataUrl) {
            var slashIndex = url.indexOf('/');
            ext = url.substring(slashIndex + 1, url.indexOf(';', slashIndex));
        }
        else {
            var queryStart = url.indexOf('?');
            var hashStart = url.indexOf('#');
            var index = Math.min(queryStart > -1 ? queryStart : url.length, hashStart > -1 ? hashStart : url.length);
            url = url.substring(0, index);
            ext = url.substring(url.lastIndexOf('.') + 1);
        }
        return ext.toLowerCase();
    };
    /**
     * Determines the mime type of an XHR request based on the responseType of
     * resource being loaded.
     *
     * @param type - The type to get a mime type for.
     * @private
     * @return The mime type to use.
     */
    LoaderResource.prototype._getMimeFromXhrType = function (type) {
        switch (type) {
            case LoaderResource.XHR_RESPONSE_TYPE.BUFFER:
                return 'application/octet-binary';
            case LoaderResource.XHR_RESPONSE_TYPE.BLOB:
                return 'application/blob';
            case LoaderResource.XHR_RESPONSE_TYPE.DOCUMENT:
                return 'application/xml';
            case LoaderResource.XHR_RESPONSE_TYPE.JSON:
                return 'application/json';
            case LoaderResource.XHR_RESPONSE_TYPE.DEFAULT:
            case LoaderResource.XHR_RESPONSE_TYPE.TEXT:
            /* falls through */
            default:
                return 'text/plain';
        }
    };
    return LoaderResource;
}());
// eslint-disable-next-line @typescript-eslint/no-namespace
(function (LoaderResource) {
    /**
     * The types of resources a resource could represent.
     *
     * @static
     * @readonly
     * @enum {number}
     * @memberof PIXI.LoaderResource
     */
    var STATUS_FLAGS;
    (function (STATUS_FLAGS) {
        /** None */
        STATUS_FLAGS[STATUS_FLAGS["NONE"] = 0] = "NONE";
        /** Data URL */
        STATUS_FLAGS[STATUS_FLAGS["DATA_URL"] = 1] = "DATA_URL";
        /** Complete */
        STATUS_FLAGS[STATUS_FLAGS["COMPLETE"] = 2] = "COMPLETE";
        /** Loading */
        STATUS_FLAGS[STATUS_FLAGS["LOADING"] = 4] = "LOADING";
    })(STATUS_FLAGS = LoaderResource.STATUS_FLAGS || (LoaderResource.STATUS_FLAGS = {}));
    /**
     * The types of resources a resource could represent.
     *
     * @static
     * @readonly
     * @enum {number}
     * @memberof PIXI.LoaderResource
     */
    var TYPE;
    (function (TYPE) {
        /** Unknown */
        TYPE[TYPE["UNKNOWN"] = 0] = "UNKNOWN";
        /** JSON */
        TYPE[TYPE["JSON"] = 1] = "JSON";
        /** XML */
        TYPE[TYPE["XML"] = 2] = "XML";
        /** Image */
        TYPE[TYPE["IMAGE"] = 3] = "IMAGE";
        /** Audio */
        TYPE[TYPE["AUDIO"] = 4] = "AUDIO";
        /** Video */
        TYPE[TYPE["VIDEO"] = 5] = "VIDEO";
        /** Plain text */
        TYPE[TYPE["TEXT"] = 6] = "TEXT";
    })(TYPE = LoaderResource.TYPE || (LoaderResource.TYPE = {}));
    /**
     * The types of loading a resource can use.
     *
     * @static
     * @readonly
     * @enum {number}
     * @memberof PIXI.LoaderResource
     */
    var LOAD_TYPE;
    (function (LOAD_TYPE) {
        /** Uses XMLHttpRequest to load the resource. */
        LOAD_TYPE[LOAD_TYPE["XHR"] = 1] = "XHR";
        /** Uses an `Image` object to load the resource. */
        LOAD_TYPE[LOAD_TYPE["IMAGE"] = 2] = "IMAGE";
        /** Uses an `Audio` object to load the resource. */
        LOAD_TYPE[LOAD_TYPE["AUDIO"] = 3] = "AUDIO";
        /** Uses a `Video` object to load the resource. */
        LOAD_TYPE[LOAD_TYPE["VIDEO"] = 4] = "VIDEO";
    })(LOAD_TYPE = LoaderResource.LOAD_TYPE || (LoaderResource.LOAD_TYPE = {}));
    /**
     * The XHR ready states, used internally.
     *
     * @static
     * @readonly
     * @enum {string}
     * @memberof PIXI.LoaderResource
     */
    var XHR_RESPONSE_TYPE;
    (function (XHR_RESPONSE_TYPE) {
        /** string */
        XHR_RESPONSE_TYPE["DEFAULT"] = "text";
        /** ArrayBuffer */
        XHR_RESPONSE_TYPE["BUFFER"] = "arraybuffer";
        /** Blob */
        XHR_RESPONSE_TYPE["BLOB"] = "blob";
        /** Document */
        XHR_RESPONSE_TYPE["DOCUMENT"] = "document";
        /** Object */
        XHR_RESPONSE_TYPE["JSON"] = "json";
        /** String */
        XHR_RESPONSE_TYPE["TEXT"] = "text";
    })(XHR_RESPONSE_TYPE = LoaderResource.XHR_RESPONSE_TYPE || (LoaderResource.XHR_RESPONSE_TYPE = {}));
    LoaderResource._loadTypeMap = {
        // images
        gif: LoaderResource.LOAD_TYPE.IMAGE,
        png: LoaderResource.LOAD_TYPE.IMAGE,
        bmp: LoaderResource.LOAD_TYPE.IMAGE,
        jpg: LoaderResource.LOAD_TYPE.IMAGE,
        jpeg: LoaderResource.LOAD_TYPE.IMAGE,
        tif: LoaderResource.LOAD_TYPE.IMAGE,
        tiff: LoaderResource.LOAD_TYPE.IMAGE,
        webp: LoaderResource.LOAD_TYPE.IMAGE,
        tga: LoaderResource.LOAD_TYPE.IMAGE,
        svg: LoaderResource.LOAD_TYPE.IMAGE,
        'svg+xml': LoaderResource.LOAD_TYPE.IMAGE,
        // audio
        mp3: LoaderResource.LOAD_TYPE.AUDIO,
        ogg: LoaderResource.LOAD_TYPE.AUDIO,
        wav: LoaderResource.LOAD_TYPE.AUDIO,
        // videos
        mp4: LoaderResource.LOAD_TYPE.VIDEO,
        webm: LoaderResource.LOAD_TYPE.VIDEO,
    };
    LoaderResource._xhrTypeMap = {
        // xml
        xhtml: LoaderResource.XHR_RESPONSE_TYPE.DOCUMENT,
        html: LoaderResource.XHR_RESPONSE_TYPE.DOCUMENT,
        htm: LoaderResource.XHR_RESPONSE_TYPE.DOCUMENT,
        xml: LoaderResource.XHR_RESPONSE_TYPE.DOCUMENT,
        tmx: LoaderResource.XHR_RESPONSE_TYPE.DOCUMENT,
        svg: LoaderResource.XHR_RESPONSE_TYPE.DOCUMENT,
        // This was added to handle Tiled Tileset XML, but .tsx is also a TypeScript React Component.
        // Since it is way less likely for people to be loading TypeScript files instead of Tiled files,
        // this should probably be fine.
        tsx: LoaderResource.XHR_RESPONSE_TYPE.DOCUMENT,
        // images
        gif: LoaderResource.XHR_RESPONSE_TYPE.BLOB,
        png: LoaderResource.XHR_RESPONSE_TYPE.BLOB,
        bmp: LoaderResource.XHR_RESPONSE_TYPE.BLOB,
        jpg: LoaderResource.XHR_RESPONSE_TYPE.BLOB,
        jpeg: LoaderResource.XHR_RESPONSE_TYPE.BLOB,
        tif: LoaderResource.XHR_RESPONSE_TYPE.BLOB,
        tiff: LoaderResource.XHR_RESPONSE_TYPE.BLOB,
        webp: LoaderResource.XHR_RESPONSE_TYPE.BLOB,
        tga: LoaderResource.XHR_RESPONSE_TYPE.BLOB,
        // json
        json: LoaderResource.XHR_RESPONSE_TYPE.JSON,
        // text
        text: LoaderResource.XHR_RESPONSE_TYPE.TEXT,
        txt: LoaderResource.XHR_RESPONSE_TYPE.TEXT,
        // fonts
        ttf: LoaderResource.XHR_RESPONSE_TYPE.BUFFER,
        otf: LoaderResource.XHR_RESPONSE_TYPE.BUFFER,
    };
    // We can't set the `src` attribute to empty string, so on abort we set it to this 1px transparent gif
    LoaderResource.EMPTY_GIF = 'data:image/gif;base64,R0lGODlhAQABAIAAAP///wAAACH5BAEAAAAALAAAAAABAAEAAAICRAEAOw==';
})(LoaderResource || (LoaderResource = {}));

/**
 * Smaller version of the async library constructs.
 * @ignore
 */
function _noop$1() {
}
/**
 * Ensures a function is only called once.
 * @ignore
 * @param {function} fn - The function to wrap.
 * @return {function} The wrapping function.
 */
function onlyOnce(fn) {
    return function onceWrapper() {
        var arguments$1 = arguments;

        var args = [];
        for (var _i = 0; _i < arguments.length; _i++) {
            args[_i] = arguments$1[_i];
        }
        if (fn === null) {
            throw new Error('Callback was already called.');
        }
        var callFn = fn;
        fn = null;
        callFn.apply(this, args);
    };
}
/**
 * @private
 * @memberof PIXI
 */
var AsyncQueueItem = /** @class */ (function () {
    /**
     * @private
     */
    function AsyncQueueItem(data, callback) {
        this.data = data;
        this.callback = callback;
    }
    return AsyncQueueItem;
}());
/**
 * @private
 * @memberof PIXI
 */
var AsyncQueue = /** @class */ (function () {
    /**
     * @private
     */
    function AsyncQueue(worker, concurrency) {
        var _this = this;
        if (concurrency === void 0) { concurrency = 1; }
        this.workers = 0;
        this.saturated = _noop$1;
        this.unsaturated = _noop$1;
        this.empty = _noop$1;
        this.drain = _noop$1;
        this.error = _noop$1;
        this.started = false;
        this.paused = false;
        this._tasks = [];
        this._insert = function (data, insertAtFront, callback) {
            if (callback && typeof callback !== 'function') {
                throw new Error('task callback must be a function');
            }
            _this.started = true;
            // eslint-disable-next-line no-eq-null,eqeqeq
            if (data == null && _this.idle()) {
                // call drain immediately if there are no tasks
                setTimeout(function () { return _this.drain(); }, 1);
                return;
            }
            var item = new AsyncQueueItem(data, typeof callback === 'function' ? callback : _noop$1);
            if (insertAtFront) {
                _this._tasks.unshift(item);
            }
            else {
                _this._tasks.push(item);
            }
            setTimeout(_this.process, 1);
        };
        this.process = function () {
            while (!_this.paused && _this.workers < _this.concurrency && _this._tasks.length) {
                var task = _this._tasks.shift();
                if (_this._tasks.length === 0) {
                    _this.empty();
                }
                _this.workers += 1;
                if (_this.workers === _this.concurrency) {
                    _this.saturated();
                }
                _this._worker(task.data, onlyOnce(_this._next(task)));
            }
        };
        this._worker = worker;
        if (concurrency === 0) {
            throw new Error('Concurrency must not be zero');
        }
        this.concurrency = concurrency;
        this.buffer = concurrency / 4.0;
    }
    /**
     * @private
     */
    AsyncQueue.prototype._next = function (task) {
        var _this = this;
        return function () {
            var arguments$1 = arguments;

            var args = [];
            for (var _i = 0; _i < arguments.length; _i++) {
                args[_i] = arguments$1[_i];
            }
            _this.workers -= 1;
            task.callback.apply(task, args);
            // eslint-disable-next-line no-eq-null,eqeqeq
            if (args[0] != null) {
                _this.error(args[0], task.data);
            }
            if (_this.workers <= (_this.concurrency - _this.buffer)) {
                _this.unsaturated();
            }
            if (_this.idle()) {
                _this.drain();
            }
            _this.process();
        };
    };
    // That was in object
    // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
    AsyncQueue.prototype.push = function (data, callback) {
        this._insert(data, false, callback);
    };
    AsyncQueue.prototype.kill = function () {
        this.workers = 0;
        this.drain = _noop$1;
        this.started = false;
        this._tasks = [];
    };
    // eslint-disable-next-line @typescript-eslint/explicit-module-boundary-types
    AsyncQueue.prototype.unshift = function (data, callback) {
        this._insert(data, true, callback);
    };
    AsyncQueue.prototype.length = function () {
        return this._tasks.length;
    };
    AsyncQueue.prototype.running = function () {
        return this.workers;
    };
    AsyncQueue.prototype.idle = function () {
        return this._tasks.length + this.workers === 0;
    };
    AsyncQueue.prototype.pause = function () {
        if (this.paused === true) {
            return;
        }
        this.paused = true;
    };
    AsyncQueue.prototype.resume = function () {
        if (this.paused === false) {
            return;
        }
        this.paused = false;
        // Need to call this.process once per concurrent
        // worker to preserve full concurrency after pause
        for (var w = 1; w <= this.concurrency; w++) {
            this.process();
        }
    };
    /**
     * Iterates an array in series.
     *
     * @param {Array.<*>} array - Array to iterate.
     * @param {function} iterator - Function to call for each element.
     * @param {function} callback - Function to call when done, or on error.
     * @param {boolean} [deferNext=false] - Break synchronous each loop by calling next with a setTimeout of 1.
     */
    AsyncQueue.eachSeries = function (array, iterator, callback, deferNext) {
        var i = 0;
        var len = array.length;
        function next(err) {
            if (err || i === len) {
                if (callback) {
                    callback(err);
                }
                return;
            }
            if (deferNext) {
                setTimeout(function () {
                    iterator(array[i++], next);
                }, 1);
            }
            else {
                iterator(array[i++], next);
            }
        }
        next();
    };
    /**
     * Async queue implementation,
     *
     * @param {function} worker - The worker function to call for each task.
     * @param {number} concurrency - How many workers to run in parrallel.
     * @return {*} The async queue object.
     */
    AsyncQueue.queue = function (worker, concurrency) {
        return new AsyncQueue(worker, concurrency);
    };
    return AsyncQueue;
}());

// some constants
var MAX_PROGRESS = 100;
var rgxExtractUrlHash = /(#[\w-]+)?$/;
/**
 * The new loader, forked from Resource Loader by Chad Engler: https://github.com/englercj/resource-loader
 *
 * ```js
 * const loader = PIXI.Loader.shared; // PixiJS exposes a premade instance for you to use.
 * // or
 * const loader = new PIXI.Loader(); // You can also create your own if you want
 *
 * const sprites = {};
 *
 * // Chainable `add` to enqueue a resource
 * loader.add('bunny', 'data/bunny.png')
 *       .add('spaceship', 'assets/spritesheet.json');
 * loader.add('scoreFont', 'assets/score.fnt');
 *
 * // Chainable `pre` to add a middleware that runs for each resource, *before* loading that resource.
 * // This is useful to implement custom caching modules (using filesystem, indexeddb, memory, etc).
 * loader.pre(cachingMiddleware);
 *
 * // Chainable `use` to add a middleware that runs for each resource, *after* loading that resource.
 * // This is useful to implement custom parsing modules (like spritesheet parsers, spine parser, etc).
 * loader.use(parsingMiddleware);
 *
 * // The `load` method loads the queue of resources, and calls the passed in callback called once all
 * // resources have loaded.
 * loader.load((loader, resources) => {
 *     // resources is an object where the key is the name of the resource loaded and the value is the resource object.
 *     // They have a couple default properties:
 *     // - `url`: The URL that the resource was loaded from
 *     // - `error`: The error that happened when trying to load (if any)
 *     // - `data`: The raw data that was loaded
 *     // also may contain other properties based on the middleware that runs.
 *     sprites.bunny = new PIXI.TilingSprite(resources.bunny.texture);
 *     sprites.spaceship = new PIXI.TilingSprite(resources.spaceship.texture);
 *     sprites.scoreFont = new PIXI.TilingSprite(resources.scoreFont.texture);
 * });
 *
 * // throughout the process multiple signals can be dispatched.
 * loader.onProgress.add(() => {}); // called once per loaded/errored file
 * loader.onError.add(() => {}); // called once per errored file
 * loader.onLoad.add(() => {}); // called once per loaded file
 * loader.onComplete.add(() => {}); // called once when the queued resources all load.
 * ```
 *
 * @memberof PIXI
 */
var Loader = /** @class */ (function () {
    /**
     * @param baseUrl - The base url for all resources loaded by this loader.
     * @param concurrency - The number of resources to load concurrently.
     */
    function Loader(baseUrl, concurrency) {
        var _this = this;
        if (baseUrl === void 0) { baseUrl = ''; }
        if (concurrency === void 0) { concurrency = 10; }
        /**
         * The progress percent of the loader going through the queue.
         */
        this.progress = 0;
        /**
         * Loading state of the loader, true if it is currently loading resources.
         */
        this.loading = false;
        /**
         * A querystring to append to every URL added to the loader.
         *
         * This should be a valid query string *without* the question-mark (`?`). The loader will
         * also *not* escape values for you. Make sure to escape your parameters with
         * [`encodeURIComponent`](https://mdn.io/encodeURIComponent) before assigning this property.
         *
         * @example
         * const loader = new Loader();
         *
         * loader.defaultQueryString = 'user=me&password=secret';
         *
         * // This will request 'image.png?user=me&password=secret'
         * loader.add('image.png').load();
         *
         * loader.reset();
         *
         * // This will request 'image.png?v=1&user=me&password=secret'
         * loader.add('iamge.png?v=1').load();
         */
        this.defaultQueryString = '';
        /**
         * The middleware to run before loading each resource.
         */
        this._beforeMiddleware = [];
        /**
         * The middleware to run after loading each resource.
         */
        this._afterMiddleware = [];
        /**
         * The tracks the resources we are currently completing parsing for.
         */
        this._resourcesParsing = [];
        /**
         * The `_loadResource` function bound with this object context.
         *
         * @param r - The resource to load
         * @param d - The dequeue function
         */
        this._boundLoadResource = function (r, d) { return _this._loadResource(r, d); };
        /**
         * All the resources for this loader keyed by name.
         */
        this.resources = {};
        this.baseUrl = baseUrl;
        this._beforeMiddleware = [];
        this._afterMiddleware = [];
        this._resourcesParsing = [];
        this._boundLoadResource = function (r, d) { return _this._loadResource(r, d); };
        this._queue = AsyncQueue.queue(this._boundLoadResource, concurrency);
        this._queue.pause();
        this.resources = {};
        this.onProgress = new Signal();
        this.onError = new Signal();
        this.onLoad = new Signal();
        this.onStart = new Signal();
        this.onComplete = new Signal();
        for (var i = 0; i < Loader._plugins.length; ++i) {
            var plugin = Loader._plugins[i];
            var pre = plugin.pre, use = plugin.use;
            if (pre) {
                this.pre(pre);
            }
            if (use) {
                this.use(use);
            }
        }
        this._protected = false;
    }
    /**
     * Same as add, params have strict order
     *
     * @private
     * @param name - The name of the resource to load.
     * @param url - The url for this resource, relative to the baseUrl of this loader.
     * @param options - The options for the load.
     * @param callback - Function to call when this specific resource completes loading.
     * @return The loader itself.
     */
    Loader.prototype._add = function (name, url, options, callback) {
        // if loading already you can only add resources that have a parent.
        if (this.loading && (!options || !options.parentResource)) {
            throw new Error('Cannot add resources while the loader is running.');
        }
        // check if resource already exists.
        if (this.resources[name]) {
            throw new Error("Resource named \"" + name + "\" already exists.");
        }
        // add base url if this isn't an absolute url
        url = this._prepareUrl(url);
        // create the store the resource
        this.resources[name] = new LoaderResource(name, url, options);
        if (typeof callback === 'function') {
            this.resources[name].onAfterMiddleware.once(callback);
        }
        // if actively loading, make sure to adjust progress chunks for that parent and its children
        if (this.loading) {
            var parent = options.parentResource;
            var incompleteChildren = [];
            for (var i = 0; i < parent.children.length; ++i) {
                if (!parent.children[i].isComplete) {
                    incompleteChildren.push(parent.children[i]);
                }
            }
            var fullChunk = parent.progressChunk * (incompleteChildren.length + 1); // +1 for parent
            var eachChunk = fullChunk / (incompleteChildren.length + 2); // +2 for parent & new child
            parent.children.push(this.resources[name]);
            parent.progressChunk = eachChunk;
            for (var i = 0; i < incompleteChildren.length; ++i) {
                incompleteChildren[i].progressChunk = eachChunk;
            }
            this.resources[name].progressChunk = eachChunk;
        }
        // add the resource to the queue
        this._queue.push(this.resources[name]);
        return this;
    };
    /* eslint-enable require-jsdoc,valid-jsdoc */
    /**
     * Sets up a middleware function that will run *before* the
     * resource is loaded.
     *
     * @param fn - The middleware function to register.
     * @return The loader itself.
     */
    Loader.prototype.pre = function (fn) {
        this._beforeMiddleware.push(fn);
        return this;
    };
    /**
     * Sets up a middleware function that will run *after* the
     * resource is loaded.
     *
     * @param fn - The middleware function to register.
     * @return The loader itself.
     */
    Loader.prototype.use = function (fn) {
        this._afterMiddleware.push(fn);
        return this;
    };
    /**
     * Resets the queue of the loader to prepare for a new load.
     *
     * @return The loader itself.
     */
    Loader.prototype.reset = function () {
        this.progress = 0;
        this.loading = false;
        this._queue.kill();
        this._queue.pause();
        // abort all resource loads
        for (var k in this.resources) {
            var res = this.resources[k];
            if (res._onLoadBinding) {
                res._onLoadBinding.detach();
            }
            if (res.isLoading) {
                res.abort('loader reset');
            }
        }
        this.resources = {};
        return this;
    };
    /**
     * Starts loading the queued resources.
     * @param cb - Optional callback that will be bound to the `complete` event.
     * @return The loader itself.
     */
    Loader.prototype.load = function (cb) {
        // register complete callback if they pass one
        if (typeof cb === 'function') {
            this.onComplete.once(cb);
        }
        // if the queue has already started we are done here
        if (this.loading) {
            return this;
        }
        if (this._queue.idle()) {
            this._onStart();
            this._onComplete();
        }
        else {
            // distribute progress chunks
            var numTasks = this._queue._tasks.length;
            var chunk = MAX_PROGRESS / numTasks;
            for (var i = 0; i < this._queue._tasks.length; ++i) {
                this._queue._tasks[i].data.progressChunk = chunk;
            }
            // notify we are starting
            this._onStart();
            // start loading
            this._queue.resume();
        }
        return this;
    };
    Object.defineProperty(Loader.prototype, "concurrency", {
        /**
         * The number of resources to load concurrently.
         *
         * @default 10
         */
        get: function () {
            return this._queue.concurrency;
        },
        set: function (concurrency) {
            this._queue.concurrency = concurrency;
        },
        enumerable: false,
        configurable: true
    });
    /**
     * Prepares a url for usage based on the configuration of this object
     * @param url - The url to prepare.
     * @return The prepared url.
     */
    Loader.prototype._prepareUrl = function (url) {
        var parsedUrl = parseUri(url, { strictMode: true });
        var result;
        // absolute url, just use it as is.
        if (parsedUrl.protocol || !parsedUrl.path || url.indexOf('//') === 0) {
            result = url;
        }
        // if baseUrl doesn't end in slash and url doesn't start with slash, then add a slash inbetween
        else if (this.baseUrl.length
            && this.baseUrl.lastIndexOf('/') !== this.baseUrl.length - 1
            && url.charAt(0) !== '/') {
            result = this.baseUrl + "/" + url;
        }
        else {
            result = this.baseUrl + url;
        }
        // if we need to add a default querystring, there is a bit more work
        if (this.defaultQueryString) {
            var hash = rgxExtractUrlHash.exec(result)[0];
            result = result.slice(0, result.length - hash.length);
            if (result.indexOf('?') !== -1) {
                result += "&" + this.defaultQueryString;
            }
            else {
                result += "?" + this.defaultQueryString;
            }
            result += hash;
        }
        return result;
    };
    /**
     * Loads a single resource.
     *
     * @param resource - The resource to load.
     * @param dequeue - The function to call when we need to dequeue this item.
     */
    Loader.prototype._loadResource = function (resource, dequeue) {
        var _this = this;
        resource._dequeue = dequeue;
        // run before middleware
        AsyncQueue.eachSeries(this._beforeMiddleware, function (fn, next) {
            fn.call(_this, resource, function () {
                // if the before middleware marks the resource as complete,
                // break and don't process any more before middleware
                next(resource.isComplete ? {} : null);
            });
        }, function () {
            if (resource.isComplete) {
                _this._onLoad(resource);
            }
            else {
                resource._onLoadBinding = resource.onComplete.once(_this._onLoad, _this);
                resource.load();
            }
        }, true);
    };
    /**
     * Called once loading has started.
     */
    Loader.prototype._onStart = function () {
        this.progress = 0;
        this.loading = true;
        this.onStart.dispatch(this);
    };
    /**
     * Called once each resource has loaded.
     */
    Loader.prototype._onComplete = function () {
        this.progress = MAX_PROGRESS;
        this.loading = false;
        this.onComplete.dispatch(this, this.resources);
    };
    /**
     * Called each time a resources is loaded.
     * @param resource - The resource that was loaded
     */
    Loader.prototype._onLoad = function (resource) {
        var _this = this;
        resource._onLoadBinding = null;
        // remove this resource from the async queue, and add it to our list of resources that are being parsed
        this._resourcesParsing.push(resource);
        resource._dequeue();
        // run all the after middleware for this resource
        AsyncQueue.eachSeries(this._afterMiddleware, function (fn, next) {
            fn.call(_this, resource, next);
        }, function () {
            resource.onAfterMiddleware.dispatch(resource);
            _this.progress = Math.min(MAX_PROGRESS, _this.progress + resource.progressChunk);
            _this.onProgress.dispatch(_this, resource);
            if (resource.error) {
                _this.onError.dispatch(resource.error, _this, resource);
            }
            else {
                _this.onLoad.dispatch(_this, resource);
            }
            _this._resourcesParsing.splice(_this._resourcesParsing.indexOf(resource), 1);
            // do completion check
            if (_this._queue.idle() && _this._resourcesParsing.length === 0) {
                _this._onComplete();
            }
        }, true);
    };
    /**
     * Destroy the loader, removes references.
     */
    Loader.prototype.destroy = function () {
        if (!this._protected) {
            this.reset();
        }
    };
    Object.defineProperty(Loader, "shared", {
        /**
         * A premade instance of the loader that can be used to load resources.
         */
        get: function () {
            var shared = Loader._shared;
            if (!shared) {
                shared = new Loader();
                shared._protected = true;
                Loader._shared = shared;
            }
            return shared;
        },
        enumerable: false,
        configurable: true
    });
    /**
     * Adds a Loader plugin for the global shared loader and all
     * new Loader instances created.
     *
     * @param plugin - The plugin to add
     * @return Reference to PIXI.Loader for chaining
     */
    Loader.registerPlugin = function (plugin) {
        Loader._plugins.push(plugin);
        if (plugin.add) {
            plugin.add();
        }
        return Loader;
    };
    Loader._plugins = [];
    return Loader;
}());
Loader.prototype.add = function add(name, url, options, callback) {
    // special case of an array of objects or urls
    if (Array.isArray(name)) {
        for (var i = 0; i < name.length; ++i) {
            this.add(name[i]);
        }
        return this;
    }
    // if an object is passed instead of params
    if (typeof name === 'object') {
        options = name;
        callback = url || options.callback || options.onComplete;
        url = options.url;
        name = options.name || options.key || options.url;
    }
    // case where no name is passed shift all args over by one.
    if (typeof url !== 'string') {
        callback = options;
        options = url;
        url = name;
    }
    // now that we shifted make sure we have a proper url.
    if (typeof url !== 'string') {
        throw new Error('No url passed to add resource to loader.');
    }
    // options are optional so people might pass a function and no options
    if (typeof options === 'function') {
        callback = options;
        options = null;
    }
    return this._add(name, url, options, callback);
};

/**
 * Application plugin for supporting loader option. Installing the LoaderPlugin
 * is not necessary if using **pixi.js** or **pixi.js-legacy**.
 * @example
 * import {AppLoaderPlugin} from '@pixi/loaders';
 * import {Application} from '@pixi/app';
 * Application.registerPlugin(AppLoaderPlugin);
 * @memberof PIXI
 */
var AppLoaderPlugin = /** @class */ (function () {
    function AppLoaderPlugin() {
    }
    /**
     * Called on application constructor
     *
     * @private
     */
    AppLoaderPlugin.init = function (options) {
        options = Object.assign({
            sharedLoader: false,
        }, options);
        this.loader = options.sharedLoader ? Loader.shared : new Loader();
    };
    /**
     * Called when application destroyed
     *
     * @private
     */
    AppLoaderPlugin.destroy = function () {
        if (this.loader) {
            this.loader.destroy();
            this.loader = null;
        }
    };
    return AppLoaderPlugin;
}());

/**
 * Loader plugin for handling Texture resources.
 *
 * @memberof PIXI
 */
var TextureLoader = /** @class */ (function () {
    function TextureLoader() {
    }
    /**
     * Handle SVG elements a text, render with SVGResource.
     */
    TextureLoader.add = function () {
        LoaderResource.setExtensionLoadType('svg', LoaderResource.LOAD_TYPE.XHR);
        LoaderResource.setExtensionXhrType('svg', LoaderResource.XHR_RESPONSE_TYPE.TEXT);
    };
    /**
     * Called after a resource is loaded.
     * @see PIXI.Loader.loaderMiddleware
     * @param resource
     * @param {function} next
     */
    TextureLoader.use = function (resource, next) {
        // create a new texture if the data is an Image object
        if (resource.data && (resource.type === LoaderResource.TYPE.IMAGE || resource.extension === 'svg')) {
            var data = resource.data, url = resource.url, name = resource.name, metadata = resource.metadata;
            Texture.fromLoader(data, url, name, metadata).then(function (texture) {
                resource.texture = texture;
                next();
            })
                // TODO: handle errors in Texture.fromLoader
                // so we can pass them to the Loader
                .catch(next);
        }
        else {
            next();
        }
    };
    return TextureLoader;
}());

var _keyStr = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
/**
 * Encodes binary into base64.
 *
 * @function encodeBinary
 * @param {string} input - The input data to encode.
 * @returns {string} The encoded base64 string
 */
function encodeBinary(input) {
    var output = '';
    var inx = 0;
    while (inx < input.length) {
        // Fill byte buffer array
        var bytebuffer = [0, 0, 0];
        var encodedCharIndexes = [0, 0, 0, 0];
        for (var jnx = 0; jnx < bytebuffer.length; ++jnx) {
            if (inx < input.length) {
                // throw away high-order byte, as documented at:
                // https://developer.mozilla.org/En/Using_XMLHttpRequest#Handling_binary_data
                bytebuffer[jnx] = input.charCodeAt(inx++) & 0xff;
            }
            else {
                bytebuffer[jnx] = 0;
            }
        }
        // Get each encoded character, 6 bits at a time
        // index 1: first 6 bits
        encodedCharIndexes[0] = bytebuffer[0] >> 2;
        // index 2: second 6 bits (2 least significant bits from input byte 1 + 4 most significant bits from byte 2)
        encodedCharIndexes[1] = ((bytebuffer[0] & 0x3) << 4) | (bytebuffer[1] >> 4);
        // index 3: third 6 bits (4 least significant bits from input byte 2 + 2 most significant bits from byte 3)
        encodedCharIndexes[2] = ((bytebuffer[1] & 0x0f) << 2) | (bytebuffer[2] >> 6);
        // index 3: forth 6 bits (6 least significant bits from input byte 3)
        encodedCharIndexes[3] = bytebuffer[2] & 0x3f;
        // Determine whether padding happened, and adjust accordingly
        var paddingBytes = inx - (input.length - 1);
        switch (paddingBytes) {
            case 2:
                // Set last 2 characters to padding char
                encodedCharIndexes[3] = 64;
                encodedCharIndexes[2] = 64;
                break;
            case 1:
                // Set last character to padding char
                encodedCharIndexes[3] = 64;
                break;
        }
        // Now we will grab each appropriate character out of our keystring
        // based on our index array and append it to the output string
        for (var jnx = 0; jnx < encodedCharIndexes.length; ++jnx) {
            output += _keyStr.charAt(encodedCharIndexes[jnx]);
        }
    }
    return output;
}

var Url = self.URL || self.webkitURL;
/**
 * A middleware for transforming XHR loaded Blobs into more useful objects
 *
 * @ignore
 * @function parsing
 * @example
 * import { Loader, middleware } from 'resource-loader';
 * const loader = new Loader();
 * loader.use(middleware.parsing);
 * @param resource - Current Resource
 * @param next - Callback when complete
 */
function parsing(resource, next) {
    if (!resource.data) {
        next();
        return;
    }
    // if this was an XHR load of a blob
    if (resource.xhr && resource.xhrType === LoaderResource.XHR_RESPONSE_TYPE.BLOB) {
        // if there is no blob support we probably got a binary string back
        if (!self.Blob || typeof resource.data === 'string') {
            var type = resource.xhr.getResponseHeader('content-type');
            // this is an image, convert the binary string into a data url
            if (type && type.indexOf('image') === 0) {
                resource.data = new Image();
                resource.data.src = "data:" + type + ";base64," + encodeBinary(resource.xhr.responseText);
                resource.type = LoaderResource.TYPE.IMAGE;
                // wait until the image loads and then callback
                resource.data.onload = function () {
                    resource.data.onload = null;
                    next();
                };
                // next will be called on load
                return;
            }
        }
        // if content type says this is an image, then we should transform the blob into an Image object
        else if (resource.data.type.indexOf('image') === 0) {
            var src_1 = Url.createObjectURL(resource.data);
            resource.blob = resource.data;
            resource.data = new Image();
            resource.data.src = src_1;
            resource.type = LoaderResource.TYPE.IMAGE;
            // cleanup the no longer used blob after the image loads
            // TODO: Is this correct? Will the image be invalid after revoking?
            resource.data.onload = function () {
                Url.revokeObjectURL(src_1);
                resource.data.onload = null;
                next();
            };
            // next will be called on load.
            return;
        }
    }
    next();
}

// parse any blob into more usable objects (e.g. Image)
Loader.registerPlugin({ use: parsing });
// parse any Image objects into textures
Loader.registerPlugin(TextureLoader);

export { AppLoaderPlugin, Loader, LoaderResource, TextureLoader };
//# sourceMappingURL=loaders.js.map
