/**
 * Send log messages to the console, filtered by log level.
 *
 * The log levels available are properties on this object:
 *
 *      Ext.space.Logger.LOG   // "LOG"
 *      Ext.space.Logger.ERROR // "ERROR"
 *      Ext.space.Logger.WARN  // "WARN"
 *      Ext.space.Logger.INFO  // "INFO" (default)
 *      Ext.space.Logger.DEBUG // "DEBUG"
 *
 * Each log level has a corresponding method that logs its arguments to the console
 * using the appropriate method on the console, optionally prefixed by the name of
 * the log level. Messages logged with `Ext.space.Logger.log(...)` are always logged
 * regardless of the current log level, but the rest take it into account.
 *
 *      var logger = Ext.space.Logger;
 *
 *      logger.level(); // "INFO" by default
 *
 *      logger.log("This message is always logged no matter what");
 *      logger.info("Info level message, gets skipped.");
 *
 *      logger.level(logger.DEBUG);
 *
 *      logger.info("Info level message, gets shown this time.");
 *
 *      logger.usePrefix = true;
 *      logger.warn("This one will be prefixed by 'WARN'");
 *
 *      logger.error("Multiple", ["argument", "error"], {prop: "message"});
 *
 * Note that the "ERROR" log level doesn't throw actual errors; that's up to the
 * application itself.
 *
 * You can also track custom application-defined events (defined as a collection of
 * basically arbitrary strings: category, action, label (optional), extra (optional))
 * with Ext.space.Logger.logEvent(). These get logged at the level provided to
 * Ext.space.Logger.eventLevel(...) (default Ext.space.Logger.INFO), and also
 * submitted to the Sencha Web Application Manager server for report generation in
 * the administration console.
 *
 */
Ext.define('Ext.space.Logger', {
    singleton: true,
 
    /**
     * @readonly
     */
    LOG: "LOG",
 
    /**
     * @readonly
     */
    ERROR: "ERROR",
 
    /**
     * @readonly
     */
    WARN: "WARN",
 
    /**
     * @readonly
     */
    INFO: "INFO",
 
    /**
     * @readonly
     */
    DEBUG: "DEBUG",
 
    /**
     * @private
     */
    levels: null,
 
    /**
     * @private
     */
    _level: 0,
 
    /**
     * @private
     */
    _eventLevel: 0,
 
    /**
     * Determines whether logged messages are prefixed with their log level.
     * type {Boolean}
     */
    usePrefix: false,
 
    /**
     * @private
     */
    constructor: function() {
        this.levels = {
            byLevel: [this.ERROR, this.WARN, this.INFO, this.DEBUG],
            byName: {}
        };
        var idx = this.levels.byName;
        var name = this.levels.byLevel;
 
        for (var i=0; i<name.length; i++) {
            idx[name[i]] = i;
            if (name[i] != this.LOG) {
                var method = this._getLevelMethodName(name[i]);
                this[method] = this._mklogger(method, name[i], i);
            }
        }
        this.level(this.INFO);
        this.eventLevel(this.INFO);
    },
 
    /**
     * Creates the logging methods
     * @private
     */
    _mklogger: function(method, prefix, level) {
        return function() {
            if (level <= this._level) {
                console[method].apply(console, this.usePrefix ? [prefix].concat(Array.prototype.slice.call(arguments)) : arguments);
            }
        };
    },
 
    /**
     * Convert a level name to a method name
     * @private
     */
    _getLevelMethodName: function(levelName) {
        return levelName.toLowerCase();
    },
 
    /**
     * Log a message to the console, regardless of the log level.
     */
    log: function() {
        console.log.apply(console, this.usePrefix ? [this.LOG].concat(Array.prototype.slice.call(arguments)) : arguments);
    },
 
    /**
     * Get or set the current log level for the given internal property name.
     * @private
     */
    _doLevel: function(prop, levelName) {
        if (arguments.length) {
            // only change the level if it's directly supported; otherwise no-op 
            if (this.levels.byName.hasOwnProperty(levelName)) {
                this[prop] = this.levels.byName[levelName];
            }
        }
        return this.levels.byLevel[this[prop]];
    },
 
    /**
     * Get or set the current log level.
     *
     * @param {String} levelName (optional) log level to set: "ERROR", "WARN", "INFO", "DEBUG"
     * @return {String} Current log level
     */
    level: function(levelName) {
        return this._doLevel("_level", levelName);
    },
 
    /**
     * Get or set the current log level to be used for custom events.
     *
     * @param {String} levelName (optional) log level to set: "ERROR", "WARN", "INFO", "DEBUG"
     * @return {String} Current log level
     */
    eventLevel: function(levelName) {
        return this._doLevel("_eventLevel", levelName);
    },
 
    /**
     * Send a custom event to the server.
     *
     * Events are a somewhat free-form set of arbitrary strings that facilitate
     * grouping in administrative reports. They're required to have a `category` and
     * `action` and can optionally also be given a `label` and/or `extra` data. Sencha
     * Web Application Client will attempt to submit the event data to the server,
     * and if the server is not accessible (for example, if the user is offline), it
     * will store the event for submission later.
     *
     *      Ext.space.Logger.logEvent({
     *          category: "Contact",
     *          action: "Contact form submission",
     *          label: "From footer",
     *          extra: "[email protected]"
     *      }).then(function() {
     *          // do something, if you want
     *      }, function(error) {
     *          // something went wrong, nothing worked, everything is terrible
     *      });
     *
     * @param {Object} kwArgs Custom event definition
     * @param {String} kwArgs.category Event category name
     * @param {String} kwArgs.action Event action name
     * @param {String} kwArgs.label (optional) Event label
     * @param {String} kwArgs.extra (optional) Extra event information
     * @return {Ext.space.Promise} Promise that resolves when the event is sent
     */
    logEvent: function(kwArgs) {
        var result = new Ext.space.Promise();
 
        if (kwArgs) {
            if (!kwArgs.category) {
                result.reject("Missing required event category");
 
            } else if (!kwArgs.action) {
                result.reject("Missing required event action");
 
            } else {
                var args = {
                    command: "Event#log",
                    timestamp: Date.now(),
                    category: kwArgs.category,
                    action: kwArgs.action,
                    callbacks: {
                        onSuccess: function() {
                            result.fulfill();
                        },
                        onError: function(error) {
                            result.reject(error);
                        }
                    }
                };
                if (kwArgs.label) {
                    args.label = kwArgs.label;
                }
                if (kwArgs.extra) {
                    args.extra = kwArgs.extra;
                }
 
                // so... much... indirection... 
                this[this._getLevelMethodName(this.eventLevel())]("Event:", kwArgs);
 
                Ext.space.Communicator.send(args);
 
            }
        } else {
            result.reject("Missing event descriptor");
        }
 
        return result;
    }
});