// Copyright © 2012, Mark Cavage. All rights reserved. // Copyright 2015 Joyent, Inc.

var assert = require('assert'); var Stream = require('stream').Stream; var util = require('util');

///— Globals

/* JSSTYLED */ var UUID_REGEXP = /^[a-fA-F0-9]{8}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{4}-[a-fA-F0-9]{12}$/;

///— Internal

function _capitalize(str) {

return (str.charAt(0).toUpperCase() + str.slice(1));

}

function _toss(name, expected, oper, arg, actual) {

throw new assert.AssertionError({
    message: util.format('%s (%s) is required', name, expected),
    actual: (actual === undefined) ? typeof (arg) : actual(arg),
    expected: expected,
    operator: oper || '===',
    stackStartFunction: _toss.caller
});

}

function _getClass(arg) {

return (Object.prototype.toString.call(arg).slice(8, -1));

}

function noop() {

// Why even bother with asserts?

}

///— Exports

var types = {

bool: {
    check: function (arg) { return typeof (arg) === 'boolean'; }
},
func: {
    check: function (arg) { return typeof (arg) === 'function'; }
},
string: {
    check: function (arg) { return typeof (arg) === 'string'; }
},
object: {
    check: function (arg) {
        return typeof (arg) === 'object' && arg !== null;
    }
},
number: {
    check: function (arg) {
        return typeof (arg) === 'number' && !isNaN(arg) && isFinite(arg);
    }
},
buffer: {
    check: function (arg) { return Buffer.isBuffer(arg); },
    operator: 'Buffer.isBuffer'
},
array: {
    check: function (arg) { return Array.isArray(arg); },
    operator: 'Array.isArray'
},
stream: {
    check: function (arg) { return arg instanceof Stream; },
    operator: 'instanceof',
    actual: _getClass
},
date: {
    check: function (arg) { return arg instanceof Date; },
    operator: 'instanceof',
    actual: _getClass
},
regexp: {
    check: function (arg) { return arg instanceof RegExp; },
    operator: 'instanceof',
    actual: _getClass
},
uuid: {
    check: function (arg) {
        return typeof (arg) === 'string' && UUID_REGEXP.test(arg);
    },
    operator: 'isUUID'
}

};

function _setExports(ndebug) {

var keys = Object.keys(types);
var out;

/* re-export standard assert */
if (process.env.NODE_NDEBUG) {
    out = noop;
} else {
    out = function (arg, msg) {
        if (!arg) {
            _toss(msg, 'true', arg);
        }
    };
}

/* standard checks */
keys.forEach(function (k) {
    if (ndebug) {
        out[k] = noop;
        return;
    }
    var type = types[k];
    out[k] = function (arg, msg) {
        if (!type.check(arg)) {
            _toss(msg, k, type.operator, arg, type.actual);
        }
    };
});

/* optional checks */
keys.forEach(function (k) {
    var name = 'optional' + _capitalize(k);
    if (ndebug) {
        out[name] = noop;
        return;
    }
    var type = types[k];
    out[name] = function (arg, msg) {
        if (arg === undefined || arg === null) {
            return;
        }
        if (!type.check(arg)) {
            _toss(msg, k, type.operator, arg, type.actual);
        }
    };
});

/* arrayOf checks */
keys.forEach(function (k) {
    var name = 'arrayOf' + _capitalize(k);
    if (ndebug) {
        out[name] = noop;
        return;
    }
    var type = types[k];
    var expected = '[' + k + ']';
    out[name] = function (arg, msg) {
        if (!Array.isArray(arg)) {
            _toss(msg, expected, type.operator, arg, type.actual);
        }
        var i;
        for (i = 0; i < arg.length; i++) {
            if (!type.check(arg[i])) {
                _toss(msg, expected, type.operator, arg, type.actual);
            }
        }
    };
});

/* optionalArrayOf checks */
keys.forEach(function (k) {
    var name = 'optionalArrayOf' + _capitalize(k);
    if (ndebug) {
        out[name] = noop;
        return;
    }
    var type = types[k];
    var expected = '[' + k + ']';
    out[name] = function (arg, msg) {
        if (arg === undefined || arg === null) {
            return;
        }
        if (!Array.isArray(arg)) {
            _toss(msg, expected, type.operator, arg, type.actual);
        }
        var i;
        for (i = 0; i < arg.length; i++) {
            if (!type.check(arg[i])) {
                _toss(msg, expected, type.operator, arg, type.actual);
            }
        }
    };
});

/* re-export built-in assertions */
Object.keys(assert).forEach(function (k) {
    if (k === 'AssertionError') {
        out[k] = assert[k];
        return;
    }
    if (ndebug) {
        out[k] = noop;
        return;
    }
    out[k] = assert[k];
});

/* export ourselves (for unit tests _only_) */
out._setExports = _setExports;

return out;

}

module.exports = _setExports(process.env.NODE_NDEBUG);