//################################################################################################
/** @file Additional Type Functionality mixin for ish.js
 * @mixin ish.type
 * @author Nick Campbell
 * @license MIT
 * @copyright 2014-2023, Nick Campbell
 * @ignore
 */ //############################################################################################
/*global module, define, global, WeakMap */                     //# Enable Node globals for JSHint
/*jshint maxcomplexity:9 */                                     //# Enable max complexity warnings for JSHint
(function () {
    'use strict'; //<MIXIN>

    function init(core, atob, btoa) {
        var bServerside = core.config.ish().onServer,                                   //# code-golf
            _root = (bServerside ? global : window),                                    //# code-golf
            _undefined /*= undefined*/,                                                 //# code-golf
            _null = null,                                                               //# code-golf
            _Date_now = Date.now                                                        //# code-golf
        ;

        //################################################################################################
        /** Collection of additional Type-based functionality.
         * @namespace ish.type
         * @ignore
         */ //############################################################################################
        core.oop.partial(core.type, function (oProtected) {
            var oCoreType = {
                //#
                is: {
                    //#########
                    /** Determines if the passed value is truthy.
                     * @function ish.type.is.truthy
                     * @param {variant} x Value to interrogate.
                     * @returns {boolean} Value representing if the passed value is truthy.
                     */ //#####
                    truthy: function (x) {
                        return (
                            x !== 0 &&
                            x !== "" &&
                            x !== _null &&
                            x !== false &&
                            x !== _undefined &&

                            //# Rely on the inline polyfill of Number.isNaN from MDN to determine if x is NaN
                            //#     NOTE: The following works because NaN is the only value in javascript which is not equal to itself.
                            //#     See: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isNaN
                            /*x !== NaN*/ !(typeof x === 'number' && x !== x)
                        );
                    }, //# type.is.truthy


                    numeric: {
                        //#########
                        /** Determines if the passed values are equal.
                         * @$note The passed values are implicitly casted per the Javascript rules (see: {@link ish.type.float.mk}).
                         * @function ish.type.is.numeric:eq
                         * @param {variant} x Value to interrogate.
                         * @param {variant} y Value to interrogate.
                         * @returns {boolean} Value representing if the passed values are equal.
                         */ //#####
                        eq: function (x, y) {
                            var bReturnVal = false;

                            //# If the passed x and y .is.numeric, .mk them .floats and reset our bReturnVal to their comparison
                            if (core.type.is.numeric(x) && core.type.is.numeric(y)) {
                                bReturnVal = (core.type.float.mk(x) === core.type.float.mk(y));
                            }

                            return bReturnVal;
                        }, //# type.is.numeric.eq


                        //#########
                        /** Compares the passed value to the reference value, determining if it is greater then, less then or equal.
                         * @$note The passed values are implicitly casted per the Javascript rules (see: {@link ish.type.float.mk}).
                         * @function ish.type.is.numeric:cmp
                         * @param {variant} vNumber Value representing the number to compare.
                         * @param {variant} vRelativeTo Value representing the relative number to compare to.
                         * @returns {integer} Value representing if the passed value is greater then (<code>1</code>), less then (<code>-1</code>) or equal to (<code>0</code>) the passed relative value with <code>undefined</code> indicating one of the passed values was non-numeric.
                         */ //#####
                        cmp: function (vNumber, vRelativeTo) {
                            var iReturnVal = _undefined,
                                dX = core.type.float.mk(vNumber, _null),
                                dY = core.type.float.mk(vRelativeTo, _null)
                            ;

                            if (core.type.is.numeric(dX) && core.type.is.numeric(dY)) {
                                if (dX < dY) {
                                    iReturnVal = -1;
                                }
                                else if (dX > dY) {
                                    iReturnVal = 1;
                                }
                                else {
                                    iReturnVal = 0;
                                }
                            }

                            return iReturnVal;
                        }, //# type.is.numeric.cmp


                        //#########
                        /** Extracts the passed value's numeric characters.
                         * @function ish.type.is.numeric:extract
                         * @param {variant} x Value to interrogate.
                         * @param {variant} [vDefaultVal=0] Value representing the default return value if casting fails.
                         * @returns {integer} Value representing the passed value's numeric characters.
                         */ //#####
                        extract: function (x, vDefaultVal) {
                            var a_sReturnVal;

                            //# Force the passed x into a string for the .match below, .join'ing the results in our a_sReturnVal
                            x = core.type.str.mk(x);
                            a_sReturnVal = x.match(/\d/g) || [];
                            return core.type.int.mk(a_sReturnVal.join(""), (arguments.length > 1 ? vDefaultVal : 0));
                        }, //# type.is.numeric.extract


                        //#########
                        /** Sums the referenced numbers in the passed value.
                         * @function ish.type.is.numeric:sum
                         * @param {Array<integer>|object[]|object} vCollection Value representing the numbers to sum.
                         * @param {string|string[]} [vPath] Value representing the path to the requested property as a period-delimited string (e.g. "parent.child.array.0.key") or an array of strings.
                         * @returns {integer} Value representing sum of the passed values.
                         */ //#####
                        sum: function (vCollection, vPath) {
                            var i,
                                fReturnVal = 0
                            ;

                            //#
                            if (core.type.arr.is(vCollection, true)) {
                                //#
                                if (core.type.str.is(vPath, true) || core.type.arr.is(vPath, true)) {
                                    for (i = 0; i < vCollection.length; i++) {
                                        fReturnVal += core.type.float.mk(core.resolve(vCollection[i], vPath));
                                    }
                                }
                                //#
                                else {
                                    for (i = 0; i < vCollection.length; i++) {
                                        fReturnVal += core.type.float.mk(vCollection[i]);
                                    }
                                }
                            }
                            //#
                            else if (core.type.obj.is(vCollection, true)) {
                                //#
                                for (i in vCollection) {
                                    if (vCollection.hasOwnProperty(i)) {
                                        fReturnVal += core.type.float.mk(vCollection[i]);
                                    }
                                }
                            }

                            return fReturnVal;
                        }, //# type.is.numeric.sum


                        //#########
                        /** Statistical analysis (mean/average, median, mode, range, sum, count and sorted list of values) of the referenced numbers in the passed value.
                         * @function ish.type.is.numeric:stats
                         * @param {Array<integer>|object[]} a_vCollection Value representing the numbers to analyse.
                         * @param {string|string[]} [vPath] Value representing the path to the requested property as a period-delimited string (e.g. "parent.child.array.0.key") or an array of strings.
                         * @returns {integer} Value representing sum of the passed values.
                         */ //#####
                        stats: function (a_vCollection, vPath) {
                            var sKey, fCurrent, i,
                                oMode = {},
                                oReturnVal = {
                                    count: 0,
                                    //average: mean,
                                    //mean: undefined,
                                    //median: undefined,
                                    mode: [],
                                    //sum: undefined,
                                    //range: undefined,
                                    values: []
                                }
                            ;

                            //#
                            if (core.type.arr.is(a_vCollection, true)) {
                                oReturnVal.sum = 0;

                                //# If we were passed a vPath, then a_vCollection is an array of objects
                                if (arguments.length === 2) {
                                    //#
                                    for (i = 0; i < a_vCollection.length; i++) {
                                        fCurrent = core.type.float.mk(core.resolve(a_vCollection[i], vPath));
                                        oReturnVal.sum += fCurrent;
                                        oReturnVal.values.push(fCurrent);
                                    }
                                }
                                //# Else a_vCollection is an array of numeric values
                                else {
                                    //#
                                    for (i = 0; i < a_vCollection.length; i++) {
                                        fCurrent = core.type.float.mk(a_vCollection[i]);
                                        oReturnVal.sum += core.type.float.mk(a_vCollection[i]);
                                        oReturnVal.values.push(fCurrent);
                                    }
                                }

                                //#
                                oReturnVal.count = oReturnVal.values.length;
                                oReturnVal.mean = oReturnVal.average = (oReturnVal.sum / oReturnVal.count);
                                oReturnVal.values.sort();

                                //# If we have an odd number of values in our .values, grab the middle most one
                                //#     NOTE: We need to use Math.floor because array indexes are 0-based rather than 1-based
                                if (oReturnVal.count % 2 === 1) {
                                    i = Math.floor(oReturnVal.count / 2);
                                    oReturnVal.median = oReturnVal.values[i];
                                }
                                //# Else we have an even number of values in our .values, so we need to average the middle-most 2
                                //#     NOTE: We need to -1 from the calculated i because array indexes are 0-based rather than 1-based
                                else {
                                    i = (oReturnVal.count / 2);
                                    oReturnVal.median = ((oReturnVal.values[i - 1] + oReturnVal.values[i]) / 2);
                                }

                                //#
                                for (i = 0; i < oReturnVal.count; i++) {
                                    oMode[oReturnVal.values[i]] = oMode[oReturnVal.values[i]] || 0;
                                    oMode[oReturnVal.values[i]]++;
                                }

                                //# Calculate the .mode(s) of the .values
                                fCurrent = 1;
                                for (sKey in oMode) {
                                    if (oMode[sKey] > fCurrent) {
                                        fCurrent = oMode[sKey];
                                        oReturnVal.mode = [core.type.float.mk(sKey)];
                                    }
                                    else if (oMode[sKey] === fCurrent) {
                                        oReturnVal.mode.push(core.type.float.mk(sKey));
                                    }
                                }
                                oReturnVal.mode.sort();

                                //# Calculate the .range (largest value - smallest)
                                oReturnVal.range = (oReturnVal.values[oReturnVal.count - 1] - oReturnVal.values[0]);
                            }

                            return oReturnVal;
                        }, //# type.is.numeric.stats


                        //#########
                        /** Formats the passed value based on the passed format.
                         * @function ish.type.is.numeric:format
                         * @param {object} [vOptions] Value representing the number of decimal places or the following options:
                         *      @param {boolean} [vOptions.decimalPlaces=2] Value representing the number of decimal places.
                         *      @param {boolean} [vOptions.decimal='.'] Value representing the decimal place delimiter.
                         *      @param {boolean} [vOptions.section=','] Value representing the thousands section delimiter.
                         *      @param {boolean} [vOptions.sectionLength=3] Value representing the thousands section length.
                         * @returns {string} Value representing the formatted number.
                         * @see {@link https://stackoverflow.com/a/14428340/235704|StackOverflow.com}
                         */ //#####
                        format: function (x, vOptions) {
                            var sRegExp;

                            //# Ensure the passed vOptions .is .obj (while setting it's defaults), ensure the passed x is a .float and set sRegExp
                            vOptions = core.extend(
                                {
                                    decimalPlaces: 2,
                                    decimal: ".",
                                    section: ",",
                                    sectionLength: 3
                                }, (core.type.int.is(vOptions) ?
                                    { decimalPlaces: vOptions } :
                                    vOptions
                                )
                            );
                            x = core.type.float.mk(x);
                            sRegExp = '\\d(?=(\\d{' + vOptions.sectionLength + '})+' + (vOptions.decimalPlaces > 0 ? '\\D' : '$') + ')';

                            //# If we have .decimalPlaces to limit, do the rounding via .toFixed now
                            if (vOptions.decimalPlaces > -1) {
                                x = x.toFixed(vOptions.decimalPlaces);
                            }

                            return (vOptions.decimal ?
                                x.replace('.', vOptions.decimal) :
                                x
                            ).replace(new RegExp(sRegExp, 'g'), '$&' + vOptions.section);
                        }, //# type.is.numeric.format


                        hex: {
                            is: function (vValue) {
                                var bReturnVal = false;

                                try {
                                    vValue = parseInt(vValue, 16);
                                    bReturnVal = true;
                                } catch (e) {}

                                return bReturnVal;
                            }, //# type.is.numeric.hex.is

                            mk: function (vValue, vDefault) {
                                var sReturnVal = (arguments.length === 2 ? vDefault : 0);

                                //#
                                if (core.type.int.is(vValue)) {
                                    sReturnVal = core.type.float.mk(vValue).toString(16);
                                }
                                //#
                                else if (core.type.is.numeric.hex.is(vValue)) {
                                    sReturnVal = vValue;
                                }

                                return sReturnVal;
                            }, //# type.is.numeric.hex.mk

                            parse: function (vValue, vDefault) {
                                var iReturnVal = (arguments.length === 2 ? vDefault : 0);

                                try {
                                    iReturnVal = parseInt(vValue, 16);
                                } catch (e) {}

                                return iReturnVal;
                            } //# type.is.numeric.hex.parse
                        },


                        base64: function () {
                            //# FROM: https://gist.github.com/GeorgioWan/16a7ad2a255e8d5c7ed1aca3ab4aacec
                            function hexToBase64(str) {
                                return btoa(String.fromCharCode.apply(null,
                                str.replace(/\r|\n/g, "").replace(/([\da-fA-F]{2}) ?/g, "0x$1 ").replace(/ +$/, "").split(" "))
                                );
                            } //# hexToBase64
                            function base64ToHex(str) {
                                for (var i = 0, bin = atob(str.replace(/[ \r\n]+$/, "")), hex = []; i < bin.length; ++i) {
                                    var tmp = bin.charCodeAt(i).toString(16);
                                    if (tmp.length === 1) tmp = "0" + tmp;
                                    hex[hex.length] = tmp;
                                }
                                return hex.join("");
                            } //# base64ToHex

                            return {
                                encode: function (vValue, bPaddingCharacters) {
                                    var sHex = core.type.is.numeric.hex.mk(vValue, null),
                                        sReturnVal = ""
                                    ;

                                    //# If we got a sHex vValue, convert it .hexToBase64 while optionally stripping the bPaddingCharacters
                                    if (sHex /* !== null*/) {
                                        sReturnVal = hexToBase64(sHex);
                                        sReturnVal = (bPaddingCharacters ? sReturnVal : sReturnVal.replace(/=/g, ""));
                                    }

                                    return sReturnVal;
                                }, //# type.is.numeric.base64.encode

                                decode: function (sBase64) {
                                    var sHex = "";

                                    try {
                                        sHex = base64ToHex(sBase64);
                                    } catch (e) {}

                                    return sHex;
                                } //# type.is.numeric.base64.decode
                            };
                        }()
                    } //# type.is.numeric
                }, //# core.type.is


                //#########
                /** Determines if the passed value is one of the reference values.
                 * @function ish.type.any
                 * @param {variant} x Value to interrogate.
                 * @param {variant[]} a_vReferenceValues Value representing the reference values to compare to.
                 * @param {boolean} [bUseCoercion=false] Value representing if coercion is to be used during comparisons.
                 * @returns {boolean} Value representing if the passed value is present in the reference values.
                 */ //#####
                any: function (x, a_vReferenceValues, bUseCoercion) {
                    var i,
                        fnTest = (bUseCoercion === true ?
                            function (vX, vTest) {
                                return vX == vTest;
                            } :
                            function (vX, vTest) {
                                return vX === vTest;
                            }
                        ),
                        bReturnVal = false
                    ;

                    //#
                    if (core.type.arr.is(a_vReferenceValues, true)) {
                        for (i = 0; i < a_vReferenceValues.length; i++) {
                            if (fnTest(x, a_vReferenceValues[i])) {
                                bReturnVal = true;
                                break;
                            }
                        }
                    }

                    return bReturnVal;
                }, //# core.type.any


                //#########
                /** Queries the passed value.
                 * @function ish.type.query
                 * @param {variant[]|object} vCollection Value representing the collection to interrogate.
                 * @param {object|object[]} vQuery Value representing the query.
                 * @param {object} [oOptions] Value representing the following options:
                 *      @param {boolean} [oOptions.firstEntryOnly=false] Value representing if only the first result is to be returned.
                 *      @param {boolean} [oOptions.caseInsensitive=false] Value representing if the keys are to be searched for in a case-insensitive manor.
                 *      @param {boolean} [oOptions.useCoercion=false] Value representing if coercion is to be used during comparisons.
                 *      @param {boolean} [oOptions.useKeyValue=false] Value representing if the query is specified as a key/value.
                 * @returns {variant[]|variant} Value representing the passed values that matched the query.
                 */ //#####
                query: function () {
                    var yNoValue = core.type.symbol();

                    //#
                    function doQuery(vKey, vQueryValue, oSource, oOptions) {
                        var a_sKeys, i,
                            bMatched = false,
                            oReturnVal = {
                                match: false,
                                matches: [],
                                items: []
                            }
                        ;

                        function queryTestValue(vTestValue, sKey) {
                            //# If the vQueryValue .is .fn, call it with fn(vTestValue, oOptions)
                            if (core.type.fn.is(vQueryValue)) {
                                bMatched = vQueryValue(vTestValue, oSource, oOptions, sKey);
                            }
                            //#
                            else if (vQueryValue instanceof RegExp) {
                                bMatched = !!(core.type.str.mk(vTestValue).match(vQueryValue));
                            }
                            //# Else we'll consider the vCurrent oQuery value as singular
                            else {
                                bMatched = (core.type.obj.is(oSource) &&
                                    (oOptions.useCoercion && vTestValue == vQueryValue) ||
                                    (vTestValue === vQueryValue)
                                );
                            }

                            //#
                            if (bMatched) {
                                oReturnVal.match = true;
                                oReturnVal.matches.push({
                                    //path: _undefined,
                                    key: sKey,
                                    value: vTestValue
                                });
                                oReturnVal.items.push(oSource);
                            }
                        } //# queryTestValue

                        //# If the vKey .is a .regexp
                        if (core.type.regexp.is(vKey)) {
                            //# Pull the a_sKeys for the oSource
                            a_sKeys = core.type.obj.ownKeys(oSource);
                            for (i = 0; i < a_sKeys.length; i++) {
                                //# If the current a_sKeys is a .match
                                if (a_sKeys[i].match(vKey)) {
                                    //# If we are looking for yNoValue in particular, .push the current a_sKeys into our .matches
                                    if (vQueryValue === yNoValue) {
                                        oReturnVal.matches.push({
                                            //path: _undefined,
                                            key: a_sKeys[i],
                                            value: oSource[a_sKeys[i]]
                                        });

                                        //# Make sure .match is true
                                        oReturnVal.match = true;
                                    }
                                    //# Else call queryTestValue with the current a_sKeys
                                    else {
                                        queryTestValue(
                                            oOptions.caseInsensitive ? core.type.obj.get(oSource, a_sKeys[i]) : core.resolve(oSource, a_sKeys[i]),
                                            a_sKeys[i]
                                        );
                                    }
                                }
                            }
                            console.log(oReturnVal);
                        }
                        //# Else call queryTestValue with the vKey
                        else {
                            queryTestValue(
                                oOptions.caseInsensitive ? core.type.obj.get(oSource, vKey) : core.resolve(oSource, vKey),
                                vKey
                            );
                        }

                        return oReturnVal;
                    } //# doQuery

                    //#
                    //# ["val1", "val2"] = core.type.query([{}, {}], ["path.to.val"])
                    //# [{}, {}] = core.type.query([{}, {}], { key: "val", key2: ["val1", "val2"], "path.to.key3": function(vTestValue, oSourceIndex, oOptions) { return true || false; } })
                    return function (vCollection, vQuery, oOptions) {
                        var a_oCollection, a_sKeys, vCurrent, oResults, h, i, j, k,
                            a_oReturnVal = [],
                            a_oReturnValQuery = []
                        ;

                        //# Ensure the passed vQuery is an array
                        vQuery = core.type.arr.mk(vQuery, [vQuery]);

                        //# .extend the passed oOptions with the defaults (which also ensures the passed oOptions .is .obj)
                        oOptions = core.extend({
                            firstEntryOnly: false,
                            caseInsensitive: false,
                            useCoercion: false,
                            useKeyValue: false
                        }, oOptions);

                        //# Calculate our a_oCollection based on the passed vCollection
                        a_oCollection = (core.type.arr.is(vCollection) ?
                            vCollection : (core.type.obj.is(vCollection) ?
                                core.type.obj.toArr(vCollection, oOptions.setKeyAs) :
                                [vCollection]
                            )
                        );

                        //# If we have a_oCollection
                        if (core.type.arr.is(a_oCollection, true)) {
                            //# Traverse the passed vQuery(ies)
                            for (h = 0; h < vQuery.length; h++) {
                                //# If we are to .useKeyValue, set the a_sKeys to the passed .key
                                if (oOptions.useKeyValue) {
                                    a_sKeys = [ vQuery[h].key ];
                                }
                                //# Else pull the a_sKeys for the current vQuery
                                else {
                                    a_sKeys = (core.type.obj.is(vQuery[h], { allowFn: true }) ?
                                        Object.keys(vQuery[h]) :
                                        _undefined
                                    );
                                }

                                //# If the current vQuery has a_sKeys
                                if (core.type.arr.is(a_sKeys, true)) {
                                    //# Traverse our a_oCollection
                                    for (i = 0; i < a_oCollection.length; i++) {
                                        //# Traverse our vQuery's a_sKeys
                                        for (j = 0; j < a_sKeys.length; j++) {
                                            //# If we are to .useKeyValue, set the vCurrent .value (defaulting if there's yNoValue)
                                            if (oOptions.useKeyValue) {
                                                vCurrent = (core.type.is.value(vQuery[h]) && vQuery[h].hasOwnProperty("value") ? vQuery[h].value : yNoValue);
                                            }
                                            //# Else we are not to .useKeyValue, so pull from the vCurrent value directly from the vQuery
                                            else {
                                                vCurrent = vQuery[h][a_sKeys[j]];
                                            }

                                            //# If we have an .is .arr of vQuery values to traverse, do so now
                                            if (core.type.arr.is(vCurrent)) {
                                                for (k = 0; k < vCurrent.length; k++) {
                                                    //# If the vCurrent value .matches our current a_oCollection item, fall from the inner loop
                                                    oResults = doQuery(a_sKeys[j], vCurrent[k], a_oCollection[i], oOptions);
                                                    if (oResults.match) {
                                                        break;
                                                    }
                                                }
                                            }
                                            //# Else we'll consider the vCurrent vQuery value as singular, so collect the oResults from doQuery
                                            else {
                                                oResults = doQuery(a_sKeys[j], vCurrent, a_oCollection[i], oOptions);
                                            }

                                            //# If this is an AND-based vQuery and the vCurrent vQuery isn't an .match or this is an OR-based vQuery and we've already found our .match, fall from the middle loop
                                            if ((!oOptions.or && !oResults.match) || (oOptions.or && oResults.match)) {
                                                break;
                                            }
                                        }

                                        //# If the current a_oCollection record passed each vQuery value, .push the oResults.matches into our a_oReturnVal
                                        if (oResults.match) {
                                            a_oReturnVal = a_oReturnVal.concat(oResults.items); //a_oCollection[i]
                                            a_oReturnValQuery = a_oReturnValQuery.concat(oResults.matches);
                                            a_oReturnVal.$query = a_oReturnValQuery;

                                            //# If we are looking for the .firstEntryOnly, reset h to fall from the outer loop then fall from the middle loop
                                            if (oOptions.firstEntryOnly) {
                                                h = vQuery.length;
                                                break;
                                            }
                                        }
                                    }
                                }
                            }
                        }

                        return (oOptions.firstEntryOnly ? a_oReturnVal[0] : a_oReturnVal);
                    };
                }(), //# core.type.query


                //#########
                /** Registers a new type interface under <code>ish.type</code>.
                 * @function ish.type.register
                 * @param {string} sTypeName Value representing the type name to register.
                 *      <br/><note>This must be unique (e.g. not currently in use under <code>ish.type</code>).</note>
                 * @param {function|RegExp} vTest Value representing the test that verifies the type as a <code>RegExp<code> expression or a function that implements the test, accepting 1 argument (<code>x</code>) and returning truthy if the value is of a valid type.
                 * @param {variant} [vMkDefaultVal] Value representing the default value returned by the <code>mk</code> interface if a <code>vDefaultVal</code> is not provided in the call.
                 *      <br/><note>If this argument is omitted, a <code>mk</code> is not created.</note>
                 * @returns {boolean} Value representing if the new type interface was successfully registered under <code>ish.type</code>.
                 *      <br/><note>The <code>ish.type[sTypeName]</code> type interface is created with an <code>is</code> and optional <code>mk</code> functions. <code>is</code> accepts at least 1 argument which defines the variable to test. <code>mk</code> accepts 2 arguments (<code>vValToTest</code>, <code>vDefraultVal</code>) while calling the <code>is</code> interface, passing in <code>vValToTest</code> to determine if it is a valid type.</note>
                 */ //#####
                register: function (sTypeName, vTest, vMkDefaultVal) {
                    /*
                    Example: https://www.geeksforgeeks.org/how-to-validate-ssn-social-security-number-using-regular-expression/
                    ish.type.register("ssn", /^(?!666|000|9\d{2})\d{3}-(?!00)\d{2}-(?!0{4})\d{4}$/, "000-00-0000");

                    ^ represents the starting of the string.
                    (?!666|000|9\\d{2})\\d{3} represents the first 3 digits should not starts with 000, 666, or between 900 and 999.
                    – represents the string followed by a hyphen (-).
                    (?!00)\\d{2} represents the next 2 digits should not starts with 00 and it should be any from 01-99.
                    – represents the string followed by a hyphen (-).
                    (?!0{4})\\d{4} represents the next 4 digits can’t 0000 and it should be any from 0001-9999.
                    $ represents the ending of the string.
                    */
                    var oInterface = {
                            is: (core.type.fn.is(vTest) ? vTest : (
                                    core.type.regexp.is(vTest) ?
                                    function (x) {
                                        return vTest.test(x + "");
                                    } :
                                    _undefined
                                )
                            )
                        },
                        bReturnVal = (
                            core.type.str.is(sTypeName, true) &&
                            core.type[sTypeName] === undefined &&
                            oInterface.is
                        )
                    ;

                    //# If the caller passed in a valid sTypeName and vTest
                    if (bReturnVal) {
                        //# If the caller passed in a vMkDefaultVal, setup the .mk oInterface
                        if (arguments.length > 2) {
                            oInterface.mk = function (x, vDefaultVal) {
                                return (oInterface.is(x) ?
                                    x : (
                                        arguments.length > 1 ? vDefaultVal : vMkDefaultVal
                                    )
                                );
                            };
                        }

                        //# Set the oInterface under the unique sTypeName
                        core.type[sTypeName] = oInterface;
                    }

                    return bReturnVal;
                },

                //####
                //####

                //# TODO tests, docs
                regexp: {
                    is: function (x) {
                        return x instanceof RegExp;
                    }
                },

                //#########
                /** Universally Unique Identifier (UUID)-based type functionality.
                 * @namespace ish.type.uuid
                 */ //#####
                uuid: function() {
                    var fnReturnValue, d,
                        crypto_randomUUID = core.resolve(_root, "crypto.randomUUID")
                    ;

                    //#
                    function fixVersion(iVersion) {
                        iVersion = core.type.int.mk(iVersion, 0);
                        return (iVersion < 1 || iVersion > 5 ? 4 : iVersion);
                    } //# fixVersion


                    //# If _root.Uint8Array and _root.crypto are available, use them in our fnReturnValue
                    if (core.type.fn.is(_root.Uint8Array) && core.type.fn.is(core.resolve(_root, "crypto.getRandomValues"))) {
                        fnReturnValue = function (iVersion) {
                            //# If this is a iVersion 4 request, use crypto_randomUUID
                            if (iVersion === 4) {
                                return crypto_randomUUID();
                            }
                            //# Else use the inline version
                            else {
                                /*jslint bitwise: true */           //# Enable bitwise operators for JSHint
                                return ([1e7] + -1e3 + -4e3 + -8e3 + -1e11).replace(/[018]/g, function (c) {
                                    return (c ^ _root.crypto.getRandomValues(new _root.Uint8Array(1))[0] & 15 >> c / 4).toString(16);
                                }).replace(/^(.{13})-4/, "$1-" + fixVersion(iVersion));
                            }
                        };
                    }
                    //# Else something went wrong using the ES6 approach, so fall back to the old skool way
                    else {
                        fnReturnValue = function (iVersion) {
                            //# If this is a iVersion 4 request, use crypto_randomUUID
                            if (iVersion === 4) {
                                return crypto_randomUUID();
                            }
                            //# Else use the inline version
                            else {
                                d = (
                                    Date.now() + (core.type.fn.call(core.resolve(_root, "performance.now")) || 0)
                                );

                                /*jslint bitwise: true */           //# Enable bitwise operators for JSHint
                                return ('xxxxxxxx-xxxx-' + fixVersion(iVersion) + 'xxx-yxxx-xxxxxxxxxxxx').replace(/[xy]/g, function (c) {
                                    var r = (d + Math.random() * 16) % 16 | 0;
                                    d = Math.floor(d / 16);
                                    return (c === 'x' ? r : (r & 0x3 | 0x8)).toString(16);
                                });
                            }
                        };

                        /*
                        var i,
                            a_bLookupTable = []
                        ;
                        //# Pre-populate our a_bLookupTable
                        for (i = 0; i < 256; i++) {
                            a_bLookupTable[i] = (i < 16 ? '0' : '') + (i).toString(16);
                        }
                        //# Set our fnReturnValue to the
                        //#     Based on code from: Jeff Ward, https://stackoverflow.com/a/21963136/235704
                        fnReturnValue = function () {
                            var xffffffff = 0xffffffff,
                                iPrefNow = (core.type.fn.is(core.resolve(_root, "performance.now")) ? _root.performance.now() : 0),
                                d0 = (Math.random() + iPrefNow) * xffffffff | 0,
                                d1 = (Math.random() + iPrefNow) * xffffffff | 0,
                                d2 = (Math.random() + iPrefNow) * xffffffff | 0,
                                d3 = (Math.random() + iPrefNow) * xffffffff | 0
                            ;
                            return a_bLookupTable[d0&0xff] + a_bLookupTable[d0>>8&0xff] + a_bLookupTable[d0>>16&0xff] + a_bLookupTable[d0>>24&0xff] +
                                '-' + a_bLookupTable[d1&0xff] + a_bLookupTable[d1>>8&0xff] +
                                '-' + a_bLookupTable[d1>>16&0x0f|0x40] + a_bLookupTable[d1>>24&0xff] +
                                '-' + a_bLookupTable[d2&0x3f|0x80] + a_bLookupTable[d2>>8&0xff] +
                                '-' + a_bLookupTable[d2>>16&0xff] + a_bLookupTable[d2>>24&0xff] + a_bLookupTable[d3&0xff] + a_bLookupTable[d3>>8&0xff] + a_bLookupTable[d3>>16&0xff] + a_bLookupTable[d3>>24&0xff]
                            ;
                        };
                        */

                        //# If we were not able to successfully collect crypto_randomUUID, reroute it's calls to our fnReturnValue
                        if (!core.type.fn.is(crypto_randomUUID)) {
                            crypto_randomUUID = fnReturnValue;
                        }
                    }

                    //#
                    return core.extend(
                        //#########
                        /** Generates a Universally Unique Identifier (UUID).
                         * @function ish.type.uuid.get
                         * @$aka ish.type.uuid
                         * @param {integer} [iVersion=4] Value representing the UUID version to create, with valid values ranging between <code>1</code>-<code>5</code>.
                         * @returns {boolean} Value representing UUID.
                         * @see {@link https://stackoverflow.com/a/2117523/235704|StackOverflow.com}
                         */ //#####
                        function (iVersion) {
                            return fnReturnValue(iVersion);
                        }, {
                            get: fnReturnValue,

                            //#########
                            /** Determines if the passed value represents a UUID.
                             * @function ish.type.uuid.is
                             * @param {string} x Value to interrogate.
                             * @param {boolean|object} [vOptions] Value representing if Nil UUIDs are to be ignored or the following options:
                             *      @param {boolean} [vOptions.excludeNilUUID=false] Value representing if Nil UUIDs are to be ignored.
                             *      @param {boolean} [vOptions.allowBrackets=false] Value representing if enclosing brackets (<code>{}</code>) are to be allowed.
                             *      @param {integer} [vOptions.version] Value representing the required UUID version, with valid values ranging between <code>1</code>-<code>5</code>.
                             * @returns {boolean} Value representing if the passed value represents a UUID.
                             * @see {@link https://tools.ietf.org/html/rfc4122#section-4.1.7|ietf.org}
                             */ //#####
                            is: function (x, vOptions) {
                                var oOptions = core.type.obj.mk(vOptions),
                                    bExcludeNilUUID = (vOptions === true || oOptions.excludeNilUUID === true),
                                    sVersion = (fixVersion(oOptions.version) === oOptions.version ?
                                        oOptions.version :
                                        "1-5"
                                    ),
                                    reTest = new RegExp(bExcludeNilUUID ?
                                        "^[0-9a-f]{8}-[0-9a-f]{4}-[" + sVersion + "][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$" :
                                        "^[0-9a-f]{8}-[0-9a-f]{4}-[0," + sVersion + "][0-9a-f]{3}-[089ab][0-9a-f]{3}-[0-9a-f]{12}$",
                                    "i"),
                                    bReturnVal = false
                                ;

                                //# If x .is a .str
                                if (core.type.str.is(x, true)) {
                                    //# If we are to .allowBrackets, .trim and remove any {}'s
                                    if (oOptions.allowBrackets) {
                                        x = core.type.uuid.format(x);
                                    }

                                    bReturnVal = reTest.test(x);
                                }

                                return bReturnVal;
                            }, //# type.uuid.is


                            //#########
                            /** Casts the passed value into a UUID.
                             * @function ish.type.uuid.mk
                             * @param {string} x Value to interrogate.
                             * @param {variant} [vDefaultVal=ish.type.uuid()] Value representing the default return value if casting fails.
                             * @returns {object} Value representing the passed value as a UUID.
                             */ //#####
                            mk: function (x, vDefaultVal) {
                                var vReturnVal;

                                //#
                                if (core.type.uuid.is(x, true)) {
                                    vReturnVal = x;
                                }
                                //#
                                else if (arguments.length > 1) {
                                    vReturnVal = vDefaultVal;
                                }
                                //#
                                else {
                                    vReturnVal = core.type.uuid();
                                }

                                return vReturnVal;
                            }, //# type.uuid.mk


                            //#########
                            /** Removes any non-canonical brackets (e.g. <code>{}</code>) from the passed value.
                             * @function ish.type.uuid.format
                             * @param {string} x Value to interrogate.
                             * @param {boolean} [bUppercase=false] Value representing if the UUID should be represented in uppercase letters.
                             * @returns {object} Value representing the passed value as a formatted UUID.
                             */ //#####
                            format: function (x, bUppercase) {
                                //# Ensure the passed x .is a .str, then .trim and leading/trailing spaces and {}'s and case it based on the passed bUppercase
                                x = core.type.str.mk(x)
                                    .trim()
                                    .replace(/^\{/, "").replace(/\}$/, "")
                                    [bUppercase === true ? "toUpperCase" : "toLowerCase"]()
                                ;

                                //#
                                if (/^[0-9a-f]{32}$/i.test(x)) {
                                    x = x.replace(/^([a-f0-9]{8})([a-f0-9]{4})([a-f0-9]{4})([a-f0-9]{4})([a-f0-9]{12})$/i, "$1-$2-$3-$4-$5");
                                }

                                //# If the above formatted x .is a .uuid (ensuring that we do not enter an infinite loop by passing allowBrackets = false), return it to the caller, else return a (falsy) null-string
                                //#     NOTE: As we are not explicitly .excludeNilUUID, this will properly format it as well.
                                return (
                                    core.type.uuid.is(x /*, { allowBrackets: false }*/) ?
                                        x :
                                        ""
                                );
                            },

                            //#########
                            /** Encodes and decodes Universally Unique Identifiers (UUID) in Base64.
                             * @function ish.type.uuid.base64
                             * @note UUIDs encoded in Base64 rather than hexadecimal (Base16) produce up to 39% shorter string lengths. UUIDs are 128bit numbers encoded in hexadecimal resulting in 36 character strings (32 plus 4 dashes). Encoding the 128bit number in Base64 reduces the string length to 24 characters (22 plus 2 optional padding characters).
                             * @param {variant} [vValue=ish.type.uuid()] Value representing a standard UUID to be returned as a Base64 encoded UUID or a Base64 encoded UUID to be returned as a standard UUID.
                             * @returns {string} Value representing a new Base64 encoded UUID, the passed UUID in Base64, the passed Base64 encoded UUID as a standard UUID or a <code>null-string</code> if the passed UUID was not recognized.
                             */ //#####
                            base64: function (vValue) {
                                var sReturnVal;

                                //# If no vValue was passed (implying a UUID creation) or it .is a .uuid, .base64.encode it
                                if (arguments.length === 0 || core.type.uuid.is(vValue)) {
                                    sReturnVal = core.type.is.numeric.base64.encode(
                                        (arguments.length === 0 ? core.type.uuid() : core.type.uuid.format(vValue)).replace(/-/g, "")
                                    );
                                }
                                //# Else a vValue was passed, so .decode it as a .base64 vValue
                                else {
                                    sReturnVal = core.type.uuid.format(
                                        core.type.is.numeric.base64.decode(vValue)
                                    );
                                }

                                return sReturnVal;
                            }
                        }
                    );
                }(), //# core.type.uuid

                //####
                //####

                bool: {
                    mk: {
                        //#########
                        /** Casts the passed value into a boolean based on language.
                         * @function ish.type.bool.mk:fuzzy
                         * @param {variant} x Value to interrogate.
                         * @param {variant} [vDefaultVal=undefined] Value representing the default return value if casting fails.
                         * @param {string[]} [a_sTruthy=['1','true','yes','on']] Value representing the lowercase strings to interpret as `true` values.
                         * @param {string[]} [a_sFalsy=['0','false','no','off']] Value representing the lowercase strings to interpret as `false` values.
                         * @returns {boolean} Value representing the passed value as a boolean type.
                         * @see {@link https://developer.mozilla.org/en-US/docs/Glossary/Falsy|Mozilla.org}
                         */ //#####
                        fuzzy: function () {
                            //# Setup the logic to be returned
                            function fuzzy(x, vDefaultVal, a_sTruthy, a_sFalsy) {
                                var bReturnVal = core.type.bool.mk(x, _undefined);

                                //# If our call to .bool.mk fails, we've got work to do
                                if (bReturnVal === _undefined) {
                                    //# Ensure the passed arguments are in the expected format
                                    x = core.type.str.mk(x).trim().toLowerCase();
                                    vDefaultVal = (arguments.length > 1 ? vDefaultVal : _undefined);

                                    //# Ensure the passed a_sTruthy and a_sFalsy .is an .arr, defaulting if nothing was passed
                                    //#     NOTE: 'true'/'false' are handled within core.type.bool.mk above.
                                    a_sTruthy = core.type.arr.mk(a_sTruthy, fuzzy.truthy);
                                    a_sFalsy = core.type.arr.mk(a_sFalsy, fuzzy.falsy);

                                    //# If x is within our a_sTruthy, set our bReturnVal accordingly
                                    if (a_sTruthy.indexOf(x) > -1) {
                                        bReturnVal = true;
                                    }
                                    //# Else if x is within our a_sFalsy, set our bReturnVal accordingly
                                    else if (a_sFalsy.indexOf(x) > -1) {
                                        bReturnVal = false;
                                    }
                                    //# Else set our bReturnVal to the vDefaultVal as all other attempts to resolve the boolean value of x have failed
                                    else {
                                        bReturnVal = vDefaultVal;
                                    }
                                }

                                return bReturnVal;
                            } //# fuzzy

                            //# Attach the default .truthy and .falsy arrays then return
                            fuzzy.truthy = [/*'1', 'true',*/ 'yes' ,'on'];
                            fuzzy.falsy = [/*'0', 'false',*/ 'no' ,'off'];
                            return fuzzy;
                        }()
                    }
                }, //# core.type.bool


                //# eq, cmp, cp, age, yyyymmdd, only
                date: {
                    //#########
                    /** Determines if the passed values are equal.
                     * @$note The passed values are implicitly casted per <code>{@link ish.type.date.mk}</code>.
                     * @function ish.type.date.eq
                     * @param {variant} x Value to interrogate.
                     * @param {variant} y Value to interrogate.
                     * @returns {boolean} Value representing if the passed values are equal.
                     * @see {@link http://stackoverflow.com/a/493018/235704|StackOverflow.com}
                     */ //#####
                    eq: function (x, y) {
                        var dDateX = core.type.date.mk(x, _null);
                        var dDateY = core.type.date.mk(y, _null);

                        //#     NOTE: `new Date("1970/01/01") === new Date("1970/01/01")` is always false as they are 2 different objects, while <= && >= will give the expected result
                        //#     SEE: Comment from Jason Sebring @ http://stackoverflow.com/a/493018/235704
                        return (core.type.date.is(dDateX) && core.type.date.is(dDateY) && dDateX <= dDateY && dDateX >= dDateY);
                    }, //# type.date.eq


                    //#########
                    /** Compares the passed value to the reference value, determining if it is greater then, less then or equal.
                     * @$note The passed values are implicitly casted per <code>{@link ish.type.date.mk}</code>.
                     * @function ish.type.date.cmp
                     * @param {variant} x Value representing the date to compare.
                     * @param {variant} [vRelativeTo=new Date()] Value representing the relative date to compare to.
                     * @returns {integer} Value representing if the passed value is greater then (<code>1</code>), less then (<code>-1</code>) or equal to (<code>0</code>) the passed relative value with <code>undefined</code> indicating one of the passed values was not a reconized date.
                     */ //#####
                    cmp: function (x, vRelativeTo) {
                        var iReturnVal = _undefined,
                            dDateX = core.type.date.mk(x, _null),
                            dDateY = (arguments.length < 2 ? new Date() : core.type.date.mk(vRelativeTo, _null))
                        ;

                        //# If the passed dates are valid, determine dDateX's relationship to dNow
                        if (core.type.date.is(dDateX) && core.type.date.is(dDateY)) {
                            if (dDateX < dDateY) {
                                iReturnVal = -1;
                            }
                            else if (dDateX > dDateY) {
                                iReturnVal = 1;
                            }
                            else {
                                iReturnVal = 0;
                            }
                        }

                        return iReturnVal;
                    }, //# type.date.cmp


                    //#########
                    /** Copies the passed value into a new instance.
                     * @$note The passed values are implicitly casted per <code>{@link ish.type.date.mk}</code>.
                     * @function ish.type.date.cp
                     * @param {variant} x Value representing the date to copy.
                     * @returns {date} Value representing the passed value as a new instance.
                     */ //#####
                    cp: function (x) {
                        var dReturnVal, // = _undefined
                            dParsed = core.type.date.mk(x, _null)
                        ;

                        //# If the caller passed in a valid d(ate),
                        if (core.type.date.is(dParsed)) {
                            dReturnVal = new Date(dParsed.getTime());
                        }

                        return dReturnVal;
                    }, //# type.date.cp


                    //#########
                    /** Determines the full years since the passed value.
                     * @$note The passed values are implicitly casted per <code>{@link ish.type.date.mk}</code>.
                     * @function ish.type.date.age
                     * @param {variant} x Value representing the date of birth.
                     * @returns {integer} Value representing the full years since the passed value.
                     */ //#####
                    age: function (x) {
                        var dAgeSpan,
                            dDOB = core.type.date.mk(x, _null),
                            iReturnVal = -1
                        ;

                        //# If the passed dob is a valid date
                        if (core.type.date.is(dDOB)) {
                            //# Set dAgeSpan based on the milliseconds from epoch
                            dAgeSpan = new Date(Date.now() - dDOB);
                            iReturnVal = Math.abs(dAgeSpan.getUTCFullYear() - 1970);
                        }

                        return iReturnVal;
                    }, //# date.age


                    //#########
                    /** Determines the difference between two dates.
                     * @$note The passed values are implicitly casted per <code>{@link ish.type.date.mk}</code>.
                     * @function ish.type.date.diff
                     * @param {variant} x Value representing the first date to compare.
                     * @param {variant} [y=new Date()] Value representing the second date to compare.
                     * @param {string} [eUnitOfTime="d"] Value representing the unit of time to return; <code>s</code> seconds, <code>m</code> minutes, <code>h</code> hours, <code>d</code> days, <code>w</code> weeks, <code>M</code> months, <code>Y</code> years.
                     * @returns {integer} Value representing the difference between two dates.
                     */ //#####
                    diff: function (x, y, eUnitOfTime) {
                        var iMS,
                            dX = core.type.date.mk(x),
                            dY = core.type.date.mk(y),
                            dReturnVal = null
                        ;

                        //# Determine the eUnitOfTime, setting iMS accordingly (or dReturnVal)
                        switch (core.type.str.mk(eUnitOfTime, "d")) {
                            case "s": {
                                iMS = 1000;
                                break;
                            }
                            case "m": {
                                iMS = 1000 * 60;
                                break;
                            }
                            case "h": {
                                iMS = 1000 * 60 * 60;
                                break;
                            }
                            case "w": {
                                iMS = 1000 * 60 * 60 * 24 * 7;
                                break;
                            }
                            case "M": { //# month
                                dReturnVal = (dY.getMonth() - dX.getMonth() + (12 * (dY.getFullYear() - dX.getFullYear())));
                                break;
                            }
                            case "Y": {
                                dReturnVal = (dY.getFullYear() - dX.getFullYear());
                                break;
                            }
                            case "d":
                            default: {
                                iMS = 1000 * 60 * 60 * 24;
                                break;
                            }
                        }

                        //#
                        if (dReturnVal === null) {
                            dReturnVal = ((dY - dX) / iMS);
                        }

                        return dReturnVal;
                    }, //# date.diff


                    //#########
                    /** Determines the date part only of the passed value.
                     * @$note The passed values are implicitly casted per <code>{@link ish.type.date.mk}</code>.
                     * @function ish.type.date.only
                     * @param {variant} [x=new Date()] Value representing the date.
                     * @param {variant} [vDefault=undefined] Value representing the default return value if casting fails.
                     * @returns {integer} Value representing the date part only of the passed value.
                     */ //#####
                    only: function (x, vDefault) {
                        return core.type.date.mk(core.type.date.yyyymmdd.apply(this, [x, vDefault]) + " 00:00:00");
                    }, //# date.only


                    //#########
                    /** Resets the datetime offset of the passed value to the local system's datetime offset.
                     * @$note The passed values are implicitly casted per <code>{@link ish.type.date.mk}</code>.
                     * @function ish.type.date.utcToLocalOffset
                     * @param {variant} [x=new Date()] Value representing the date.
                     * @returns {integer} Value representing the passed value reset to the local system's datetime offset.
                     */ //#####
                    utcToLocalOffset: function (x) {
                        var dDate = core.type.date.mk(x);

                        return new Date(
                            dDate.getUTCFullYear(), dDate.getUTCMonth(), dDate.getUTCDate(),
                            dDate.getUTCHours(), dDate.getUTCMinutes(), dDate.getUTCSeconds()
                        );
                    } //# date.utcToLocalOffset
                }, //# core.type.date


                //# eq, cmp, lpad, rpad, begins, ends, contains, sub
                str: function () {
                    //#
                    function doPad(s, sChar, iLength, bLPad) {
                        var sReturnVal = core.type.str.mk(s);
                        iLength = core.type.int.mk(iLength);
                        sChar = core.type.str.mk(sChar, " ");

                        //# If we are supposed to bLPad the passed s(tring)
                        if (bLPad) {
                            //# Left pad out this [string] while it's .length is less than the passed iLength
                            while (sReturnVal.length < iLength) {
                                sReturnVal = sChar + sReturnVal;
                            }
                        }
                        //# Else we are supposed to RPad the passed s(tring)
                        else {
                            //# Left pad out this [string] while it's .length is less than the passed iLength
                            while (sReturnVal.length < iLength) {
                                sReturnVal = sReturnVal + sChar;
                            }
                        }

                        return sReturnVal;
                    } //# doPad

                    //#
                    function doSearch(s, a_vReference, fnTest) {
                        var i, bResult,
                            bReturnVal = false
                        ;

                        //#
                        for (i = 0; i < a_vReference.length; i++) {
                            bResult = doSearchCompare(s, a_vReference[i], fnTest);

                            //#
                            if (bResult === true) {
                                bReturnVal = bResult; //iResult;
                                break;
                            }
                            else if (bResult !== false) {
                                bReturnVal = bResult; //iResult;
                            }
                        }

                        return bReturnVal;
                    } //# doSearch

                    //#
                    function doSearchCompare(x, q, fnTest) {
                        var sX = core.type.str.mk(x),
                            sReference = core.type.str.mk(q),
                            bReturnVal = false
                        ;

                        //# If the passed s(tring) .starts with sReference, set our bReturnVal to true
                        if (fnTest(sX.indexOf(sReference), sX.length, sReference.length)) {
                            bReturnVal = true;
                        }
                        //# Else if the passed s(tring) .starts with sReference after .trim'ing and .toLowerCase'ing, set our bReturnVal to 1 (truthy)
                        else {
                            sX = sX.trim().toLowerCase();
                            sReference = sReference.trim().toLowerCase();
                            if (fnTest(sX.indexOf(sReference), sX.length, sReference.length)) {
                                bReturnVal = 1;
                            }
                        }

                        return bReturnVal;
                    } //# doSearchCompare


                    return {
                        //#########
                        /** Determines if the passed values are equal.
                         * @$note The passed values are implicitly casted per <code>{@link ish.type.str.mk}</code>.
                         * @function ish.type.str.eq
                         * @param {variant} x Value to interrogate.
                         * @param {variant} y Value to interrogate.
                         * @param {boolean} [bCaseInsensitive=true] Value representing if the keys are to be searched for in a case-insensitive manor.
                         * @returns {boolean} Value representing if the passed values are equal.
                         */ //#####
                        eq: function (x, y, bCaseInsensitive) {
                            x = core.type.str.mk(x, "");
                            y = core.type.str.mk(y, "");

                            //# Unless specifically told not to, compare the passed string as bCaseInsensitive
                            return (bCaseInsensitive !== false ?
                                (x.toLowerCase() === y.toLowerCase()) :
                                (x === y)
                            );
                        }, //# type.str.eq


                        //#########
                        /** Compares the passed value to the reference value(s), determining if it is equal, equal when case-insensitive and trimmed or not equal.
                         * @$note The passed values are implicitly casted per <code>{@link ish.type.str.mk}</code>.
                         * @function ish.type.str.cmp
                         * @param {variant} x Value representing the string to compare.
                         * @param {variant|variant[]} vReference Value representing the reference string(s) to compare to.
                         * @returns {boolean|integer} Value representing if the passed value is equal (<code>true</code>), equal when case-insensitive and trimmed (<code>1</code>) or not equal (<code>false</code>) to the passed reference string(s).
                         */ //#####
                        cmp: function () {
                            //# .compare's two strings, returning truthy or false based on their relationship
                            function compare(x, y) {
                                var vReturnVal = false,
                                    s1 = core.type.str.mk(x),
                                    s2 = core.type.str.mk(y)
                                ;

                                //# If the strings match as-is, reset our vReturnVal to 1
                                if (s1 === s2) {
                                    vReturnVal = true;
                                }
                                //# Else if the strings match after .trim'ing and .toLowerCase'ing, reset our vReturnVal to -1
                                else if (s1.trim().toLowerCase() === s2.trim().toLowerCase()) {
                                    vReturnVal = 1; //# truthy
                                }

                                return vReturnVal;
                            } //# compare


                            return function (x, vReference) {
                                var i,
                                    bReturnVal = false
                                ;

                                //# If the passed vReference .is an .arr, traverse it, bReturnVal'ing on the first .compare hit
                                if (core.type.arr.is(vReference)) {
                                    for (i = 0; i < vReference.length; i++) {
                                        bReturnVal = compare(x, vReference[i]);
                                        if (bReturnVal) {
                                            break;
                                        }
                                    }
                                }
                                //# Else the passed vReference .is not an .arr, so bReturnVal the result of .compare
                                else {
                                    bReturnVal = compare(x, vReference);
                                }

                                return bReturnVal;
                            };
                        }(), //# type.str.cmp.*

                        //cp:

                        //#########
                        /** Prepends the passed character onto the passed value to a minimum length.
                         * @$note The passed values are implicitly casted per <code>{@link ish.type.str.mk}</code>.
                         * @function ish.type.str.lpad
                         * @param {variant} x Value representing the string to pad.
                         * @param {string} sChar Value representing the character to pad with.
                         * @param {integer} iLength Value representing the minimum length to pad to.
                         * @returns {string} Value representing the passed value prepended with the pad character to the minimum length.
                         */ //#####
                        lpad: function (x, sChar, iLength) {
                            return doPad(x, sChar, iLength, true);
                        }, //# type.str.lpad


                        //#########
                        /** Appends the passed character onto the passed value to a minimum length.
                         * @$note The passed values are implicitly casted per <code>{@link ish.type.str.mk}</code>.
                         * @function ish.type.str.rpad
                         * @param {variant} x Value representing the string to pad.
                         * @param {string} sChar Value representing the character to pad with.
                         * @param {integer} iLength Value representing the minimum length to pad to.
                         * @returns {string} Value representing the passed value appended with the pad character to the minimum length.
                         */ //#####
                        rpad: function (x, sChar, iLength) {
                            return doPad(x, sChar, iLength, false);
                        }, //# type.str.rpad


                        //#########
                        /** Compares the passed value to the reference value, determining if it begins with, begins with when case-insensitive and trimmed or does not begin with the reference value.
                         * @$note The passed values are implicitly casted per <code>{@link ish.type.str.mk}</code>.
                         * @function ish.type.str.begins
                         * @param {variant} x Value representing the string to compare.
                         * @param {variant|variant[]} vReference Value representing the reference string(s) to compare to.
                         * @returns {boolean|integer} Value representing if the passed value begins with (<code>true</code>), begins with when case-insensitive and trimmed (<code>1</code>) or does not begin with (<code>false</code>) to the passed reference value.
                         */ //#####
                        begins: function (x, vReference) {
                            return doSearch(x, core.type.arr.mk(vReference, [vReference]), function (iIndexOf /*, iX, iReference*/) {
                                return (iIndexOf === 0);
                            });
                        }, //# type.str.begins


                        //#########
                        /** Compares the passed value to the reference value, determining if it ends with, ends with when case-insensitive and trimmed or does not end with the reference value.
                         * @$note The passed values are implicitly casted per <code>{@link ish.type.str.mk}</code>.
                         * @function ish.type.str.ends
                         * @param {variant} x Value representing the string to compare.
                         * @param {variant|variant[]} vReference Value representing the reference string(s) to compare to.
                         * @returns {boolean|integer} Value representing if the passed value ends with (<code>true</code>), ends with when case-insensitive and trimmed (<code>1</code>) or does not end with (<code>false</code>) to the passed reference value.
                         */ //#####
                        ends: function (x, vReference) {
                            return doSearch(x, core.type.arr.mk(vReference, [vReference]), function (iIndexOf, iX, iReference) {
                                return (iIndexOf != -1 && iIndexOf === (iX - iReference));
                            });
                        }, //# type.str.ends


                        //#########
                        /** Compares the passed value to the reference value, determining if it is contained within, contained within when case-insensitive and trimmed or is not contained within with the reference value.
                         * @$note The passed values are implicitly casted per <code>{@link ish.type.str.mk}</code>.
                         * @function ish.type.str.contains
                         * @param {variant} x Value representing the string to compare.
                         * @param {variant|variant[]} vReference Value representing the reference string(s) to compare to.
                         * @returns {boolean|integer} Value representing if the passed value is contained within (<code>true</code>), contained within when case-insensitive and trimmed (<code>1</code>) or is not contained within (<code>false</code>) to the passed reference value.
                         */ //#####
                        contains: function (x, vReference) {
                            return doSearch(x, core.type.arr.mk(vReference, [vReference]), function (iIndexOf /*, iX, iReference*/) {
                                return (iIndexOf > -1);
                            });
                        }, //# type.str.contains


                        //#########
                        /** Removes the referenced leading and trailing characters from the passed value.
                         * @$note The passed values are implicitly casted per <code>{@link ish.type.str.mk}</code>.
                         * @function ish.type.str.sub
                         * @param {variant} x Value representing the string.
                         * @param {integer} iFromStart Value representing the number of characters to remove from the beginning of the passed value.
                         * @param {integer} iFromEnd Value representing the number of characters to remove from the end of the passed value.
                         * @returns {string} Value representing the remaining characters from the passed value.
                         */ //#####
                        sub: function (x, iFromStart, iFromEnd) {
                            var sReturnVal = core.type.str.mk(x),
                                iStart = core.type.int.mk(iFromStart),
                                iEnd = sReturnVal.length - core.type.int.mk(iFromEnd)
                            ;

                            return sReturnVal.substring(
                                (iStart < 0 ? 0 : iStart),
                                (iEnd < 0 ? 0 : iEnd)
                            );
                        }, //# type.str.sub


                        //#########
                        /** Interpolates variables within the passed value.
                         * @$note The passed values are implicitly casted per <code>{@link ish.type.str.mk}</code>.
                         * @function ish.type.str.replace
                         * @param {variant} x Value representing the string.
                         * @param {object} oData Value representing the data to interpolate within the passed value.
                         * @param {object} [oOptions] Value representing the following options:
                         *      @param {RegExp} [oOptions.pattern] Value representing the regular expression that defines the interpolation delimiters.
                         *      @param {boolean} [oOptions.start="{"] Value representing the leading interpolation delimiter.
                         *      @param {boolean} [oOptions.end="}"] Value representing the trailing interpolation delimiter.
                         * @returns {string} Value representing the results of the passed value's interpolation.
                         */ //#####
                        replace: function() {
                            var rePattern = /{([^{]+)}/g;

                            return function(x, oData, oOptions) {
                                var reCurrentPattern, vCurrent;

                                //#
                                x = core.type.str.mk(x);
                                oData = core.type.obj.mk(oData);
                                oOptions = core.type.obj.mk(oOptions);
                                reCurrentPattern = oOptions.pattern || rePattern;

                                //#
                                if (oOptions.start && oOptions.end) {
                                    reCurrentPattern = new RegExp(oOptions.start + "([^" + oOptions.start + "]+)" + oOptions.end, "g");
                                }

                                return x.replace(reCurrentPattern, function(ignore, sKey) {
                                    vCurrent = core.resolve(oData, sKey);

                                    return (core.type.fn.is(vCurrent) ?
                                        vCurrent() :
                                        core.type.str.mk(vCurrent)
                                    );
                                });
                            };
                        }(), //# type.str.replace


                        //#########
                        /** Creates a Base64-encoded ASCII string from a binary string (i.e., a string in which each character in the string is treated as a byte of binary data).
                         * @function ish.type.str.atob
                         * @param {variant} x Value representing the binary string containing base64-encoded data.
                         * @returns {string} Value representing the  the results of the passed value's Base64 decoded data.
                         * @see {@link https://dirask.com/posts/Node-js-atob-btoa-functions-equivalents-1Aqb51|Node.js - atob / btoa functions equivalents}
                         */ //#####
                        //# https://dirask.com/posts/Node-js-atob-btoa-functions-equivalents-1Aqb51
                        atob: function (encodedData) {
                            return atob(encodedData);
                        }, //# type.str.atob


                        //#########
                        /** Decodes a Base64-encoded ASCII string of data which has been encoded using Base64 encoding.
                         * @function ish.type.str.btoa
                         * @param {string} encodedData Value representing the binary string.
                         * @returns {string} Value representing the results of the passed value's Base64 representation.
                         * @see {@link https://dirask.com/posts/Node-js-atob-btoa-functions-equivalents-1Aqb51|Node.js - atob / btoa functions equivalents}
                         */ //#####
                        btoa: function (stringToEncode) {
                            return btoa(stringToEncode);
                        } //# type.str.btoa
                    };
                }(), //# core.type.str


                //# cp
                fn: {
                    //eq:
                    //cmp:
                    //#
                    //#     SEE: https://stackoverflow.com/a/22534864/235704
                    /*cp: function (fn) {
                        var sKey;

                        //#
                        function newFn() {
                            var fnReturnVal, i,
                                sEval = "",
                                a = core.type.fn.convert(arguments)
                            ;
                            //try {
                            //    fnReturnVal = new fn(...a);
                            //} catch (e) {
                                //#
                                if (a.length > 0) {
                                    for (i = 0; i < a.length; i++) {
                                        sEval += "a[" + i + "],";
                                    }
                                    sEval = sEval.substr(0, sEval.length - 1);
                                }
                                //#
                                fnReturnVal = eval("new fn(" + sEval + ")");
                            //}
                            return fnReturnVal;
                        } //# newFn

                        //#
                        function fnReturnVal() {
                            if (this instanceof fnReturnVal) {
                                return newFn.apply(_null, arguments);
                            }
                            return fn.apply(this, arguments);
                        } //# fnReturnVal
                        //#
                        for (sKey in fn) {
                            if (fn.hasOwnProperty(sKey)) {
                                fnReturnVal[sKey] = fn[sKey];
                            }
                        }
                        return fnReturnVal;
                    }*/ //# type.fn.cp


                    //#########
                    /** Injects dependencies into the passed function from the passed value.
                     * @function ish.type.fn.inject
                     * @param {function} fn Value representing the function to execute.
                     * @param {object} oDependencies Value representing the dependencies to inject.
                     * @param {boolean} [bNoCallWhenMissing=false] Value representing if the passed function is to be called if dependencies are missing.
                     * @returns {object} =interface Value representing the following properties:
                     *      @returns {boolean} =interface.success Value indicating if the passed function was executed.
                     *      @returns {string[]} =interface.missing Value indicating the missing dependencies.
                     */ //#####
                    inject: function (fn, oDependencies, bNoCallWhenMissing) {
                        var i,
                            oMetadata = core.type.fn.metadata(fn),
                            a_sKeys = core.type.obj.ownKeys(oDependencies),
                            oReturnVal = {
                                success: (oMetadata.is && core.type.arr.is(a_sKeys, true)),
                                missing: []
                            }
                        ;

                        //# If we have a fn and oDependencies to .inject, traverse the .parameters
                        if (oReturnVal.success) {
                            for (i = 0; i < oMetadata.parameters.length; i++) {
                                //# If the oDependencies don't have the current .parameter
                                if (a_sKeys.indexOf(oMetadata.parameters[i]) === -1) {
                                    oReturnVal.missing.push(oMetadata.parameters[i]);
                                }

                                //# Reset the current .parameters with the related oDependencies
                                //#     NOTE: Unknown a_sKeys will set as undefined
                                oMetadata.parameters[i] = oDependencies[oMetadata.parameters[i]];
                            }

                            //# Redetermine our .success based on bNoCallWhenMissing or .missing.length
                            oReturnVal.success = (!bNoCallWhenMissing || oReturnVal.missing.length === 0);

                            //# If we're not to bNoCallWhenMissing, asynchronously .run the fn with the above replaced .parameters
                            if (oReturnVal.success) {
                                setTimeout(function () {
                                    core.type.fn.run(fn, { args: oMetadata.parameters });
                                });
                            }
                        }

                        return oReturnVal;
                    }, //# fn.inject


                    //#########
                    /** Wraps the passed function, ensuring it is executed as much as possible without ever executing more than once per wait duration.
                     *   <br/>The passed function is executed via <code>function.apply()</code>.
                     * @function ish.type.fn.throttle
                     * @param {function} fn Value representing the function to execute.
                     * @param {object} [oOptions] Value representing the desired options:
                     *      @param {variant} [oOptions.context=undefined] Value representing the context (e.g. <code>this</code>) the passed function is executed under.
                     *      @param {integer} [oOptions.wait=500] Value representing the minimum number of milliseconds (1/1000ths of a second) between each call.
                     *      @param {boolean} [oOptions.leading=true] Value representing if the passed function is to be executed immediately on the first call.
                     *      @param {boolean} [oOptions.trailing=false] Value representing if the passed function is to be executed at the conclusion of the last wait time.
                     * @returns {function} Function that returns a value representing the passed function's return value from the most recent call.
                     * @see {@link http://underscorejs.org/docs/underscore.html|UnderscoreJS.org}
                     */ //#####
                    throttle: function (fn, oOptions) {
                        var context, args, result,
                            timeout = _null,
                            previous = 0,
                            later = function () {
                                previous = oOptions.leading === false ? 0 : _Date_now();
                                timeout = _null;
                                result = fn.apply(context, args);
                                if (!timeout) context = args = _null;
                            }
                        ;

                        //#
                        oOptions = oProtected.processFnOptions(oOptions, {
                            leading: true,
                            trailing: false
                        }, 500);

                        return function (/*arguments*/) {
                            var remaining,
                                now = _Date_now()
                            ;
                            if (!previous && oOptions.leading === false) previous = now;
                            remaining = oOptions.wait - (now - previous);
                            context = oOptions.context;
                            args = core.type.fn.convert(arguments);
                            if (remaining <= 0 || remaining > oOptions.wait) {
                                if (timeout) {
                                    clearTimeout(timeout);
                                    timeout = _null;
                                }
                                previous = now;
                                result = fn.apply(context, args);
                                if (!timeout) context = args = _null;
                            } else if (!timeout && oOptions.trailing !== false) {
                                timeout = setTimeout(later, remaining);
                            }
                            return result;
                        };
                    }, //# fn.throttle


                    //#########
                    /** Wraps the passed function, ensuring it cannot be executed until the wait duration has passed without a call being made.
                     *   <br/>The passed function is executed via <code>function.apply()</code>.
                     * @function ish.type.fn.debounce
                     * @param {function} fn Value representing the function to execute.
                     * @param {object} [oOptions] Value representing the desired options:
                     *      @param {variant} [oOptions.context=undefined] Value representing the context (e.g. <code>this</code>) the passed function is executed under.
                     *      @param {integer} [oOptions.wait=500] Value representing the minimum number of milliseconds (1/1000ths of a second) between each call.
                     *      @param {boolean} [oOptions.leading=false] Value representing if the passed function is to be executed immediently on the first call.
                     * @returns {function} Function that returns a value representing the passed function's return value from the most recent call.
                     * @example
                     *    var myEfficientFn = ish.type.fn.debounce(function () {
                     *      // All the taxing stuff you do
                     *    }, 250);
                     *    window.addEventListener('resize', myEfficientFn);
                     */ //#####
                    debounce: function (fn, oOptions) {
                        var timeout, args, context, timestamp, result,
                            later = function () {
                                var last = _Date_now() - timestamp;

                                if (last < oOptions.wait && last >= 0) {
                                    timeout = setTimeout(later, oOptions.wait - last);
                                } else {
                                    timeout = _null;
                                    if (!oOptions.leading) {
                                        result = fn.apply(context, args);
                                        if (!timeout) context = args = _null;
                                    }
                                }
                            }
                        ;

                        //#
                        oOptions = oProtected.processFnOptions(oOptions, {
                            //context: _undefined,
                            leading: false
                        }, 500);

                        return function (/*arguments*/) {
                            var callNow = oOptions.leading && !timeout;
                            context = oOptions.context;
                            args = core.type.fn.convert(arguments);
                            timestamp = _Date_now();
                            if (!timeout) timeout = setTimeout(later, oOptions.wait);
                            if (callNow) {
                                result = fn.apply(context, args);
                                context = args = _null;
                            }

                            return result;
                        };
                    }, //# fn.debounce


                    //#########
                    /** Creates a proxy function that forwards calls to all registered functions.
                     * @function ish.type.fn.proxy
                     * @returns {function} =proxy Value representing the proxy function that forwards calls to all registered functions along with an interface with the following properties:
                     *      @returns {function} =proxy.add Registers the passed function to the parent proxy function; <code>add(fn)</code>:
                     *          <table class="params">
                     *              <tr><td class="name"><code>fn</code><td><td class="type param-type">function<td><td class="description last">Value representing the function to register.</td></tr>
                     *              <tr><td class="name">Returns:<td><td class="type param-type">boolean<td><td class="description last">Value representing if the passed function has been successfully registered.</td></tr>
                     *          </table>
                     *      @returns {function} =proxy.rm Unregisters the passed function from the parent proxy function; <code>rm(fn)</code>:
                     *          <table class="params">
                     *              <tr><td class="name"><code>fn</code><td><td class="type param-type">function<td><td class="description last">Value representing the function to unregister.</td></tr>
                     *              <tr><td class="name">Returns:<td><td class="type param-type">boolean<td><td class="description last">Value representing if the passed function has been successfully unregistered.</td></tr>
                     *          </table>
                     */ //#####
                    proxy: function (bPreventDefault) {
                        var a_fns = [];

                        //#
                        function callProxy(fn, _arguments, _this) {
                            setTimeout(function () {
                                core.type.fn.run(fn, { args: _arguments, context: _this });
                            }, 0);
                        } //# callProxy


                        return core.extend(
                            function proxy(/*arguments*/) {
                                //#
                                for (var i = 0; i < a_fns.length; i++) {
                                    callProxy(a_fns[i], arguments, this);
                                }
                                return (bPreventDefault === true);
                            }, {
                                //#########
                                /** Registers the passed function to the parent proxy function.
                                 * @function ish.type.fn.proxy:add
                                 * @param {function} fn Value representing the function to register.
                                 * @returns {boolean} Value representing if the passed function has been successfully registered.
                                 * @ignore
                                 */ //#####
                                add: function (fn) {
                                    var bReturnVal = core.type.fn.is(fn);

                                    if (bReturnVal) {
                                        a_fns.push(fn);
                                    }

                                    return bReturnVal;
                                }, //# type.fn.proxy.add

                                //#########
                                /** Unregisters the passed function from the parent proxy function.
                                 * @function ish.type.fn.proxy:rm
                                 * @param {function} fn Value representing the function to unregister.
                                 * @returns {boolean} Value representing if the passed function has been successfully unregistered.
                                 * @ignore
                                 */ //#####
                                rm: function (fn) {
                                    var iIndex = a_fns.indexOf(fn),
                                        bReturnVal = (iIndex > -1)
                                    ;

                                    if (bReturnVal) {
                                        a_fns.splice(iIndex, 1);
                                    }

                                    return bReturnVal;
                                } //# type.fn.proxy.rm
                            }
                        );
                    } //# type.fn.proxy
                }, //# core.type.fn


                //# cmp, cp, unique, matches, randomize, of, countOf, sort
                arr: {
                    //eq: function () {}, //# type.arr.eq

                    //#########
                    /** Compares the passed value to the reference value, determining if the values match, match when coerced or match in a different order.
                     * @function ish.type.arr.cmp
                     * @param {variant[]} x Value representing the array to compare.
                     * @param {variant[]} y Value representing the reference array.
                     * @param {boolean} [bInOrderOnly=false] Value representing if the comparison is to be in matching order only.
                     * @returns {integer|boolean} Value representing if the values match (<code>true</code>), match when coerced (<code>1</code>) or match in a different order (<code>2</code>) the passed relative value with <code>false</code> indicating the arrays do not match.
                     */ //#####
                    cmp: function (x, y, bInOrderOnly) {
                        var a_vYClone, iIndex, i,
                            vReturnVal = (
                                core.type.arr.is(x) && core.type.arr.is(y) &&
                                x.length === y.length
                            ),
                            iLen = (vReturnVal ? x.length : 0)
                        ;

                        //# If the passed x and y .is .arr and both have the same iLen
                        if (vReturnVal) {
                            //# Traverse the passed arrays, flipping our vReturnVal if an index isn't === or ==
                            for (i = 0; i < iLen; i++) {
                                if (x[i] !== y[i]) {
                                    if (x[i] == y[i]) {
                                        vReturnVal = 1;
                                    }
                                    else {
                                        vReturnVal = (bInOrderOnly ? false : 2);
                                        break;
                                    }
                                }
                            }

                            //# If an index didn't match via coercion (e.g. ==) and we're not looking for bInOrderOnly, see if both arrays have the same values in a different order
                            if (vReturnVal === 2) {
                                //# Create a a_vYClone of y
                                //#     NOTE: We have to create a clone as a simple list of found iIndexes won't work because values could be repeated
                                a_vYClone = y.slice(0);

                                //# Traverse x, determining the iIndex in a_vYClone as we go
                                for (i = 0; i < iLen; i++) {
                                    iIndex = a_vYClone.indexOf(x[i]);

                                    //# If we could not find the current iIndex, flip our vReturnVal and fall from the loop
                                    if (iIndex === -1) {
                                        vReturnVal = false;
                                        break;
                                    }
                                    //# Else remove the iIndex from a_vYClone
                                    else {
                                        a_vYClone.splice(iIndex, 1);
                                    }
                                }
                            }
                        }

                        return vReturnVal;
                    }, //# type.arr.cmp


                    //#########
                    /** Copies the passed value into a new instance.
                     * @function ish.type.arr.cp
                     * @param {variant[]} x Value representing the array to copy.
                     * @param {boolean|integer} [vMaxDepth=0] Indicates if a deep copy is to occur. <code>false</code>/<code>0</code> performs a shallow copy, a positive integer indicates the max depth to perform a deep copy to, <code>true</code> and all other integer values perform a deep copy to an unlimited depth.
                     * @returns {variant[]} Value representing the passed value as a new instance.
                     */ //#####
                    cp: function (x, vMaxDepth) {
                        var vCurrent, i,
                            a_vReturnVal /* = _undefined */
                        ;

                        //#
                        vMaxDepth = core.type.int.mk((vMaxDepth === true ? -1 : vMaxDepth), 0);

                        //# If the caller passed in a valid a(rray)
                        if (core.type.arr.is(x)) {
                            //#
                            if (vMaxDepth !== 0) {
                                a_vReturnVal = [];

                                //#
                                for (i = 0; i < x.length; i++) {
                                    vCurrent = x[i];

                                    if (core.type.arr.is(vCurrent)) {
                                        a_vReturnVal.push(core.type.arr.cp(vCurrent, vMaxDepth - 1));
                                    }
                                    else if (core.type.obj.is(vCurrent)) {
                                        a_vReturnVal.push(core.type.obj.cp(vCurrent, vMaxDepth - 1));
                                    }
                                    else if (core.type.date.is(vCurrent)) {
                                        a_vReturnVal.push(core.type.date.cp(vCurrent));
                                    }
                                    else if (core.type.fn.is(vCurrent)) {
                                        a_vReturnVal.push(core.type.fn.cp(vCurrent));
                                    }
                                    else if (core.type.dom.is(vCurrent)) {
                                        a_vReturnVal.push(core.type.dom.cp(vCurrent, true));
                                    }
                                    else {
                                        a_vReturnVal.push(vCurrent);
                                    }
                                }
                            }
                            //# Else we don't need to do a vMaxDepth, so set our a_vReturnVal to a .slice'd clone of the a(rray)
                            else {
                                a_vReturnVal = x.slice(0);
                            }
                        }

                        return a_vReturnVal;
                    }, //# type.arr.cp


                    //#########
                    /** Determines the unique entries within the passed value.
                     * @function ish.type.arr.unique
                     * @param {variant[]} x Value representing the array to compare.
                     * @param {object|boolean} [vOptions=false] Value representing if the passed value is to be compared in a case-insensitive manor.
                     *      @param {boolean} [vOptions.caseInsensitive=false] Value representing if the keys are to be searched for in a case-insensitive manor.
                     *      @param {string} [vOptions.path=""] Value representing the path to the requested property as a period-delimited string (e.g. "parent.child.array.0.key") or an array of strings.
                     * @returns {variant[]} Value representing the passed values' unique entries.
                     */ //#####
                    unique: function (x, vOptions) {
                        var s_vValues, vCurrentValue, iCurrentIndex, i, j,
                            a_vReturnVal = [],
                            oOptions = core.type.obj.mk(vOptions, { caseInsensitive: vOptions === true })
                        ;

                        //# If the caller passed in a valid .arr
                        if (core.type.arr.is(x)) {
                            //# If the caller passed in a .path to .resolve, setup our s_vValues
                            if (oOptions.path) {
                                s_vValues = [];

                                //# Traverse the passed .arr, determine the vCurrentValue and iCurrentIndex as we gp
                                for (i = 0; i < x.length; i++) {
                                    vCurrentValue = core.resolve(x[i], oOptions.path);
                                    iCurrentIndex = s_vValues.indexOf(vCurrentValue);

                                    //# If the vCurrentValue was not found
                                    if (iCurrentIndex === -1) {
                                        //# If we are looking for .caseInsensitive s_vValues, traverse them resetting iCurrentIndex if a .cmp match is found
                                        if (oOptions.caseInsensitive) { //} && core.type.str.is(vCurrentValue)) {
                                            for (j = 0; j < s_vValues.length; j++) {
                                                if (core.type.str.cmp(s_vValues[j], vCurrentValue)) {
                                                    iCurrentIndex = j;
                                                    break;
                                                }
                                            }
                                        }

                                        //# If the vCurrentValue was (still) not found, .push the vCurrentValue into the arrays
                                        if (iCurrentIndex === -1) {
                                            s_vValues.push(vCurrentValue);
                                            a_vReturnVal.push(x[i]);
                                        }
                                    }
                                }
                            }
                            //# Else this is a standard .arr .reduce search for unique values
                            else {
                                a_vReturnVal = x.reduce(function (acc, v) {
                                    if (acc.indexOf(v) === -1 && (!oOptions.caseInsensitive || acc.indexOf((v + "").toLowerCase()) === -1)) {
                                        acc.push(v);
                                    }
                                    return acc;
                                }, []);
                            }
                        }

                        return a_vReturnVal;
                    }, //# type.arr.unique


                    //#########
                    /** Determines the matching entries within the passed values.
                     * @function ish.type.arr.matches
                     * @param {variant[]} x Value representing the array to compare.
                     * @param {variant[]} y Value representing the reference array.
                     * @returns {variant[]} Value representing the passed values' matching entries.
                     */ //#####
                    matches: function (x, y) {
                        var a_vReturnVal = [];

                        if (core.type.arr.is(x) && core.type.arr.is(y)) {
                            a_vReturnVal = x.filter(function (v) {
                                return y.indexOf(v) !== -1;
                            });
                        }

                        return a_vReturnVal;
                    }, //# type.arr.matches


                    //#########
                    /** Randomizes the entries within the passed value.
                     * @function ish.type.arr.randomize
                     * @param {variant[]} x Value to interrogate.
                     * @returns {variant[]} Value representing the passed value's randomized entries.
                     */ //#####
                    randomize: function (x) {
                        var a_vReturnVal = [];

                        if (core.type.arr.is(x)) {
                            a_vReturnVal = x.sort(function (/*a, b*/) {
                                return Math.random() >= 0.5 ? -1 : 1;
                            });
                        }

                        return a_vReturnVal;
                    }, //# type.arr.randomize


                    //#########
                    /** Extracts the passed paths from the within the passed value.
                     * @function ish.type.arr.extract
                     * @param {variant[]} x Value to interrogate.
                     * @param {string|Array<string>} a_vPath Value representing the path to the requested property as a period-delimited string (e.g. "parent.child.array.0.key") or an array of strings.
                     * @returns {variant[]} Value representing the extracted entries from the passed value.
                     */ //#####
                    extract: function (x, vPath) {
                        var i,
                            o_vReturnVal = []
                        ;

                        //#
                        if (core.type.arr.is(x)) {
                            for (i = 0; i < x.length; i++) {
                                o_vReturnVal.push(core.resolve(x[i], vPath));
                            }
                        }

                        return o_vReturnVal;
                    }, //# type.arr.extract


                    //#########
                    /** Counts the instances of the passed value within the passed collection.
                     * @function ish.type.arr.countOf
                     * @param {variant[]} x Value representing the array to interrogate.
                     * @param {variant} vValue Value representing the entries to count.
                     * @returns {integer} Value representing the count of the passed value within the passed collection.
                     */ //#####
                    countOf: function(x, vValue) {
                        var iReturnVal = 0;

                        if (core.type.arr.is(x)) {
                            iReturnVal = x.filter(function (v) {
                                return v === vValue;
                            }).length;
                        }

                        return iReturnVal;
                    }, //# type.arr.countOf


                    sort: function () {
                        //# Natural Sort implementation for feeding into Array.sort()
                        //#     FROM: https://stackoverflow.com/questions/15478954/sort-array-elements-string-with-numbers-natural-sort/15479354#15479354
                        function naturalCompare(a, b) {
                            var an, bn, nn,
                                ax = [],
                                bx = []
                            ;

                            a.replace(/(\d+)|(\D+)/g, function(_, $1, $2) { ax.push([$1 || Infinity, $2 || ""]); });
                            b.replace(/(\d+)|(\D+)/g, function(_, $1, $2) { bx.push([$1 || Infinity, $2 || ""]); });

                            while (ax.length && bx.length) {
                                an = ax.shift();
                                bn = bx.shift();
                                nn = (an[0] - bn[0]) || an[1].localeCompare(bn[1]);
                                if (nn) return nn;
                            }

                            return (ax.length - bx.length);
                        } //# naturalCompare


                        //#
                        return core.extend(
                            //#########
                            /** Sorts the entries in the passed value into a new array via Javascript's default sort.
                             * @function ish.type.arr.sort
                             * @param {variant[]} x Value representing the array to interrogate.
                             * @param {boolean} bReverse Value representing if the entries are to be reversed.
                             * @returns {variant[]} Value the entries in the passed value into a new array via Javascript's default sort.
                             * @see {@link https://stackoverflow.com/questions/15478954/sort-array-elements-string-with-numbers-natural-sort/15479354#15479354|StackOverflow.com}
                             */ //#####
                            function (x, bReverse) {
                                var a_vReturnVal = core.type.arr.mk(x).slice(0).sort();
                                return (bReverse ? a_vReturnVal.reverse() : a_vReturnVal);
                            }, {
                                //#########
                                /** Sorts the entries in the passed value into a new array via a natural sort.
                                 * @function ish.type.arr.sort:natural
                                 * @param {variant[]} x Value representing the array to interrogate.
                                 * @param {boolean} bReverse Value representing if the entries are to be reversed.
                                 * @returns {variant[]} Value representing the entries in the passed value into a new array via a natural sort.
                                 * @see {@link https://stackoverflow.com/questions/15478954/sort-array-elements-string-with-numbers-natural-sort/15479354#15479354|StackOverflow.com}
                                 */ //#####
                                natural: function (a_vArray, bReverse) {
                                    if (core.type.arr.is(a_vArray)) {
                                        var a_vReturnVal = a_vArray.slice(0).sort(naturalCompare);
                                        return (bReverse === true ? a_vReturnVal.reverse() : a_vReturnVal);
                                    }
                                }, //# natural


                                //#########
                                /** Sorts the entries in the passed value into a new array via the comparator.
                                 * @function ish.type.arr.sort:by
                                 * @param {variant[]} x Value representing the array to interrogate.
                                 * @param {object} [oOptions] Value representing the following options:
                                 *      @param {string} [oOptions.path=""] Value representing the path to the requested property as a period-delimited string (e.g. "parent.child.array.0.key") or an array of strings.
                                 *      @param {boolean} [oOptions.reverse=false] Value representing if the entries are to be reversed.
                                 *      @param {function} [oOptions.compare=function (a, b) { return ish.resolve(a, oOptions.path) > ish.resolve(b, oOptions.path); }] Value representing the function that implements the compare, accepting 2 arguments (<code>a</code>, <code>b</code>) and returning truthy if <code>a > b</code>.
                                 * @returns {variant[]} Value representing the entries in the passed value into a new array via the comparator.
                                 */ //#####
                                by: function (a_vArray, vOptions) {
                                    var a_vReturnVal /*= undefined*/,
                                        oOptions = core.type.obj.mk(vOptions),
                                        sPath = core.type.str.mk(oOptions.path),
                                        bReverse = (oOptions.reverse === true),
                                        fnCompare = core.type.fn.mk(oOptions.compare, function (a, b) {
                                            var vA = core.resolve(a, sPath),
                                                vB = core.resolve(b, sPath),
                                                iReturn = 0
                                            ;

                                            //#
                                            if (vA < vB) {
                                                iReturn = 1;
                                            }
                                            else if (vA > vB) {
                                                iReturn = -1;
                                            }
                                            return (iReturn * (bReverse ? 1 : -1));
                                        })
                                    ;

                                    //# If the caller passed in a valid arr.is
                                    if (core.type.arr.is(a_vArray, true)) {
                                        //# Clone the a_vArray into our a_vReturnVal
                                        a_vReturnVal = a_vArray.slice(0);
                                        a_vReturnVal.sort(fnCompare);
                                    }

                                    return a_vReturnVal;
                                } //# by
                            }
                        );
                    }() //# type.arr.sort
                }, //# core.type.arr


                //# cp, clone, toArr, join, mv, rm, has
                obj: function () {
                    //#
                    function doCopy(oSource, oFromTo, a_sOwnKeys) {
                        var sKey, i,
                            oReturnVal = {}
                        ;

                        for (i = 0; i < a_sOwnKeys.length; i++) {
                            sKey = a_sOwnKeys[i];
                            oReturnVal[oFromTo[sKey]] = oSource[sKey];
                        }

                        return oReturnVal;
                    } //# doCopy

                    function objCompare(oOptions) {
                        var fnCompares = [];

                        //#
                        fnCompares.push(
                            oOptions.useCoercion ?
                            function (vSource, vCompare) { return vSource == vCompare; } :
                            function (vSource, vCompare) { return vSource === vCompare; }
                        );
                        fnCompares.push(
                            oOptions.caseInsensitive ? (
                                oOptions.trim ?
                                function (vSource, vCompare) { return core.type.str.is(vSource) && core.type.str.cmp(vSource, vCompare); } :
                                function (vSource, vCompare) { return core.type.str.is(vSource) && core.type.str.eq(vSource, vCompare, true); }
                            ) : (
                                oOptions.trim ?
                                function (vSource, vCompare) { return core.type.str.is(vSource) && core.type.str.mk(vSource).trim() === core.type.str.mk(vCompare).trim(); } :
                                function (vSource, vCompare) { return core.type.str.is(vSource) && core.type.str.mk(vSource) === core.type.str.mk(vCompare); }
                            )
                        );

                        //# If we are still to recurse, setup the fnCompares to do so
                        if (core.type.int.is(oOptions.maxDepth) && oOptions.maxDepth !== 0) {
                            fnCompares.push(
                                function (vSource, vCompare) {
                                    return core.type.obj.is(vSource) &&
                                        core.type.obj.eq(vSource, vCompare, core.extend({}, oOptions, { maxDepth: oOptions.maxDepth - 1 }))
                                    ;
                                }
                            );
                        }

                        //# If there is a .compare .fn in the oOptions, .push it into the fnCompares
                        if (core.type.fn.is(oOptions.compare)) {
                            fnCompares.push(oOptions.compare);
                        }

                        //# Return the comparison wrapper function to the caller
                        return function (vSource, vCompare) {
                            var i,
                                bReturnVal = false
                            ;

                            //#
                            for (i = 0; i < fnCompares.length; i++) {
                                if (fnCompares[i](vSource, vCompare)) {
                                    bReturnVal = true;
                                    break;
                                }
                            }

                            return bReturnVal;
                        };
                    } //# objCompare

                    return {
                        is: {
                            //#########
                            /** Determines if the passed value conforms to the passed interface.
                             * @function ish.type.obj.is:interface
                             * @param {object|function} x Value to interrogate.
                             * @param {object} oInterface Value representing the interface, where each value represents a string representing a type under <code>ish.type</code> or a value representing the function that implements the test, accepting 1 argument (<code>x</code>) and returning truthy if the value is of a valid type.
                             * @param {boolean} [bStrict=false] Value indicating if the passed value is not allowed to have any additional keys outside of the those in the passed interface.
                             * @returns {boolean} Value representing if the passed value conforms to the passed interface.
                             */ //#####
                            interface: function (x, oInterface, bStrict) {
                                var a_sKeys, sKey, i,
                                    bReturnVal = (core.type.obj.is(x, { allowFn: true }) && core.type.obj.is(oInterface, true))
                                ;

                                //# If the caller passed in valid values, pull the a_sKeys from the oInterface
                                if (bReturnVal) {
                                    a_sKeys = core.type.obj.ownKeys(oInterface);

                                    //# Traverse the a_sKeys, pulling each sKey as we go
                                    for (i = 0; i < a_sKeys.length; i++) {
                                        sKey = a_sKeys[i];

                                        //# If the current sKey .is a .str, try to .resolve it to core.type[sKey].is
                                        if (core.type.str.is(oInterface[sKey])) {
                                            oInterface[sKey] = core.resolve(core.type, [oInterface[sKey], "is"]);
                                        }

                                        //# If x doesn't .hasOwnProperty, or the current sKey .is(n't) a .fn or the oInterface's test returns falsy, flip our bReturnVal and fall from the loop
                                        if (sKey in x || //!x.hasOwnProperty(sKey) ||
                                            !core.type.fn.is(oInterface[sKey]) ||
                                            !oInterface[sKey](x[sKey])
                                        ) {
                                            bReturnVal = false;
                                            break;
                                        }
                                    }

                                    //# If x passed all tests above and we are supposed to be bStrict, ensure x has no other a_sKeys
                                    //#     NOTE: As we tested all of the oInterface's a_sKeys above, so long as x's .ownKeys are the same .length we know there are no additional keys in x
                                    if (bReturnVal && bStrict === true) {
                                        bReturnVal = (core.type.obj.ownKeys(x).length === a_sKeys.length);
                                    }
                                }

                                return bReturnVal;
                            }, //# type.obj.is.interface

                            //#########
                            /** Determines if the passed value has any circular references.
                             * @function ish.type.obj.is:cyclic
                             * @param {object|function} x Value to interrogate.
                             * @param {boolean} [bReturnReport=false] Value indicating if an array of circular references is to be returned.
                             * @returns {boolean|object[]} Value representing if the passed value has any circular references or an array of objects describing any circular references.
                             */ //#####
                            cyclic: function cr(x, bReturnReport) {
                                var a_sKeys = [],
                                    a_oStack = [],
                                    wm_oSeenObjects = new WeakMap(), //# see: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/WeakMap
                                    oReturnVal = {
                                        found: false,
                                        report: []
                                    }
                                ;

                                //# Ensure the passed bReturnReport .is .bool, defaulted to false
                                bReturnReport = core.type.bool.mk(bReturnReport, false);

                                //# Setup the recursive logic to locate any circular references while kicking off the initial call
                                (function doIsCyclic(oTarget, sKey) {
                                    var a_sTargetKeys, sCurrentKey, i;

                                    //# If we've seen this oTarget before, flip our .found to true
                                    if (wm_oSeenObjects.has(oTarget)) {
                                        oReturnVal.found = true;

                                        //# If we are to bReturnReport, add the entries into our .report
                                        if (bReturnReport) {
                                            oReturnVal.report.push({
                                                instance: oTarget,
                                                source: a_sKeys.slice(0, a_oStack.indexOf(oTarget) + 1).join('.'),
                                                duplicate: a_sKeys.join('.') + "." + sKey
                                            });
                                        }
                                    }
                                    //# Else if oTarget is an instanceof Object, determine the a_sTargetKeys and .set our oTarget into the wm_oSeenObjects
                                    else if (oTarget instanceof Object) {
                                        a_sTargetKeys = Object.keys(oTarget);
                                        wm_oSeenObjects.set(oTarget /*, undefined*/);

                                        //# If we are to bReturnReport, .push the  current level's/call's items onto our stacks
                                        if (bReturnReport) {
                                            if (sKey) { a_sKeys.push(sKey); }
                                            a_oStack.push(oTarget);
                                        }

                                        //# Traverse the a_sTargetKeys, pulling each into sCurrentKey as we go
                                        //#     NOTE: If you want all properties, even non-enumerables, see Object.getOwnPropertyNames() so there is no need to call .hasOwnProperty (per: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/keys)
                                        for (i = 0; i < a_sTargetKeys.length; i++) {
                                            sCurrentKey = a_sTargetKeys[i];

                                            //# If we've already .found a circular reference and we're not bReturnReport, fall from the loop
                                            if (oReturnVal.found && !bReturnReport) {
                                                break;
                                            }
                                            //# Else if the sCurrentKey is an instanceof Object, recurse to test
                                            else if (oTarget[sCurrentKey] instanceof Object) {
                                                doIsCyclic(oTarget[sCurrentKey], sCurrentKey);
                                            }
                                        }

                                        //# .delete our oTarget into the wm_oSeenObjects
                                        //#     NOTE: Not 100% sure why this is required, but without it .source is missing above and less .duplicates are found(!?)
                                        wm_oSeenObjects.delete(oTarget);

                                        //# If we are to bReturnReport, .pop the current level's/call's items off our stacks
                                        if (bReturnReport) {
                                            if (sKey) { a_sKeys.pop(); }
                                            a_oStack.pop();
                                        }
                                    }
                                }(x, '')); //# doIsCyclic

                                return (bReturnReport ? oReturnVal.report : oReturnVal.found);
                            } //# type.obj.is.cyclic
                        }, //# type.obj.is

                        //#########
                        /** Determines if the passed values are equal.
                         * @function ish.type.obj.eq
                         * @param {object|function} x Value to interrogate.
                         * @param {object|function} y Value to interrogate.
                         * @param {object} [oOptions] Value representing the following options:
                         *      @param {function} [oOptions.compare=undefined] Value representing the function that implements the compare, accepting 2 arguments (<code>a</code>, <code>b</code>) and returning truthy if <code>a > b</code>.
                         *      @param {boolean} [oOptions.useCoercion=true] Value representing if coercion is to be used during comparisons.
                         *      @param {boolean} [oOptions.caseInsensitive=false] Value representing if the values are to be compared in a case-insensitive manor.
                         *      @param {boolean} [oOptions.trimWhitespace=false] Value representing if leading and trailing whitespace is to be trimmed prior to comparison.
                         *      @param {boolean|integer} [oOptions.maxDepth=0] Value representing if a deep comparison is to occur. <code>false</code>/<code>0</code> performs a shallow comparison, a positive integer indicates the max depth to perform a deep comparison to, <code>true</code> and all other integer values perform a deep comparison to an unlimited depth.
                         * @returns {boolean} Value representing if the passed values are equal.
                         */ //#####
                        eq: function (x, y, oOptions) {
                            var fnCompare, i,
                                a_sSourceKeys = core.type.obj.ownKeys(x),
                                a_sCompareKeys = core.type.obj.ownKeys(y),
                                bReturnVal = false
                            ;

                            //# If both x and y's .ownkeys match (ignoring the order of the respective arrays)
                            if (core.type.arr.cmp(a_sSourceKeys, a_sCompareKeys /*, false*/)) {
                                //# Set the defaults for the passed oOptions (forcing it into an .is .obj as we go) then calculate our fnCompare
                                oOptions = core.extend({
                                    //compare: core.type.fn.noop,
                                    useCoercion: true,
                                    caseInsensitive: false,
                                    trimWhitespace: false,
                                    maxDepth: 0
                                }, oOptions);
                                oOptions.maxDepth = (
                                    oOptions.maxDepth === false ?
                                    0 :
                                        oOptions.maxDepth === true ?
                                        -1 :
                                            core.type.int.mk(oOptions.maxDepth)
                                );
                                fnCompare = objCompare(oOptions);
                                bReturnVal = true;

                                //#
                                for (i = 0; i < a_sSourceKeys.length; i++) {
                                    if (!fnCompare(x[a_sSourceKeys[i]], y[a_sSourceKeys[i]])) {
                                        bReturnVal = false;
                                        break;
                                    }
                                }
                            }

                            return bReturnVal;
                        }, //# type.obj.eq


                        //#########
                        /** Determines the unique entries within the passed value.
                         * @function ish.type.obj.unique
                         * @param {object[]|function[]} x Value representing the array to compare.
                         * @param {string|string[]} vPaths Value representing the values to interrogate.
                         * @param {object} [oOptions] Value representing the following options:
                         *      @param {function} [oOptions.compare=undefined] Value representing the function that implements the compare, accepting 2 arguments (<code>a</code>, <code>b</code>) and returning truthy if <code>a > b</code>.
                         *      @param {boolean} [oOptions.useCoercion=true] Value representing if coercion is to be used during comparisons.
                         *      @param {boolean} [oOptions.caseInsensitive=false] Value representing if the values are to be compared in a case-insensitive manor.
                         *      @param {boolean} [oOptions.trimWhitespace=false] Value representing if leading and trailing whitespace is to be trimmed prior to comparison.
                         *      @param {boolean|integer} [oOptions.maxDepth=0] Value representing if a deep comparison is to occur. <code>false</code>/<code>0</code> performs a shallow comparison, a positive integer indicates the max depth to perform a deep comparison to, <code>true</code> and all other integer values perform a deep comparison to an unlimited depth.
                         * @returns {variant[]} Value representing the passed values' unique entries.
                         */ //#####
                        unique: function (x, vPaths, oOptions) {
                            var fnCompare, vCurrentVal, i, j, k, bFound,
                                a_sPaths = core.type.arr.mk(vPaths, [vPaths]),
                                a_vReturnVal = []
                            ;

                            //# Set the defaults for the passed oOptions (forcing it into an .is .obj as we go) then calculate our fnCompare
                            oOptions = core.extend({
                                //compare: core.type.fn.noop,
                                useCoercion: true,
                                caseInsensitive: false,
                                trimWhitespace: false,
                                maxDepth: 0
                            }, oOptions);
                            oOptions.maxDepth = (
                                oOptions.maxDepth === false ?
                                0 :
                                    oOptions.maxDepth === true ?
                                    -1 :
                                        core.type.int.mk(oOptions.maxDepth)
                            );
                            fnCompare = objCompare(oOptions);

                            //# If the passed x value .is and .arr
                            if (core.type.arr.is(x, true)) {
                                //# Traverse our a_sPaths
                                for (i = 0; i < a_sPaths.length; i++) {
                                    //# Traverse the passed x value, flipping bFound to false on each loop
                                    for (j = 0; j < x.length; j++) {
                                        bFound = false;

                                        //# If the current x index .hasOwnProperty, set the vCurrentVal and look at it further
                                        if (x[j] && x[j].hasOwnProperty(a_sPaths[i])) {
                                            vCurrentVal = x[j];

                                            //# Traverse our a_vReturnVal, fnCompare'ing each entry with the vCurrentVal and falling from the loop if it's bFound
                                            for (k = 0; k < a_vReturnVal.length; k++) {
                                                if (fnCompare(core.resolve(a_vReturnVal[k], a_sPaths[i]), core.resolve(vCurrentVal, a_sPaths[i]))) {
                                                    bFound = true;
                                                    break;
                                                }
                                            }
                                        }

                                        //# If the vCurrentVal wasn't bFound in our a_vReturnVal, .push the vCurrentVal in
                                        if (!bFound) {
                                            a_vReturnVal.push(vCurrentVal);
                                        }
                                    }

                                    //#
                                    x = a_vReturnVal;
                                    a_vReturnVal = [];
                                }

                                //#
                                a_vReturnVal = x;
                            }

                            return a_vReturnVal;
                        }, //# type.obj.unique

                        //cmp:

                        //#########
                        /** Copies the passed value into a new instance.
                         * @function ish.type.obj.cp
                         * @param {variant[]} x Value representing the object to copy.
                         * @param {boolean|integer} [vMaxDepth=0] Value representing if a deep copy is to occur. <code>false</code>/<code>0</code> performs a shallow copy, a positive integer indicates the max depth to perform a deep copy to, <code>true</code> and all other integer values perform a deep copy to an unlimited depth.
                         * @returns {variant[]} Value representing the passed value as a new instance.
                         */ //#####
                        cp: function (x, vMaxDepth) {
                            var oReturnVal /* = _undefined */;

                            //# If the caller passed in a valid o(bject), .extend our oReturnVal as a new .is .obj
                            if (core.type.obj.is(x)) {
                                oReturnVal = (vMaxDepth ?
                                    core.extend(vMaxDepth, {}, x) :
                                    core.extend({}, x)
                                );
                            }

                            return oReturnVal;
                        }, //# type.obj.cp


                        //#########
                        /** Copies the referenced keys into a new instance.
                         * @function ish.type.obj.clone
                         * @param {variant[]} x Value representing the object to copy.
                         * @param {string|string[]|object} [vKeysOrFromTo] Value representing the key(s) to copy into the new object, with the option to remap keys when an object is passed.
                         * @returns {variant[]} Value representing the passed value as a new instance.
                         */ //#####
                        clone: function (x, vKeysOrFromTo) {
                            var i, a_sOwnKeys,
                                oFromTo = {},
                                vReturnVal /*= undefined*/
                            ;

                            //#
                            if (core.type.str.is(vKeysOrFromTo)) {
                                vKeysOrFromTo = [vKeysOrFromTo];
                            }

                            /*
                            //# If the caller passed in an .is .arr of keys, set it into our a_sOwnKeys
                            //#     NOTE: `core.type.obj.is` returns true for arrays, so this test must come first
                            if (core.type.arr.is(vKeysOrFromTo)) {
                                a_sOwnKeys = vKeysOrFromTo;
                                oFromTo = {};

                                //# Traverse a_sOwnKeys, setting each into our (flat) oFromTo definition
                                for (i = 0; i < a_sOwnKeys.length; i++) {
                                    oFromTo[a_sOwnKeys[i]] = a_sOwnKeys[i];
                                }
                            }
                            //# Else if the caller passed in a mapping .is .obj, set it into our oFromTo definition and collect a_sOwnKeys
                            else if (core.type.obj.is(vKeysOrFromTo)) {
                                oFromTo = vKeysOrFromTo;
                                a_sOwnKeys = core.type.obj.ownKeys(oFromTo);
                            }
                            */

                            //# If the caller passed in a mapping .is .obj, set it into our oFromTo definition and collect a_sOwnKeys
                            //#     NOTE: `core.type.obj.is` returns true for arrays, `{ strict: true }` is required
                            if (core.type.obj.is(vKeysOrFromTo, { strict: true })) {
                                oFromTo = vKeysOrFromTo;
                                a_sOwnKeys = core.type.obj.ownKeys(oFromTo);
                            }
                            //# Else we need to look to the other sources
                            else {
                                //# If the caller passed in an .is .arr of keys, set it into our a_sOwnKeys
                                if (core.type.arr.is(vKeysOrFromTo)) {
                                    a_sOwnKeys = vKeysOrFromTo;
                                }
                                //# Else if x .is .obj, pull it's .ownKeys
                                else if (core.type.obj.is(x)) {
                                    a_sOwnKeys = core.type.obj.ownKeys(x);
                                }

                                //# If we were able to collect the a_sOwnKeys above
                                if (a_sOwnKeys) {
                                    //# Traverse a_sOwnKeys, setting each into our (flat) oFromTo definition
                                    for (i = 0; i < a_sOwnKeys.length; i++) {
                                        oFromTo[a_sOwnKeys[i]] = a_sOwnKeys[i];
                                    }
                                }
                            }

                            //# If vKeysOrFromTo was either an .is .arr or .is .obj
                            if (a_sOwnKeys) {
                                //# If the passed x .is .arr, set our vReturnVal to an array
                                if (core.type.arr.is(x)) {
                                    vReturnVal = [];

                                    //# Traverse the x, .push'ing each .doCopy into our vReturnVal
                                    for (i = 0; i < x.length; i++) {
                                        vReturnVal.push(doCopy(x[i], oFromTo, a_sOwnKeys));
                                    }
                                }
                                //# Else if the passed x .is .obj, .doCopy directly into our vReturnVal
                                else if (core.type.obj.is(x)) {
                                    vReturnVal = doCopy(x, oFromTo, a_sOwnKeys);
                                }
                            }

                            return vReturnVal;
                        }, //# type.obj.clone


                        //#########
                        /** Removes any characters from object keys that require bracket-style notation (e.g. <code>oObject["2 this is\n1 valid key"]</code> becomes <code>oObject.thisis1validkey</code>)
                         * @function ish.type.obj.rekey
                         * @param {object|function|object[]|function[]} x Value representing the object(s) to rekey.
                         * @param {string|boolean|object} [vOptions] Value representing the <code>replacementChar</code>, if we are to <code>deleteRekeyed</code> values or the following options:
                         *      @param {string} [oOptions.replacementChar=""] Value representing the char (or characters) used to replace any unfriendly characters.
                         *      @param {string} [oOptions.trackRekeyed=""] Value representing the key to store the remapped key data.
                         *      @param {boolean} [oOptions.deleteRekeyed=false] Value representing if the original key/value is to be <code>delete</code>d from the object.
                         * @returns {object|function|object[]|function[]} Value representing the rekeyed object(s) (i.e. the passed <code>x</code> is returned for convenience).
                         */ //#####
                        rekey: function (x, vOptions) {
                            var a_sKeys, oReplacedKeys, oCurrent, oOptions, sFriendlyKeyName, sChar, i, j,
                                a_oX = (core.type.arr.is(x) ? x : [x])
                            ;

                            //# Process the passed vOptions into oOptions
                            oOptions = core.extend({
                                replacementChar: (core.type.str.is(vOptions) ? vOptions : ""),
                                trackRekeyed: "",
                                deleteRekeyed: (core.type.bool.is(vOptions) ? vOptions : false)
                            }, vOptions);
                            sChar = oOptions.replacementChar || "_";

                            //# Traverse the a_oX(s), pulling the oCurrent a_oX, it's a_sKeys and resetting oReplacedKeys as we go
                            for (i = 0; i < a_oX.length; i++) {
                                oCurrent = a_oX[i];
                                a_sKeys = core.type.obj.ownKeys(oCurrent);
                                oReplacedKeys = {};

                                //#
                                if (core.type.arr.is(a_sKeys, true)) {
                                    //# Traverse the a_sKeys for the oCurrent a_oX, determining the sFriendlyKeyName as we go
                                    for (j = 0; j < a_sKeys.length; j++) {
                                        sFriendlyKeyName = a_sKeys[j].replace(/[^a-z0-9_$]/gi, oOptions.replacementChar).replace(/^[0-9]{1,}/, oOptions.replacementChar);

                                        //# If the oCurrent a_sKeys has non-friendly characters
                                        if (sFriendlyKeyName !== a_sKeys[j]) {
                                            //# If the sFriendlyKeyName already exists in our oCurrent a_oX, append as many sChar's as required to make it unique
                                            if (a_sKeys.indexOf(sFriendlyKeyName) !== -1) {
                                                do {
                                                    sFriendlyKeyName += sChar;
                                                } while (a_sKeys.indexOf(sFriendlyKeyName) !== -1);
                                            }

                                            //# Place our sFriendlyKeyName mapping into oReplacedKeys and set a new value in the oCurrent a_oX
                                            oReplacedKeys[sFriendlyKeyName] = { original: a_sKeys[j], remapped: sFriendlyKeyName };
                                            oCurrent[sFriendlyKeyName] = oCurrent[a_sKeys[j]];

                                            //# If we are to .deleteRekeyed, remove the a_sKeys[j] now
                                            if (oOptions.deleteRekeyed) {
                                                delete oCurrent[a_sKeys[j]];
                                            }
                                        }
                                    }
                                }

                                //# If we are .trackRekeyed, attach the oReplacedKeys under the set key name
                                if (core.type.str.is(oOptions.trackRekeyed, true)) {
                                    oCurrent[oOptions.trackRekeyed] = oReplacedKeys;
                                }
                            }

                            //# For convenience, return the passed (though updated by reference) x to the caller
                            return x;
                        }, //# type.obj.rekey


                        //#########
                        /** Flattens the structure of an object into a single depth object as <code>parent_child_grandchild</code> using <code>ish.type.obj.rekey</code> to preprocess key names.
                         * @function ish.type.obj.flatten
                         * @param {object|function|object[]|function[]} x Value representing the object(s) to rekey.
                         * @param {string|boolean|object} [vOptions] Value representing the <code>replacementChar</code>, if we are to <code>deleteRekeyed</code> values or the following options:
                         *      @param {string} [oOptions.replacementChar=""] Value representing the char (or characters) used to replace any unfriendly characters.
                         *      @param {string} [oOptions.trackRekeyed=""] Value representing the key to store the remapped key data.
                         *      @param {boolean} [oOptions.deleteRekeyed=true] Value representing if the original key/value is to be <code>delete</code>d from the object.
                         * @returns {object|function|object[]|function[]} Value representing the rekeyed object(s) (i.e. the passed <code>x</code> is returned for convenience).
                         */ //#####
                        flatten: function f(x, vOptions) {
                            let a_sKeys, a_sCurrentKeys, oCurrent, i, j,
                                oReturnVal /* = _undefined */
                            ;

                            //#
                            vOptions = core.extend({
                                deleteRekeyed: true
                            }, vOptions);

                            //# If the passed x .is .obj, .rekey it (passing along the vOptions) and collect the a_sKeys
                            if (core.type.obj.is(x, true)) {
                                oReturnVal = core.type.obj.rekey(x, vOptions);
                                a_sKeys = core.type.obj.ownKeys(oReturnVal);

                                //# Traverse the a_sKeys
                                //#     NOTE: We don't need to test for a_sKeys being an .arr due to the .is .obj test above
                                for (i = 0; i < a_sKeys.length; i++) {
                                    //#
                                    //#     NOTE: Shell environment variable naming restrictions: [a-zA-Z_]{1,}[a-zA-Z0-9_]{0,}
                                    if (a_sKeys[i].indexOf("$") > -1) {
                                        //# TODO
                                    }

                                    //# If the current a_sKeys .is an .obj (including .arr), pull the oCurrent object and it's .ownKeys
                                    if (core.type.obj.is(oReturnVal[a_sKeys[i]], true)) { // || core.type.arr.is(oReturnVal[a_sKeys[i]])) {
                                        oCurrent = core.type.obj.flatten(oReturnVal[a_sKeys[i]], vOptions);
                                        a_sCurrentKeys = core.type.obj.ownKeys(oCurrent);

                                        //# If we have a_sCurrentKeys traverse them, placing each key under a underscored path
                                        //#     NOTE: This test is required due to no testing of oCurrent prior to .ownKeys
                                        if (core.type.arr.is(a_sCurrentKeys, true)) {
                                            for (j = 0; j < a_sCurrentKeys.length; j++) {
                                                oReturnVal[a_sKeys[i] + "_" + a_sCurrentKeys[j]] = oCurrent[a_sCurrentKeys[j]];
                                            }
                                        }

                                        //# delete the current a_sKeys[i] as it was recreated above as a flat structure
                                        delete oReturnVal[a_sKeys[i]];
                                    }
                                }
                            }

                            return oReturnVal;
                        }, //# type.obj.flatten


                        //#########
                        /** Mutates the passed value into an array.
                         * @function ish.type.obj.toArr
                         * @param {object|function} x Value representing the object to mutate.
                         * @param {string} [sSetKeyAs=undefined] Value representing the name of the key to set the original key's name under.
                         * @returns {variant[]} Value representing the passed value mutated into an array.
                         */ //#####
                        toArr: function (x, sSetKeyAs) {
                            var a_vReturnVal, i,
                                bSetKey = core.type.str.is(sSetKeyAs, true),
                                a_sKeys = (core.type.obj.is(x, { allowFn: true }) ? Object.keys(x) : _undefined)
                            ;

                            //# If the passed x .is .obj, setup our a_vReturnVal
                            if (a_sKeys) {
                                a_vReturnVal = [];

                                //# Traverse the a_sKeys, .push'ing each into our a_vReturnVal and bSetKey (if necessary)
                                for (i = 0; i < a_sKeys.length; i++) {
                                    a_vReturnVal.push(x[a_sKeys[i]]);
                                    if (bSetKey) {
                                        x[a_sKeys[i]][sSetKeyAs] = a_sKeys[i];
                                    }
                                }
                            }

                            return a_vReturnVal;
                        }, //# type.obj.toArr


                        //#########
                        /** Concat's arrays and copies properties into an object.
                         * @function ish.type.obj.concat
                         * @param {object|function} vTarget Value representing the target object to receive properties.
                         * @param {object|function|object[]|function[]} vSource(s) representing the source object(s) whose arrays will be concat'ed and properties will be copied into the target.
                         * @returns {object|function} Value representing the passed target object.
                         */ //#####
                        concat: function (vTarget, vSource, bConcatNonArrays) {
                            var vCurrentSource, a_sCurrentKeys, i, j,
                                a_vSources = core.type.arr.mk(vSource, [vSource])
                            ;

                            //# If the passed vTarget is valid
                            if (core.type.obj.is(vTarget, { allowFn: true })) {
                                //# Traverse the passed a_vSources, pulling the vCurrentSource and a_sCurrentKeys as we go
                                for (i = 0; i < a_vSources.length; i++) {
                                    vCurrentSource = a_vSources[i];
                                    a_sCurrentKeys = core.type.obj.ownKeys(vCurrentSource);

                                    //# If we were able to pull a_sCurrentKeys for the vCurrentSource, traverse them
                                    if (core.type.arr.is(a_sCurrentKeys, true)) {
                                        for (j = 0; j < a_sCurrentKeys.length; j++) {
                                            //# If our vTarget already .has the a_sCurrentKeys
                                            if (core.type.obj.has(vTarget, a_sCurrentKeys[j], false)) {
                                                //# If vCurrentSource's a_sCurrentKeys .is .arr
                                                if (core.type.arr.is(vCurrentSource[a_sCurrentKeys[j]], true)) {
                                                    //# If the vTarget's a_sCurrentKeys .is .arr and it's not the same array as in vCurrentSource, concat the values
                                                    if (core.type.arr.is(vTarget[a_sCurrentKeys[j]])) {
                                                        if (vTarget[a_sCurrentKeys[j]] !== vCurrentSource[a_sCurrentKeys[j]]) {
                                                            vTarget[a_sCurrentKeys[j]] = vTarget[a_sCurrentKeys[j]].concat(vCurrentSource[a_sCurrentKeys[j]]);
                                                        }
                                                    }
                                                    //# Else if we are bConcatNonArrays, transform the vTarget's a_sCurrentKeys into an array including the vCurrentSource's value
                                                    else if (bConcatNonArrays) {
                                                        vTarget[a_sCurrentKeys[j]] = [vTarget[a_sCurrentKeys[j]]].concat(vCurrentSource[a_sCurrentKeys[j]]);
                                                    }
                                                }
                                                //# Else if we are bConcatNonArrays
                                                else if (bConcatNonArrays) {
                                                    //# If the vTarget's a_sCurrentKeys .is .arr, push the vCurrentSource's value in
                                                    if (core.type.arr.is(vTarget[a_sCurrentKeys[j]])) {
                                                        vTarget[a_sCurrentKeys[j]].push(vCurrentSource[a_sCurrentKeys[j]]);
                                                    }
                                                    //# Else transform the vTarget's a_sCurrentKeys into an array including the vCurrentSource's value
                                                    else {
                                                        vTarget[a_sCurrentKeys[j]] = [
                                                            vTarget[a_sCurrentKeys[j]],
                                                            vCurrentSource[a_sCurrentKeys[j]]
                                                        ];
                                                    }
                                                }
                                            }
                                            //# Else this a_sCurrentKeys is new for our vTarget, so set it from the vCurrentSource
                                            else {
                                                vTarget[a_sCurrentKeys[j]] = vCurrentSource[a_sCurrentKeys[j]];
                                            }
                                        }
                                    }
                                }
                            }

                            return vTarget;
                        },

                        //#########
                        /** Joins the passed value into a string.
                         * @function ish.type.obj.join
                         * @param {object|function} x Value representing the object to join.
                         * @param {string} [sDelimiter=""] Value representing the delimiter to be included between values.
                         * @param {string} [sKeyDelimiter=undefined] Value representing if the keys are to be included within the string, defining the delimiter to be included between keys and their related values.
                         * @returns {variant[]} Value representing the passed value mutated into an array.
                         */ //#####
                        join: function (x, sDelimiter, sKeyDelimiter) {
                            var i,
                                a_sOwnKeys = core.type.obj.ownKeys(x),
                                bIncludeKeys = core.type.str.is(sKeyDelimiter),
                                sReturnVal = ""
                            ;

                            //# Ensure the passed sDelimiter .is a .str, defaulting to "" if need be
                            sDelimiter = core.type.str.mk(sDelimiter);

                            //# If we were able to collect the passed x's a_sOwnKeys
                            if (a_sOwnKeys) {
                                //# If we are supposed to bIncludeKeys, build our sReturnVal accordingly
                                if (bIncludeKeys) {
                                    for (i = 0; i < a_sOwnKeys.length - 1; i++) {
                                        sReturnVal += a_sOwnKeys[i] + sKeyDelimiter + x[a_sOwnKeys[i]] + sDelimiter;
                                    }

                                    //# Append the final key onto our sReturnVal
                                    sReturnVal += a_sOwnKeys[i] + sKeyDelimiter;
                                }
                                //# Else we are not supposed to bIncludeKeys, building our sReturnVal accordingly
                                else {
                                    for (i = 0; i < a_sOwnKeys.length - 1; i++) {
                                        sReturnVal += x[a_sOwnKeys[i]] + sDelimiter;
                                    }
                                }

                                //# Append the final value onto our sReturnVal
                                sReturnVal += x[a_sOwnKeys[i]];
                            }

                            return sReturnVal;
                        }, //# type.obj.join


                        //#########
                        /** Moves the referenced keys into the passed new keys.
                         * @function ish.type.obj.mv
                         * @param {object|function|variant[]} x Value representing the object(s) to interrogate.
                         * @param {object} [oFromTo] Value representing the keys to move.
                         * @param {boolean} [bSetToUndefined=false] Value representing if the moved keys are to be <code>delete</code>d (<code>false</code>) or reset to <code>undefined</code> (<code>true</code>).
                         * @returns {boolean} Value representing if the keys have been moved.
                         */ //#####
                        mv: function (x, oFromTo, bSetToUndefined) {
                            var bReturnVal = core.type.obj.is(oFromTo, { nonEmpty: true });

                            //# If the caller passed in a valid oFromTo, toss our arguments into the oProtected.processObj
                            if (bReturnVal) {
                                bReturnVal = oProtected.processObj(x, oFromTo, bSetToUndefined);
                            }

                            return bReturnVal;
                        }, //# type.obj.mv


                        //#########
                        /** Determines if the referenced keys are present in the passed value.
                         * @function ish.type.obj.has
                         * @param {object|function} x Value representing the object to interrogate.
                         * @param {string|string[]} vKeys Value representing the key(s) to interrogate.
                         * @param {boolean} [bKeysArePaths=true] Value representing if the passed keys represent dot-delimited paths (e.g. <code>grandparent.parent.array.1.property</code>, see: {@link: ish.resolve}).
                         * @returns {boolean} Value representing if the referenced keys are present in the passed value.
                         */ //#####
                        has: function (x, vKeys, bKeysArePaths) {
                            var i,
                                a_sKeys = (core.type.arr.is(vKeys) ? vKeys : [vKeys]),
                                bReturnVal = core.type.obj.is(x, { allowFn: true })
                            ;

                            //# If x is valid
                            if (bReturnVal) {
                                //# If the bKeysArePaths traverse the a_sKeys accordingly
                                if (bKeysArePaths !== false) {
                                    for (i = 0; i < a_sKeys.length; i++) {
                                        //# If the current a_sKeys doesn't .existed in x, unflip our bReturnVal and fall from the loop
                                        if (!core.resolve(core.resolve.returnMetadata, x, a_sKeys[i]).existed) {
                                            bReturnVal = false;
                                            break;
                                        }
                                    }
                                }
                                //# Else we just have to traverse the a_sKeys as-is
                                else {
                                    for (i = 0; i < a_sKeys.length; i++) {
                                        //# If this a_sKeys isn't a .hasOwnProperty of x, unflip our bReturnVal and fall from the loop
                                        if (!x.hasOwnProperty(a_sKeys[i])) {
                                            bReturnVal = false;
                                            break;
                                        }
                                    }
                                }
                            }

                            return bReturnVal;
                        }, //# type.obj.has


                        //#########
                        /** Removes values set to <code>undefined</code> along with any referenced keys from the passed value.
                         * @function ish.type.obj.prune
                         * @param {object|function} x Value representing the object to interrogate.
                         * @param {string|string[]} [vAddlKeysToPrune] Value representing any additional key(s) to remove.
                         * @returns {boolean} Value representing the remaining keys from the passed value.
                         */ //#####
                        prune: function (x, vAddlKeysToPrune) {
                            var sCurrentKey, i,
                                a_sOwnKeys = core.type.obj.ownKeys(x),
                                oReturnVal = core.type.obj.empty()
                            ;

                            //# Ensure the passed vAddlKeysToPrune is an .arr
                            vAddlKeysToPrune = core.type.arr.mk(vAddlKeysToPrune, vAddlKeysToPrune);

                            //#
                            for (i = 0; i < a_sOwnKeys.length; i++) {
                                sCurrentKey = a_sOwnKeys[i];

                                if (x[sCurrentKey] !== _undefined && vAddlKeysToPrune.indexOf(sCurrentKey) === -1) {
                                    oReturnVal[sCurrentKey] = x[sCurrentKey];
                                }
                            }

                            return oReturnVal;
                        }, //# type.obj.prune


                        //#########
                        /** Determines the differences between the passed values.
                         * @function ish.type.obj.diff
                         * @param {object|function} x Value to interrogate.
                         * @param {object|function} y Value to interrogate.
                         * @param {object} [oOptions] Value representing the following options:
                         *      @param {function} [oOptions.compare=undefined] Value representing the function that implements the compare, accepting 2 arguments (<code>a</code>, <code>b</code>) and returning truthy if <code>a > b</code>.
                         *      @param {boolean} [oOptions.useCoercion=true] Value representing if coercion is to be used during comparisons.
                         *      @param {boolean} [oOptions.caseInsensitive=false] Value representing if the values are to be compared in a case-insensitive manor.
                         *      @param {boolean} [oOptions.trimWhitespace=false] Value representing if leading and trailing whitespace is to be trimmed prior to comparison.
                         *      @param {boolean} [oOptions.includeMissingKeys=false] Value representing if keys present in <code>y</code> but missing from <code>x</code> are to be included in the reported differences.
                         *      @param {boolean} [oOptions.caseInsensitiveKeys=false] Value representing if keys are to be treated as case insensitive.
                         *      @param {boolean} [oOptions.pruneUndefinedValues=false] Value representing if if keys with <code>undefined</code> as their value are to be removed from the return value.
                         * @returns {object} Value representing the differences between the passed values.
                         */ //#####
                        diff: function (x, y, oOptions) {
                            var fnCompare, i,
                                a_sSourceKeys = core.type.obj.ownKeys(x),
                                a_sCompareKeys = core.type.obj.ownKeys(y),
                                oReturnVal = core.type.obj.empty()
                            ;

                            //# If we have a_sSourceKeys and a_sCompareKeys (which also determines that the passed x and y .is .obj)
                            if (core.type.arr.is(a_sSourceKeys) && core.type.arr.is(a_sCompareKeys)) {
                                //# Set the defaults for the passed oOptions (forcing it into an .is .obj as we go) then calculate our fnCompare
                                oOptions = core.extend({
                                    //compare: core.type.fn.noop,
                                    useCoercion: true,
                                    caseInsensitive: true,
                                    trimWhitespace: true,
                                    //maxDepth: 0,
                                    includeMissingKeys: true,
                                    caseInsensitiveKeys: true,
                                    pruneUndefinedValues: false
                                }, oOptions, { maxDepth: null });
                                fnCompare = objCompare(oOptions);

                                //# If we are to find .caseInsensitiveKeys, use core.type.obj.get to resolve each a_sSourceKeys
                                if (oOptions.caseInsensitiveKeys) {
                                    //# Traverse the a_sSourceKeys, fnCompare'ing the passed x to the y (loading any mismatches into our oReturnVal) as we go
                                    for (i = 0; i < a_sSourceKeys.length; i++) {
                                        if (!fnCompare(core.type.obj.get(x, a_sSourceKeys[i]), core.type.obj.get(y, a_sSourceKeys[i]))) {
                                            oReturnVal[a_sSourceKeys[i]] = y[a_sSourceKeys[i]];
                                        }

                                        //# .rm the current a_sSourceKeys from our a_sCompareKeys
                                        core.type.arr.rm(a_sCompareKeys, a_sSourceKeys[i]);
                                    }
                                }
                                //# Else the a_sSourceKeys are case sensitive, so resolve each a_sSourceKeys normally
                                else {
                                    //# Traverse the a_sSourceKeys, fnCompare'ing the passed x to the y (loading any mismatches into our oReturnVal) as we go
                                    for (i = 0; i < a_sSourceKeys.length; i++) {
                                        if (!fnCompare(x[a_sSourceKeys[i]], y[a_sSourceKeys[i]])) {
                                            oReturnVal[a_sSourceKeys[i]] = y[a_sSourceKeys[i]];
                                        }

                                        //# .rm the current a_sSourceKeys from our a_sCompareKeys
                                        core.type.arr.rm(a_sCompareKeys, a_sSourceKeys[i]);
                                    }
                                }

                                //# If we are to .includeMissingKeys, traverse any remaining a_sCompareKeys and set them into our oReturnVal
                                if (oOptions.includeMissingKeys) {
                                    for (i = 0; i < a_sCompareKeys.length; i++) {
                                        oReturnVal[a_sCompareKeys[i]] = y[a_sCompareKeys[i]];
                                    }
                                }
                            }

                            return (oOptions.pruneUndefinedValues ? core.type.obj.prune(oReturnVal) : oReturnVal);
                        }, //# type.obj.diff


                        //#########
                        /** Provides access to an object structure's nested properties, returning the first found path.
                         * @function ish.type.obj.resolveFirst
                         * @param {object} oObject Value to interrogate.
                         * @param {Array<string>|Array<Array<string>>} a_vPaths Value representing the paths to the requested property's as a period-delimited string (e.g. "parent.child.array.0.key") or an array of strings.
                         * @returns {variant} Value representing the variant at the referenced path.
                         */ //#####
                        resolveFirst: function (oObject, a_vPaths) {
                            var i, oResolved,
                                vReturnVal /*= undefined */
                            ;

                            //# If the caller passed in valid arguments, traverse the a_vPaths
                            if (core.type.obj.is(oObject) && core.type.arr.is(a_vPaths, true)) {
                                for (i = 0; i < a_vPaths.length; i++) {
                                    //# .resolve the current a_vPaths, .returnMetadata'ing into oResolved
                                    oResolved = core.resolve(core.resolve.returnMetadata, oObject, a_vPaths[i]);

                                    //# If the current a_vPaths .existed, reset our vReturnVal and fall from the loop
                                    if (oResolved.existed) {
                                        vReturnVal = oResolved.value;
                                        break;
                                    }
                                }
                            }

                            return vReturnVal;
                        }, //# type.obj.resolveFirst


                        //#########
                        /** Provides an object structure as a string in Javascript's Object Literal Notation.
                         * @note <code>JSON.stringify</code> returns <code>Object</code>s in JSON (e.g. with quoted keys <code>{ "key": "value" }</code>) while <code>ish.type.obj.stringify</code> returns <code>Object</code>s in Javascript's Object Literal notation (e.g. <code>{ key: "value" }</code>).
                         * @function ish.type.obj.stringify
                         * @param {object} oObject Value to interrogate.
                         * @returns {variant} Value representing the passed object as a string in Javascript's Object Literal Notation.
                         * @see {@link https://stackoverflow.com/a/11233515/235704|StackOverflow.com}
                         */ //#####
                        stringify: function n(oObject) {
                            var a_sKeys, i,
                                sReturnVal = ""
                            ;

                            //# If the passed oObject .is and .obj
                            if (core.type.obj.is(oObject)) {
                                a_sKeys = Object.keys(oObject); //core.type.obj.ownKeys(oObject);

                                //#
                                for (i = 0; i < a_sKeys.length; i++) {
                                    a_sKeys[i] = a_sKeys[i] + ":" + core.type.obj.stringify(oObject[a_sKeys[i]]);
                                }
                                sReturnVal = "{" + a_sKeys.join(",") + "}";

                                /*return Object
                                    .keys(oObject)
                                    .map(key => `${key}:${core.type.obj.stringify(oObject[key])}`)
                                    .join(",")
                                ;*/
                            }
                            //# Else the passed oObject .is not an .obj, so stringify using the native JSON.stringify
                            else {
                                sReturnVal = JSON.stringify(oObject);
                            }

                            return sReturnVal;
                        }, //# type.obj.stringify


                        //#########
                        /** Creates an empty object.
                         * @$note Creating an object via <code>{}</code> results in an object that includes <code>__proto__</code> and <code>hasOwnProperty</code>. This method returns an object with no properties.
                         * @function ish.type.obj.empty
                         * @returns {object} Value representing an empty object.
                         * @see {@link https://davidwalsh.name/javascript-tricks|DavidWalsh.name}
                         */ //#####
                        empty: function () {
                            return Object.create(_null);
                        } //# type.obj.empty
                    };
                }() //# core.type.obj
            };

            //#
            if (!bServerside) {
                //# cp
                oCoreType.dom = function () {
                    function pender(vDomParent, vDomToAdd, bPrepend) {
                        var i,
                            _parent = core.type.dom.mk(vDomParent, null),
                            _domToAdd = core.type.dom.mk(vDomToAdd, null),
                            bReturnVal = !!(_parent && vDomToAdd)
                        ;

                        //# If we have a _parent and _domToAdd, bPrepend or .appendChild it
                        if (bReturnVal) {
                            if (core.type.arr.is(vDomToAdd)) {
                                //#
                                if (bPrepend) {
                                    for (i = vDomToAdd.length; i > 0; i--) {
                                        _domToAdd = core.type.dom.mk(vDomToAdd[i], null);
                                        if (_domToAdd) {
                                            _parent.insertBefore(_domToAdd, _parent.childNodes[0]);
                                        }
                                    }
                                }
                                //#
                                else {
                                    for (i = 0; i < vDomToAdd.length; i++) {
                                        _domToAdd = core.type.dom.mk(vDomToAdd[i], null);
                                        if (_domToAdd) {
                                            _parent.appendChild(_domToAdd);
                                        }
                                    }
                                }
                            }
                            //#
                            else if (core.type.dom.is(_domToAdd)) {
                                if (bPrepend) {
                                    _parent.insertBefore(_domToAdd, _parent.childNodes[0]);
                                }
                                else {
                                    _parent.appendChild(_domToAdd);
                                }
                            }
                        }

                        return bReturnVal;
                    } //# pender

                    return {
                        // eq:
                        // cmp:

                        //#########
                        /** Copies the passed value into a new instance using {@link https://developer.mozilla.org/en-US/docs/Web/API/Node/cloneNode|cloneNode}.
                         * @$note This is functionally a type-safe wrapper of {@link https://developer.mozilla.org/en-US/docs/Web/API/Node/cloneNode|cloneNode}.
                         * @function ish.type.dom.cp
                         * @param {variant[]} x Value representing the DOM element to copy.
                         * @param {boolean} [bDeepCopy=false] Value representing if the children of the DOM element should also be cloned. <code>false</code> performs a copy of the DOM element only while <code>true</code> also performs a copy of the child DOM elements.
                         * @returns {variant[]} Value representing the passed value as a new instance.
                         */ //#####
                        cp: function (x, bDeepCopy) {
                            if (core.type.dom.is(x) && core.type.fn.is(x.cloneNode)) {
                                return x.cloneNode(bDeepCopy === true);
                            }
                        }, //# type.dom.cp


                        //#########
                        /** Prepends the passed value into the referenced parent as the first element.
                         * @$note The passed DOM elements are passed through {@link: ish.type.dom.mk}.
                         * @function ish.type.dom.prepend
                         * @param {variant} vDomParent Value representing the parent DOM element.
                         * @param {variant} vDomToAdd Value representing the DOM element to insert.
                         * @returns {boolean} Value representing if the passed value was successfully inserted.
                         */ //#####
                        prepend: function (vDomParent, vDomToAdd) {
                            return pender(vDomParent, vDomToAdd, true);
                        }, //# type.dom.prepend


                        //#########
                        /** Appends the passed value into the referenced parent as the last element.
                         * @$note The passed DOM elements are passed through {@link: ish.type.dom.mk}.
                         * @function ish.type.dom.append
                         * @param {variant} vDomParent Value representing the parent DOM element.
                         * @param {variant} vDomToAdd Value representing the DOM element to insert.
                         * @returns {boolean} Value representing if the passed value was successfully inserted.
                         */ //#####
                        append: function (vDomParent, vDomToAdd) {
                            return pender(vDomParent, vDomToAdd /*, false*/);
                        }, //# type.dom.append


                        //#########
                        /** Replaces the referenced DOM element with the passed value.
                         * @$note The passed DOM elements are passed through {@link: ish.type.dom.mk}.
                         * @function ish.type.dom.replace
                         * @param {variant} vTarget Value representing the target DOM element to replace.
                         * @param {variant} vReplacement Value representing the DOM element to insert.
                         * @returns {boolean} Value representing if the passed value was successfully inserted.
                         */ //#####
                        replace: function (vTarget, vReplacement) {
                            var _target = core.type.dom.mk(vTarget, null),
                                _replacement = (vReplacement ? core.type.dom.parse(vReplacement)[0] : null), //# TODO: add looping
                                bReturnVal = !!(_target && _target.parentNode)
                            ;

                            //# If the _target and _replacement are valid, .replaceChild now
                            if (bReturnVal) {
                                if (_replacement) {
                                    _target.parentNode.replaceChild(_replacement, _target);
                                }
                                else {
                                    _target.remove();
                                }
                            }

                            return bReturnVal;
                        } //# type.dom.replace
                    };
                }(); //# core.type.dom
            }

            return oCoreType;
        }); //# core.type

        //# .fire the plugin's loaded event
        core.io.event.fire("ish.type-ex");

        //# Return core to allow for chaining
        return core;
    } //# init

    /*
        diff: function ($assert) {
            var o1 = { n: 1, i: 2, c: 3, k: 4, camp: "Camp" },
                o2 = { n: 1, e: 22, k: 4, camp: " camP " },
                oResult1 = { i: undefined, c: undefined, camp: " camP " },
                oResult2 = { i: undefined, c: undefined }
            ;

            //#

        }
    */


    //# If we are running server-side
    //#     NOTE: Compliant with UMD, see: https://github.com/umdjs/umd/blob/master/templates/returnExports.js
    //#     NOTE: Does not work with strict CommonJS, but only CommonJS-like environments that support module.exports, like Node.
    if (typeof module === 'object' && module.exports) { //if (typeof module !== 'undefined' && this.module !== module && module.exports) {
        module.exports = function (core) {
            return init(
                core,
                //x => Buffer.from(x, 'base64').toString(),
                function (x) { return Buffer.from(x, 'base64').toString('binary'); },
                //x => Buffer.from(x).toString('base64')
                function (x) { return Buffer.from(x, 'binary').toString('base64'); }
            );
        };
    }
    //# Else if we are running in an .amd environment, register as an anonymous module
    else if (typeof define === 'function' && define.amd) {
        define([], function (core) {
            return init(core, window.atob, window.btoa);
        });
    }
    //# Else we are running in the browser, so we need to setup the _document-based features
    else {
        return init(window.head.ish || document.querySelector("SCRIPT[ish]").ish, window.atob, window.btoa);
    }

    //</MIXIN>
}());