(function (paw) {
    function ValueUtils() {

    }

    /**
     * Checks if it's function.
     *
     * @param {mixed} fx
     * @returns {boolean}
     */
    ValueUtils.prototype.isFunction = function (fx) {
        return typeof fx === 'function';
    };

    ValueUtils.prototype.notFunction = function (fx) {
        return !this.isFunction(fx);
    };


    /**
     * Checks if it's undefined.
     *
     * @param {mixed} x
     * @returns {boolean}
     */
    ValueUtils.prototype.isUndefined = function (x) {
        return x === undefined;
    };

    /**
     * Checks if it's NOT undefined..
     *
     * @param {mixed} x
     * @returns {boolean}
     */
    ValueUtils.prototype.isDefined = function (x) {
        return x !== undefined;
    };

    /**
     * Checks if it's an array.
     *
     * @param {mixed} x
     * @returns {boolean}
     */
    ValueUtils.prototype.isArray = function (x) {
        return Object.prototype.toString.call(x) === '[object Array]';
    };

    /**
     * Checks if it's an object.
     *
     * @param {mixed} x
     * @returns {boolean}
     */
    ValueUtils.prototype.isObject = function (x) {
        return !this.isNull(x) && !this.isUndefined(x) && !this.isArray(x) && typeof x === 'object';
    };

    /**
     * Checks if it's a promise.
     *
     * @param {mixed} p
     * @returns {boolean}
     */
    ValueUtils.prototype.isPromise = function (p) {
        if (this.isDefined(p) && this.isFunction(p.then)) {
            return true;
        }
        return false;
    };

    /**
     * Checks if it's a string.
     *
     * @param {mixed} val
     * @returns {boolean}
     */
    ValueUtils.prototype.isString = function (val) {
        return typeof val === "string";
    };

    /**
     * Checks if it's a number.
     *
     * @param {mixed} val
     * @returns {boolean}
     */
    ValueUtils.prototype.isNumber = function (val) {
        //        return typeof val === "number";
        return !isNaN(Number(val));
    };

    ValueUtils.prototype.isNumeric = function (value) {
        // if it's not NaN (Not A Number) => it's a number
        return !isNaN(value);
    };

    /**
     * Checks if it's boolean value.
     *
     * @param {mixed} val
     * @returns {boolean}
     */
    ValueUtils.prototype.isBoolean = function (val) {
        if (this.isString(val) && (val === 'false' || val === 'true'))
            return true;
        return typeof val === 'boolean';
    };

    /**
     * Checks if it's NULL value.
     *
     * @param {mixed} val
     * @returns {boolean}
     */
    ValueUtils.prototype.isNull = function (val) {
        return val === null;
    };

    /**
     * Converts string to number. Will detect decimal and thousands separator.
     * 
     * @param {String} str
     * @returns {Number}
     */
    ValueUtils.prototype.strToNum = function (str) {
        var commaI = str.indexOf(',');
        var dotI = str.indexOf('.')
        // f: #,#
        if (commaI > -1 && dotI === -1) {
            str = str.replace(',', '.');
        }
        else if (commaI > -1 && dotI > -1) {
            // f: #.#,#
            if (commaI > dotI) {
                str = this.strReplace(str, '.', '').replace(',', '.');
            }
            // f: #,#.#
            else {
                str = this.strReplace(str, ',', '');
            }
        }
        return str * 1;
    };

    ValueUtils.prototype.strReplace = function (str, pattern, replacement) {
        while (str.indexOf(pattern) > -1 && str.indexOf(pattern) !== str.indexOf(replacement)) {
            str = str.replace(pattern, replacement);
        }
        return str;
    };

    /**
     *
     * @param {String} str
     * @returns {Number}
     */
    ValueUtils.prototype.strToBool = function (str) {
        return this.isSet(str) && str.toString().toLowerCase() === "true";
    };

    /**
     *
     * @param {Boolean} val
     * @returns {Number}
     */
    ValueUtils.prototype.boolToStr = function (val) {
        return (val === true) + "";
    };

    /**
     *
     * @param {Number} num
     * @returns {String}
     */
    ValueUtils.prototype.numToStr = function (num) {
        return num + '';
    };

    /**
    * Checks if value is set. Value must not be null, undefined or empty
    * string.
    *
    * @param {mixed} val
    * @returns {boolean}
    */
    ValueUtils.prototype.notSet = function (val) {
        return this.isNull(val) || this.isUndefined(val) || (this.isString(val) && val.length === 0);
    };

    /**
    * Checks if value is set. Value must not be null, undefined or empty
    * string.
    *
    * @param {mixed} val
    * @returns {boolean}
    */
    ValueUtils.prototype.isSet = function (val) {
        return !this.notSet(val);
    };

    ValueUtils.prototype.base64toArrayBuffer = function (val) {
        var binaryString = atob(val);
        var arrayBuffer = new ArrayBuffer(binaryString.length);
        var uint8Array = new Uint8Array(arrayBuffer);
        for (var i = 0; i < binaryString.length; i++) {
            uint8Array[i] = binaryString.charCodeAt(i);
        }
        return uint8Array;
    };

    ValueUtils.prototype.base64toBlob = function (val, props) {
        return new Blob([this.base64toArrayBuffer(val)], props);
    };

    ValueUtils.prototype.trimString = function (str) {
        return this.isSet(str) ? str.trim() : str;
    };

    ValueUtils.prototype.iterate = function (iterable, onIteration) {
        if (this.isArray(iterable)) {
            var filtered = [];
            for (var i = 0; i < iterable.length; i++) {
                if (onIteration(iterable[i], i) === false) {
                    break;
                }
            }
            return filtered;
        }
        else if (this.isObject(iterable)) {
            var filtered = {};
            for (var k in iterable) {
                if (onIteration(iterable[k], k) === false) {
                    break;
                }
            }
        }
        else {
            throw "Can not iterate, " + (typeof iterable) + " given. Must be an array or an object.";
        }
    };

    ValueUtils.prototype.filter = function (iterable, condition) {
        if (this.isArray(iterable)) {
            var filtered = [];
            for (var i = 0; i < iterable.length; i++) {
                var item = iterable[i];

            }
            this.iterate(iterable, function (item, i) {
                if (condition(item, i) === true) {
                    filtered.push(item);
                }
            });
            return filtered;
        }
        else if (this.isObject(iterable)) {
            var filtered = {};
            for (var k in iterable) {
                var item = iterable[k];
                if (condition(item, k) === true) {
                    filtered[k] = item;
                }
            }
        }

    };

    ValueUtils.prototype.get = function (iterable, condition) {
        var filtered = this.filter(iterable, condition);
        return filtered.length === 0 ? null : filtered[0];
    };

    ValueUtils.prototype.exists = function (iterable, condition) {
        return this.isSet(this.get(iterable, condition));
    };

    ValueUtils.prototype.getIndex = function (arr, condition) {
        var index = -1;
        this.iterate(arr, function (item, i) {
            if (condition(item, i) === true) {
                index = i;
                return false;
            }
        });
        return index;
    };

    ValueUtils.prototype.deleteItem = function (iterable, condition) {
        if (this.isArray(iterable)) {
            var index = this.getIndex(iterable, condition);
            if (index > -1) {
                iterable.splice(index, 1);
                return true;
            }
            return false;
        }
        throw "Can not delete item, not an array. " + (typeof iterable) + " given.";
    };

    ValueUtils.prototype.deleteItems = function (iterable, indeces) {
        if (this.isArray(iterable)) {
            this.iterate(indeces, function (index) {
                iterable.splice(index, 1);
            });
            return;
        }
        throw "Can not delete item, not an array. " + (typeof iterable) + " given.";
    };

    ValueUtils.prototype.b64EncodeUnicode = function(str) {
        // first we use encodeURIComponent to get percent-encoded UTF-8,
        // then we convert the percent encodings into raw bytes which
        // can be fed into btoa.
        return btoa(encodeURIComponent(str).replace(/%([0-9A-F]{2})/g,
            function toSolidBytes(match, p1) {
                return String.fromCharCode(parseInt(p1, 16));
            }));
    };

    ValueUtils.prototype.b64DecodeUnicode = function(str) {
        // Going backwards: from bytestream, to percent-encoding, to original string.
        try{
            return decodeURIComponent(atob(str).split('').map(function (c) {
                return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
            }).join(''));
        }
        catch(e){
            return str;
        }
    };

    ValueUtils.prototype.defaultValue = function(val, defVal){
        if(this.isSet(val)){
            return val;
        }
        return defVal;
    };

    ValueUtils.prototype.interpolateString = function(str, values){
        for(var k in values){
            var val = values[k];
            str = str.replace(k, val);
        }
        return str;
    };

    ValueUtils.prototype.getQparams = function(url, filter) {
        const params = new URL(url).searchParams;
        const queryParams = {};
        
        // Iterate through each entry in params and add to queryParams object
        for (const [key, value] of params.entries()) {
            if(!this.isSet(filter) || filter.indexOf(key) > -1){
                queryParams[key] = value;
            }
        }
        
        return queryParams;
    };

    ValueUtils.prototype.deleteQparams = function(location, qparams, removeHistory) {
        const url = new URL(location);
        for (const [key, value] of url.searchParams.entries()) {
            if(!this.isSet(qparams) || qparams.indexOf(key) > -1){
                url.searchParams.delete(key);
            }
        }
        if(removeHistory){
            window.history.replaceState({}, '', url.toString());
        }
    };

    paw.utils.value = new ValueUtils();
})(paw);