import { extendPrototype } from "@zap/utils/lib/ExtensionHelpers";
import { polyfill as promise_polyfill } from "es6-promise";
import { default as symbol_polyfill } from "es6-Symbol";

if (typeof Promise === "undefined") {
    promise_polyfill();
}

if (!window.Symbol) {
    window.Symbol = symbol_polyfill;
}

if (!Function.prototype.bind) {
    Function.prototype.bind = function (oThis) {
        if (typeof this !== "function") {
            // closest thing possible to the ECMAScript 5 internal IsCallable function
            throw new TypeError("Function.prototype.bind - what is trying to be bound is not callable");
        }

        var aArgs = Array.prototype.slice.call(arguments, 1),
            fToBind = this,
            fNOP = function () { },
            fBound = function () {
                return fToBind.apply((this && this instanceof fNOP)
                    ? this
                    : oThis || window,
                    aArgs.concat(Array.prototype.slice.call(arguments)));
            };

        fNOP.prototype = this.prototype || {};
        fBound.prototype = new fNOP();

        return fBound;
    };
};

if (!window.Map || !window.Map.prototype.values) {

    window.Map = class Map {
        constructor(entries) {
            this._keys = (entries || []).map(e => e[0]);
            this._values = (entries || []).map(e => e[1]);
        }

        clear() {
            this._keys = [];
            this._values = [];
        }

        delete(key) {
            var i = this._keys.indexOf(key);
            if (i >= 0) {
                this._keys.splice(i, 1);
                this._values.splice(i, 1);
                return true;
            }
            return false;
        }

        entries() {
            return this._keys.map((k, i) => [k, this._values[i]]);
        }

        forEach(callback, thisArg) {
            this._keys.forEach((k, i) => callback.call(thisArg, this._values[i], k, this));
        }

        get(key) {
            var i = this._keys.indexOf(key);
            if (i >= 0)
                return this._values[i];
        }

        has(key) {
            return this._keys.indexOf(key) >= 0;
        }

        keys() {
            return this._keys.slice();
        }

        set(key, value) {
            var i = this._keys.indexOf(key);
            if (i >= 0) {
                this._values[i] = value;
            } else {
                this._keys.push(key);
                this._values.push(value);
            }
            return this;
        }

        get size() { return this._keys.length; }

        values() {
            return this._values.slice();
        }

        /** To support Array.from */
        get iterator() { return this.entries(); }
    }
}

if (!window.Set || !window.Set.prototype.entries) {
    window.Set = class Set {
        constructor(iterable) {
            this._items = [];
            (iterable || []).forEach(i => this.add(i));
        }

        add(item) {
            if (!this.has(item))
                this._items.push(item);
            return this;
        }

        clear() {
            this._items = [];
        }

        delete(item) {
            var i = this._items.indexOf(item);
            if (i >= 0) {
                this._items.splice(i, 1);
                return true;
            }
            return false;
        }

        entries() {
            return this._items.map(i => [i, i]);
        }

        forEach(callback, thisArg) {
            this._items.forEach(i => callback.call(thisArg, i, i, this));
        }

        has(item) {
            return this._items.includes(item);
        }

        get size() { return this._items.length; }

        values() {
            return this._items.slice();
        }

        /** To support Array.from */
        get iterator() { return this.values(); }
    }
}

if (!Array.from) {
    Array.from = function (iterable) {
        if (iterable == null)
            throw new Error("Cannot convert undefined or null to an object");

        if (Array.isArray(iterable))
            return iterable.slice();

        if (Array.isArray(iterable.iterator))
            return iterable.iterator;

        return [];
    }
}

if (!Array.prototype.includes) {
    extendPrototype(Array, {
        includes(item) {
            return this.indexOf(item) >= 0;
        },
        find(predicate, thisArg) {
            let i = this.findIndex(predicate, thisArg);
            if (i >= 0)
                return this[i];
        },
        findIndex(predicate, thisArg) {
            for (let i = 0; i < this.length; i++) {
                let item = this[i];
                if (i in this && predicate.call(thisArg, item, i, this))
                    return i;
            }
            return -1;
        },
        findLast(predicate, thisArg) {
            return this[this.findLastIndex(predicate, thisArg)];
        },
        findLastIndex(predicate, thisArg) {
            predicate = predicate || function () { return true; };
            for (let i = this.length - 1; i >= 0; i--) {
                if (predicate.call(thisArg, this[i], i, this))
                    return i;
            }
            return -1;
        }
    });
}

if (!Object.values) {
    Object.values = function (obj) {
        return Object.keys(obj).map(key => obj[key]);
    };
}

if (!Object.entries) {
    Object.entries = function (obj) {
        return Object.keys(obj).map(key => [key, obj[key]]);
    };
}

if (typeof Object.assign != 'function') {
    // Must be writable: true, enumerable: false, configurable: true
    Object.defineProperty(Object, "assign", {
        value: function assign(target, varArgs) { // .length of function is 2
            'use strict';
            if (target == null) { // TypeError if undefined or null
                throw new TypeError('Cannot convert undefined or null to object');
            }

            var to = Object(target);

            for (var index = 1; index < arguments.length; index++) {
                var nextSource = arguments[index];

                if (nextSource != null) { // Skip over if undefined or null
                    for (var nextKey in nextSource) {
                        // Avoid bugs when hasOwnProperty is shadowed
                        if (Object.prototype.hasOwnProperty.call(nextSource, nextKey)) {
                            to[nextKey] = nextSource[nextKey];
                        }
                    }
                }
            }
            return to;
        },
        writable: true,
        configurable: true
    });
}

if (!String.prototype.includes) {
    extendPrototype(String, {
        includes(search, start) {
            if (typeof start !== 'number') {
                start = 0;
            }

            if (start + search.length > this.length) {
                return false;
            } else {
                return this.indexOf(search, start) !== -1;
            }
        },
        startsWith(search, pos) {
            pos = !pos || pos < 0 ? 0 : +pos;
            return this.substring(pos, pos + search.length) === search;
        },
        // derived from: https://github.com/paulmillr/es6-shim/blob/d8c4ec246a15e7df55da60b7f9b745af84ca9021/es6-shim.js#L146-L154
        repeat: (function () {
            var repeat = function (s, times) {
                if (times < 1) return '';
                if (times % 2) return repeat(s, times - 1) + s;
                var half = repeat(s, times / 2);
                return half + half;
            };

            return function (times) {
                if (times < 0 || times === Infinity) {
                    throw new RangeError('Invalid String#repeat value');
                }
                return repeat(this, times);
            };
        })()
    });
}

Number.isNaN = Number.isNaN || function (value) {
    return value !== value;
}

if (typeof globalThis === 'undefined')
    window.globalThis = window;

if (typeof CSS === 'undefined')
    window.CSS = { supports: () => false };

if (typeof WeakMap === 'undefined')
    window.WeakMap = window.Map;