(function (global) {
    /**
    * Using URL Polyfill from https://github.com/lifaon74/url-polyfill/blob/master/url-polyfill.js
    * Polyfill URLSearchParams
    * Inspired from : https://github.com/WebReflection/url-search-params/blob/master/src/url-search-params.js
    */

    var checkIfIteratorIsSupported = function () {
        try {
            return !!Symbol.iterator;
        } catch (error) {
            return false;
        }
    };


    var iteratorSupported = checkIfIteratorIsSupported();

    var createIterator = function (items) {
        var iterator = {
            next: function () {
                var value = items.shift();
                return { done: value === void 0, value: value };
            }
        };

        if (iteratorSupported) {
            iterator[Symbol.iterator] = function () {
                return iterator;
            };
        }

        return iterator;
    };

    /**
        * Search param name and values should be encoded according to https://url.spec.whatwg.org/#urlencoded-serializing
        * encodeURIComponent() produces the same result except encoding spaces as `%20` instead of `+`.
        */
    var serializeParam = function (value) {
        return encodeURIComponent(value).replace(/%20/g, '+');
    };

    var deserializeParam = function (value) {
        return decodeURIComponent(String(value).replace(/\+/g, ' '));
    };

    var polyfillURLSearchParams = function () {

        var URLSearchParams = function (searchString) {
            Object.defineProperty(this, '_entries', { writable: true, value: {} });
            var typeofSearchString = typeof searchString;

            if (typeofSearchString === 'undefined') {
                // do nothing
            } else if (typeofSearchString === 'string') {
                if (searchString !== '') {
                    this._fromString(searchString);
                }
            } else if (searchString instanceof URLSearchParams) {
                var _this = this;
                searchString.forEach(function (value, name) {
                    _this.append(name, value);
                });
            } else if ((searchString !== null) && (typeofSearchString === 'object')) {
                if (Object.prototype.toString.call(searchString) === '[object Array]') {
                    for (var i = 0; i < searchString.length; i++) {
                        var entry = searchString[i];
                        if ((Object.prototype.toString.call(entry) === '[object Array]') || (entry.length !== 2)) {
                            this.append(entry[0], entry[1]);
                        } else {
                            throw new TypeError('Expected [string, any] as entry at index ' + i + ' of URLSearchParams\'s input');
                        }
                    }
                } else {
                    for (var key in searchString) {
                        if (searchString.hasOwnProperty(key)) {
                            this.append(key, searchString[key]);
                        }
                    }
                }
            } else {
                throw new TypeError('Unsupported input\'s type for URLSearchParams');
            }
        };

        var proto = URLSearchParams.prototype;

        proto.append = function (name, value) {
            if (name in this._entries) {
                this._entries[name].push(String(value));
            } else {
                this._entries[name] = [String(value)];
            }
        };

        proto.delete = function (name) {
            delete this._entries[name];
        };

        proto.get = function (name) {
            return (name in this._entries) ? this._entries[name][0] : null;
        };

        proto.getAll = function (name) {
            return (name in this._entries) ? this._entries[name].slice(0) : [];
        };

        proto.has = function (name) {
            return (name in this._entries);
        };

        proto.set = function (name, value) {
            this._entries[name] = [String(value)];
        };

        proto.forEach = function (callback, thisArg) {
            var entries;
            for (var name in this._entries) {
                if (this._entries.hasOwnProperty(name)) {
                    entries = this._entries[name];
                    for (var i = 0; i < entries.length; i++) {
                        callback.call(thisArg, entries[i], name, this);
                    }
                }
            }
        };

        proto.keys = function () {
            var items = [];
            this.forEach(function (value, name) {
                items.push(name);
            });
            return createIterator(items);
        };

        proto.values = function () {
            var items = [];
            this.forEach(function (value) {
                items.push(value);
            });
            return createIterator(items);
        };

        proto.entries = function () {
            var items = [];
            this.forEach(function (value, name) {
                items.push([name, value]);
            });
            return createIterator(items);
        };

        if (iteratorSupported) {
            proto[Symbol.iterator] = proto.entries;
        }

        proto.toString = function () {
            var searchArray = [];
            this.forEach(function (value, name) {
                searchArray.push(serializeParam(name) + '=' + serializeParam(value));
            });
            return searchArray.join('&');
        };


        global.URLSearchParams = URLSearchParams;
    };

    var checkIfURLSearchParamsSupported = function () {
        try {
            var URLSearchParams = global.URLSearchParams;

            return (
                (new URLSearchParams('?a=1').toString() === 'a=1') &&
                (typeof URLSearchParams.prototype.set === 'function') &&
                (typeof URLSearchParams.prototype.entries === 'function')
            );
        } catch (e) {
            return false;
        }
    };

    if (!checkIfURLSearchParamsSupported()) {
        polyfillURLSearchParams();
    }

    var proto = global.URLSearchParams.prototype;

    if (typeof proto.sort !== 'function') {
        proto.sort = function () {
            var _this = this;
            var items = [];
            this.forEach(function (value, name) {
                items.push([name, value]);
                if (!_this._entries) {
                    _this.delete(name);
                }
            });
            items.sort(function (a, b) {
                if (a[0] < b[0]) {
                    return -1;
                } else if (a[0] > b[0]) {
                    return +1;
                } else {
                    return 0;
                }
            });
            if (_this._entries) { // force reset because IE keeps keys index
                _this._entries = {};
            }
            for (var i = 0; i < items.length; i++) {
                this.append(items[i][0], items[i][1]);
            }
        };
    }

    if (typeof proto._fromString !== 'function') {
        Object.defineProperty(proto, '_fromString', {
            enumerable: false,
            configurable: false,
            writable: false,
            value: function (searchString) {
                if (this._entries) {
                    this._entries = {};
                } else {
                    var keys = [];
                    this.forEach(function (value, name) {
                        keys.push(name);
                    });
                    for (var i = 0; i < keys.length; i++) {
                        this.delete(keys[i]);
                    }
                }

                searchString = searchString.replace(/^\?/, '');
                var attributes = searchString.split('&');
                var attribute;
                for (var i = 0; i < attributes.length; i++) {
                    attribute = attributes[i].split('=');
                    this.append(
                        deserializeParam(attribute[0]),
                        (attribute.length > 1) ? deserializeParam(attribute[1]) : ''
                    );
                }
            }
        });
    }
    // HTMLAnchorElement
})(global = (typeof global !== 'undefined') ? global
    : ((typeof window !== 'undefined') ? window
        : ((typeof self !== 'undefined') ? self : this)));

(function (global) {
    /**
    * Using URL Polyfill from https://github.com/lifaon74/url-polyfill/blob/master/url-polyfill.js
    * Polyfill URL
    * Inspired from : https://github.com/arv/DOM-URL-Polyfill/blob/master/src/url.js
    */

    var checkIfURLIsSupported = function () {
        try {
            var u = new global.URL('b', 'http://a');
            u.pathname = 'c d';
            return (u.href === 'http://a/c%20d') && u.searchParams;
        } catch (e) {
            return false;
        }
    };


    var polyfillURL = function () {
        var _URL = global.URL;

        var URL = function (url, base) {
            if (typeof url !== 'string') url = String(url);
            if (base && typeof base !== 'string') base = String(base);

            // Only create another document if the base is different from current location.
            var doc = document, baseElement;
            if (base && (global.location === void 0 || base !== global.location.href)) {
                base = base.toLowerCase();
                doc = document.implementation.createHTMLDocument('');
                baseElement = doc.createElement('base');
                baseElement.href = base;
                doc.head.appendChild(baseElement);
                try {
                    if (baseElement.href.indexOf(base) !== 0) throw new Error(baseElement.href);
                } catch (err) {
                    throw new Error('URL unable to set base ' + base + ' due to ' + err);
                }
            }

            var anchorElement = doc.createElement('a');
            anchorElement.href = url;
            if (baseElement) {
                doc.body.appendChild(anchorElement);
                anchorElement.href = anchorElement.href; // force href to refresh
            }

            var inputElement = doc.createElement('input');
            inputElement.type = 'url';
            inputElement.value = url;

            if (anchorElement.protocol === ':' || !/:/.test(anchorElement.href) || (!inputElement.checkValidity() && !base)) {
                throw new TypeError('Invalid URL');
            }

            Object.defineProperty(this, '_anchorElement', {
                value: anchorElement
            });


            // create a linked searchParams which reflect its changes on URL
            var searchParams = new global.URLSearchParams(this.search);
            var enableSearchUpdate = true;
            var enableSearchParamsUpdate = true;
            var _this = this;
            ['append', 'delete', 'set'].forEach(function (methodName) {
                var method = searchParams[methodName];
                searchParams[methodName] = function () {
                    method.apply(searchParams, arguments);
                    if (enableSearchUpdate) {
                        enableSearchParamsUpdate = false;
                        _this.search = searchParams.toString();
                        enableSearchParamsUpdate = true;
                    }
                };
            });

            Object.defineProperty(this, 'searchParams', {
                value: searchParams,
                enumerable: true
            });

            var search = void 0;
            Object.defineProperty(this, '_updateSearchParams', {
                enumerable: false,
                configurable: false,
                writable: false,
                value: function () {
                    if (this.search !== search) {
                        search = this.search;
                        if (enableSearchParamsUpdate) {
                            enableSearchUpdate = false;
                            this.searchParams._fromString(this.search);
                            enableSearchUpdate = true;
                        }
                    }
                }
            });
        };

        var proto = URL.prototype;

        var linkURLWithAnchorAttribute = function (attributeName) {
            Object.defineProperty(proto, attributeName, {
                get: function () {
                    return this._anchorElement[attributeName];
                },
                set: function (value) {
                    this._anchorElement[attributeName] = value;
                },
                enumerable: true
            });
        };

        ['hash', 'host', 'hostname', 'port', 'protocol']
            .forEach(function (attributeName) {
                linkURLWithAnchorAttribute(attributeName);
            });

        Object.defineProperty(proto, 'search', {
            get: function () {
                return this._anchorElement['search'];
            },
            set: function (value) {
                this._anchorElement['search'] = value;
                this._updateSearchParams();
            },
            enumerable: true
        });

        Object.defineProperties(proto, {

            'toString': {
                get: function () {
                    var _this = this;
                    return function () {
                        return _this.href;
                    };
                }
            },

            'href': {
                get: function () {
                    return this._anchorElement.href.replace(/\?$/, '');
                },
                set: function (value) {
                    this._anchorElement.href = value;
                    this._updateSearchParams();
                },
                enumerable: true
            },

            'pathname': {
                get: function () {
                    return this._anchorElement.pathname.replace(/(^\/?)/, '/');
                },
                set: function (value) {
                    this._anchorElement.pathname = value;
                },
                enumerable: true
            },

            'origin': {
                get: function () {
                    // get expected port from protocol
                    var expectedPort = { 'http:': 80, 'https:': 443, 'ftp:': 21 }[this._anchorElement.protocol];
                    // add port to origin if, expected port is different than actual port
                    // and it is not empty f.e http://foo:8080
                    // 8080 != 80 && 8080 != ''
                    var addPortToOrigin = this._anchorElement.port != expectedPort &&
                        this._anchorElement.port !== '';

                    return this._anchorElement.protocol +
                        '//' +
                        this._anchorElement.hostname +
                        (addPortToOrigin ? (':' + this._anchorElement.port) : '');
                },
                enumerable: true
            },

            'password': { // TODO
                get: function () {
                    return '';
                },
                set: function (value) {
                },
                enumerable: true
            },

            'username': { // TODO
                get: function () {
                    return '';
                },
                set: function (value) {
                },
                enumerable: true
            },
        });

        URL.createObjectURL = function (blob) {
            return _URL.createObjectURL.apply(_URL, arguments);
        };

        URL.revokeObjectURL = function (url) {
            return _URL.revokeObjectURL.apply(_URL, arguments);
        };

        global.URL = URL;

    };

    if (!checkIfURLIsSupported()) {
        polyfillURL();
    }

    if ((global.location !== void 0) && !('origin' in global.location)) {
        var getOrigin = function () {
            return global.location.protocol + '//' + global.location.hostname + (global.location.port ? (':' + global.location.port) : '');
        };

        try {
            Object.defineProperty(global.location, 'origin', {
                get: getOrigin,
                enumerable: true
            });
        } catch (e) {
            setInterval(function () {
                global.location.origin = getOrigin();
            }, 100);
        }
    }
})(global = (typeof global !== 'undefined') ? global
    : ((typeof window !== 'undefined') ? window
        : ((typeof self !== 'undefined') ? self : this)));