(function($) {
    // mappings for string level names
    var levelMap = {
        off: 100,
        error: 12,
        warn: 10,
        info: 6,
        debug: 2
    };
    // base `log` function
    $.log = function(message, options) {
        var settings = $.extend({}, $.log.defaults, options);
        if (!$.log.defaults.logger 
            || settings.level == 'off'
            || levelMap[settings.level] < levelMap[$.log.defaults.level]) {
            return false;
        };
        settings.logger[settings.level](message);
    };
    $.log.debug = function(message, options) {
        var options = options || {};
        options.level = 'debug';
        this(message, options);
    };
    $.log.info =function(message, options) {
        var options = options || {};
        options.level = 'info';
        this(message, options);
    };
    $.log.warn = function(message, options) {
        var options = options || {};
        options.level = 'warn';
        this(message, options);
    };
    $.log.error = function(message, options) {
        var options = options || {};
        options.level = 'error';
        this(message, options);
    };
    $.log.defaults = {
        // logging level, off by default
        level: 'off',
        // logging firebugs `console`-like object
        logger: window.console ? window.console : null
    };
    $.log.setBoxLogger = function(box, force) {
        var force = force || false;
        if ($.log.defaults.logger && !force) {
            // use window.console
            return false;
        };
        // return firebugs `console`-like object, that will write to given box
        $.log.defaults.logger = {
            debug: function(message) {
                box.append(
                    '<p class="logging logging-debug"><strong>DEBUG</strong>: '
                    + message + '</p>');
            },
            info: function(message) {
                box.append(
                    '<p class="logging logging-info"><strong>INFO</strong>: '
                    + message + '</p>');
            },
            warn: function(message) {
                box.append(
                    '<p class="logging logging-warn"><strong>WARNING</strong>: '
                    + message + '</p>');
            },
            error: function(message) {
                box.append(
                    '<p class="logging logging-error"><strong>ERROR</strong>: '
                    + message + '</p>');
            }
        };
    };
    $.fn.introspect = function(findArr) {
        // introspect object and print it's attributes
        return this.each(function() {
            $.log.introspect($(this), findArr);
        });
    };
    $.log.introspect = function(obj, findArr){
        var findArr = findArr || [];
        var i = obj + ' # \n';
        for(var propName in obj) {
            if (findArr.length > 0 && findArr.indexOf(propName) == -1) {
                // if filter was given, and property name does not match,
                // then ignore it
                continue;
            };
            var prop = obj[propName]
            // using non standard features here
            // https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Function/name
            switch (typeof(prop)) {
                case 'function': 
                    prop = '[function ' + prop.name + ']';
                    break
                case 'object':
                    prop = '[object ' + prop.constructor && prop.constructor.name + ']';
                    break
            }
            i += (propName + '=' + prop + ' ; ');
        };
        $.log(i, {level: 'info'});
    };
})(jQuery);

