Source: type.js

import { warn } from './debug'
import { swap } from './utils'

/**
 * A collection of javascript types corresponding to unique numbers.
 *
 * @ignore
 *
 * @type  {Object.<string, number>}
 */
export var typeNumber = {
    'undefined': 1,
    string: 2,
    number: 4,
    'boolean': 8,
    'function': 16,
    // typeof == 'object' variations
    'null': 32,
    date: 64,
    regexp: 128,
    array: 256,
    set: 512,
    object: 1024,
    // anything else ?
    rest: (1 << 30)
};

/**
 * The swaped version of {@link typeNumber}
 *
 * @ignore
 *
 * @type  {Object.<number, string>}
 */
var swaped = swap(typeNumber);

/**
 * Get the type bit from {@link typeNumber} for given object
 *
 * @ignore
 *
 * @param    {*}       obj  The object to get the type number from.
 *
 * @returns  {number}       The number corresponding to given object in {@link typeNumber}
 */
export function getTypeNumber(obj) {
    switch (typeof obj) {
        case 'undefined':
            return typeNumber['undefined'];
        case 'string':
            return typeNumber['string'];
        case 'number':
            return typeNumber['number'];
        case 'boolean':
            return typeNumber['boolean'];
        case 'function':
            return typeNumber['function'];
        case 'object':
            switch (true) {
                case (obj === null):
                    return typeNumber['null'];
                case (obj instanceof Date):
                    return typeNumber['date'];
                case (obj instanceof RegExp):
                    return typeNumber['regexp'];
                case (obj instanceof Array):
                case (obj instanceof Map):
                case (obj instanceof WeakMap):
                    return typeNumber['array'];
                case (obj instanceof Set):
                case (obj instanceof WeakSet):
                    return typeNumber['set'];
                default:
                    return typeNumber['object'];
            }
        default:
            return typeNumber['rest'];
    }
}

/**
 * Check if given object is a string.
 *
 * @ignore
 *
 * @param    {*}         obj           The object to check.
 * @param    {boolean}   [warns=true]  Whether to warn if check fails.
 *
 * @returns  {boolean}  Whether given object is a string.
 */
export function isString(obj, warns) {
    var result = (getTypeNumber(obj) === typeNumber.string);
    if (warns && !result) {
        warn(`Value is not of type string, given: ${typeof obj}`);
    }
    return result;
}

/**
 * Get the javascript type for given object
 *
 * @ignore
 *
 * @param    {*}      obj  Any javascript object
 *
 * @returns  {string}      The type name of given object
 */
export function getType(obj) {
    return swaped[getTypeNumber(obj)];
}

/**
 * Set the number for a javascript type in {@link typeNumber}
 *
 * @ignore
 *
 * @param  {(Object|string)}  obj    An example object or the javascript
 *                                   type name (as a string) for which to set the number.
 * @param  {number}           value  The (new) corresponding number for given obj
 */
export function setTypeNumber(obj, value) {
    if (isString(obj)) {
        typeNumber[obj] = value;
        return;
    }
    var type = getType(obj);
    if (type !== undefined) {
        typeNumber[type] = value;
    } else {
        warn('Unknown object type, please pass it\'s name as a string as first argument.', obj);
    }
}

/**
 * Check that an object comply with given type rules
 *
 * @param    {*}                                      object  Any javascript object.
 * @param    {TypeRule}  typeRules    A description of allowed types.
 *
 * @returns  {boolean}  Whether if given object complies with given type rules.
 */
export function checkType(object, typeRules) {
    var result = true;
    var res;
    var typeofRule = typeof typeRules;
    if (typeofRule === 'string') {
        result = (getType(object) === typeRules);
    } else if(typeRules instanceof Array) {
        res = false;
        for (var i = 0; i < typeRules.length; i++) {
            if ((typeRules[i] instanceof Array) && (object instanceof Array)) {
                var rres = 0;
                for (var j = 0; j < object.length; j++) {
                    if (!checkType(object[j], typeRules[i])) {
                        rres += 1;
                    }
                }
                res = res || !rres;
            } else {
                res = res || checkType(object, typeRules[i]);
            }
        }
        result = res;
    } else if (typeofRule === 'function') {
        result = typeRules(object);
    } else if (typeRules !== null && typeofRule === 'object') {
        if (object !== null && typeof object !== 'object') {
            return false;
        }
        res = 0;
        for(var key in object) {
            var allOrRuleOrOther = typeRules['__all'] || typeRules[key] || typeRules['__others'];
            if (typeof typeRules !== 'undefined') {
                if (!checkType(object[key], allOrRuleOrOther)) {
                    res += 1;
                }
            }
        }
        result = !res;
    } else {
        warn("Oops, unknown case in checktype(). Args: ", object, typeRules);
    }
    return result;
}

/**
 * A set of rules defining the allowed types to check an object against.
 *
 * @typedef  {(string|Array.<TypeRule>|Object.<string, TypeRule>)}  TypeRule
 * @example
 * 'number'                             // The object should be a number.
 * or
 * ['boolean', ['string', 'number']]    // The object should either be a boolean
 *                                      // or an array of string and/or numbers.
 * or
 * {                                    // noStrings and onlyIntegers should be
 *     noStrings: 'boolean',            // booleans, all other properties should
 *     onlyIntegers: 'boolean',         // be numbers. Use __all instead of __others
 *     __others: 'numbers'              // if all properties should comply with
 * }                                    // given TypeRule.
 */

/**
 * Check if given object is of given javascript type.
 *
 * @ignore
 *
 * @param    {(string|string[])}  types         The javascript type name(s) to check against.
 * @param    {*}                  obj           The object that needs to be checked if of givven type(s).
 * @param    {boolean}            [warns=true]  Whether to warn if check fails.
 *
 * @returns  {boolean}   Whether given object is (one) of given type(s).
 */
export function isOfType(types, obj, warns) {
    warns = (typeof warns === 'undefined')? true : warns;
    var res = false;
    if (getTypeNumber(types) === typeNumber['array']) {
        types.forEach(function(type) {
            res = res || isOfType(type, obj, false);
        });
        types = types.join(', ');
    } else {
        res = getTypeNumber(obj) === typeNumber[types];
    }
    if (!res && warns) {
        warn(`Value is not of type ${types}, given: ${typeof obj}`);
    }
    return res;
}

/**
 * Check if given object is a number.
 *
 * @ignore
 *
 * @param    {*}         obj           The object to check.
 * @param    {boolean}   [warns=true]  Whether to warn if check fails.
 *
 * @returns  {boolean}  Whether given object is a number.
 */
export function isNumber(obj, warns) {
    var result = (getTypeNumber(obj) === typeNumber.number);
    if (warns && !result) {
        warn(`Value is not of type number, given: ${typeof obj}`);
    }
    return result;
}