window.gj = window.gj || {};
window.gj.logging = window.gj.logging || {};

(function (ns) {

    // Possible log levels
    ns.CRITICAL = 50;
    ns.ERROR = 40;
    ns.WARNING = 30;
    ns.INFO = 20;
    ns.DEBUG = 10;
    ns.NOTSET = 0;

    // TODO (yvdm): consider filtering on just the "constants" defined above
    ns.getLevelName = function (level) {
        for (property in ns) {
            if (ns[property] === level) {
                return property;
            }
        }
    };

    ns.loggers = {};

    ns.getLogger = function (name) {
        var logger;

        if (name in ns.loggers) {
            // console.log('Logger named "' + name + '" already exists, returning it');
            logger = ns.loggers[name];
        } else {
            // console.log('Creating new logger named "' + name + '"');
            logger = ns.logger({name: name});
            ns.loggers[name] = logger;
        }
        return logger;
    };

    ns.logger = function (spec, my) {
        var that = {};

        my = my || {};
        my.level = ns.NOTSET;

        // TODO (yvdm): make configurable
        my.handlers = []

        if (window.console) {
            my.handlers.push(ns.consoleHandler());
        }

        my.handle = function (record) {
            my.callHandlers(record);
        };

        my.callHandlers = function (record) {
            var handler;

            for (var i = 0; i < my.handlers.length; i++) {
                handler = my.handlers[i];
                handler.handle(record);
            }
        };

        that.addHandler = function (handler) {
            my.handlers.push(handler);
        };

        that.setLevel = function (level) {
            my.level = level;
            // console.log('Log level for "' + spec.name + '" set to', level, '(' + ns.getLevelName(level) + ')');
        };

        that.isEnabledFor = function (level) {
            return level >= that.getEffectiveLevel(level);
        };

        that.getEffectiveLevel = function () {
            return my.level;
        };

        that.log = function (level, messages) {
            if (!that.isEnabledFor(level)) {
                return;
            }
            var record = ns.logRecord({
                name: spec.name,
                level: level,
                messages: messages
            })
            my.handle(record);
        };

        that.debug = function () {
            that.log(ns.DEBUG, arguments);
        };

        that.info = function () {
            that.log(ns.INFO, arguments);
        };

        that.warning = function () {
            that.log(ns.WARNING, arguments);
        };

        that.error = function () {
            that.log(ns.ERROR, arguments);
        };

        that.critical = function () {
            that.log(ns.CRITICAL, arguments);
        };

        // By default, set log level to ERROR
        that.setLevel(ns.ERROR);

        return that;
    };

    ns.logRecord = function (spec, my) {
        var that = {};

        my = my || {};

        that.name = spec.name;
        that.level = spec.level;
        that.levelName = ns.getLevelName(spec.level);
        that.messages = spec.messages;

        return that;
    };

    ns.handler = function (spec, my) {
        var that = {};

        spec = spec || {};
        spec.level = spec.level || ns.NOTSET;

        that.handle = function (record) {
            if (record.level >= spec.level) {
                that.emit(record);
            }
        };

        that.setLevel = function (newLevel) {
            spec.level = newLevel;
        };

        return that;
    };

    ns.consoleHandler = function (spec, my) {
        var that;

        var stream = window.console;

        var mappings = {}
        mappings[ns.ERROR] = 'error';
        mappings[ns.WARNING] = 'warn';
        mappings[ns.INFO] = 'info';
        mappings[ns.DEBUG] = 'log';

        var getMethodForLevel = function (level) {
            if (level in mappings) {
                return mappings[level];
            }
            return 'log'; // The generic fallback
        };

        that = ns.handler(spec, my);

        that.flush = function () {
            stream.clear();
        };

        that.emit = function (record) {
            var method = getMethodForLevel(record.level);
            var arguments = [];

            arguments.push('[' + record.name + ']');

            for (var i = 0; i < record.messages.length; i++) {
                arguments.push(record.messages[i]);
            }

            // Catch errors using apply() on Webkit; resort to joining
            // arguments instead
            try {
                stream[method].apply(that, arguments);
            } catch (err) {
                try {
                    stream[method](arguments.join(' '));
                } catch (err) {
                    // fail silently
                }
            }
        };

        return that;
    };

})(window.gj.logging);

