/* LOG
SUMMARY: Log is a convenience frontend for the firebug console.
DESCRIPTION: Log will automatically hierarchically group output by the function that
  called it, and compute total time spent in each group.  Log has lots of settings and
  is very flexible.  By default, Log writes to console.debug.  The first call to log()
  in any function will be output using console.group
SETTINGS:
  log.data.enable=false -- global on/off, enables all logging, not just from log.setup
  log.data.time=true -- enables displaying time spent in each log group
  log.data.delay=1000 -- stop logging after this # of ms w/out a call to log
METHODS:
  log(["func",]...) -- outputs arguments to console.  if the first argument is the name
             of a console function, that function will be called instead of the default.
  log.enable() -- global enable, or just add "all" to the log query string parameter
  log.setup([sets]) -- if test is null or true or a string value set using the log query
             string parameter, will temporarily turn on logging for this function.
  log.stop() -- stops logging this function.  Has no effect if global logging is enabled
  log.stopAll() -- stops all logging.  Has no effect if global logging is enabled
  log.trace() -- safe call to console.trace
  log.debug(), log.info(), log.warn(), log.error() -- aliases to log("func",...)
AUTHOR: Kevan Davis on 29 APR 2009
KNOWN ISSUES:
  #1 If using collapsed grouping, the order the groups are render is sometimes incorrect
       this is a fire bug issue (http://code.google.com/p/fbug/issues/detail?id=1676)
  #2 If the top group does not have an explicit stop() at the end, the final time is too
       long, by approximately the length of the delay (default: 1 second)
*/

if ("undefined" == typeof log) {
  try {
    this.log = function() {
      if (!(log.data.enabled || log.caller.logThis) || "undefined" == typeof console) {
        return log;
      }
      clearTimeout(log.data.autoclosetimeout);
      if (-1 == log.data.closetocurrent()) {
        var args = arguments;
        if (log.data.calcuniques) {
          args = [].splice.call(arguments, 0);
          args.push(" (Key: " + log.caller.uniqueKey + ")");
        }
        try {
          if ("function" == typeof console[arguments[0]]) {
            log.data.group.apply(null, [].splice.call(args, 1));
          } else {
            log.data.group.apply(null, args);
          }
        } catch(_) { log.data.out(args); }
        if (log.caller.profileThis && !log.data.profiling) {
          try { console.profile(); } catch(_) {}
          log.data.profiling = true;
        }
        if (log.data.time) {
          try { console.time("" + log.data.callstack.length); } catch(_) {}
        }
        log.data.callstack.push(log.caller);
      } else {
        log.data.out(arguments);
      }
      log.data.autoclosetimeout = setTimeout(log.data.autoclosefunc, log.data.delay);
      return log;
    };
    log.data = {
      message: console.debug || console.info || console.warn || console.error, //not all browsers are equal.  if console is not defined (IE), will fail here, and be caught below
      out: function( args ){
        try {
          if ("function" == typeof console[ args[0] ]) {
            try {
              console[ args[0] ].apply(null, [].splice.call(args, 1));
            } catch(_) { console[ args[0] ]( [].splice.call(args, 1)); }
          } else {
            try { log.data.message.apply(null, args); } catch(_) { log.data.message(args); }
          }
        } catch(chromeFubar) { try { console.info(args); } catch(_) {} }
      },
      group: /*console.groupCollapsed||*/console.group, // groupCollapsed is an experimental feature;  use with caution
      calcuniques: false,
      enabled:     false,
      enableList:  [],
      profileList: [],
      profiling:   false,
      time:        true,
      callstack:   [],
      delay:       1000,
      autoclosetimeout: null,
      regex: {
        newline: /\n/g,
        slash:   /\//,
        colon:   /:/,
        js:      /\.js/
      },
      autoclosefunc: function() {
        clearTimeout(log.data.autoclosetimeout);
        var closeIndex = log.data.callstack.length - 1;
        while (0 <= closeIndex--) {
          if (log.data.time) {
            try { console.timeEnd("" + closeIndex); } catch (_) {}
          }
          try{ console.groupEnd(); } catch(_) {}
          log.data.callstack.pop();
        }
        if (log.data.profiling) {
          try { console.profileEnd(); } catch (_) {}
        }
        log.data.profiling = false;
        return log;
      },
      closetocurrent: function() {
        var nca = arguments.callee.caller.caller,//nearest common ancestor
            index = log.data.inArray(nca, log.data.callstack);
        if (log.data.callstack.length && index != log.data.callstack.length - 1) {
          try {
            while (nca && -1 == log.data.inArray(nca, log.data.callstack))
              nca = nca.caller;
          } catch (_) {}; // occasionally happens, if an ajax error is raised (500 error?)
          if ( nca ) {
            while (log.data.callstack.length && log.data.callstack[ log.data.callstack.length - 1 ] != nca) {
              if (log.data.time) {
                try { console.timeEnd("" + (log.data.callstack.length - 1)); } catch (_) {}
              }
              try { console.groupEnd(); } catch(_) {}
              log.data.callstack.pop();
            }
          }
        }
        return index;
      },
      inArray: function(elem, array) {
        var i=array.length;
        while (i--){
          if (array[i] === elem) {
            return i;
          }
        }
        return -1;
      },
      prepend: function(msg, args) {
        args = [].splice.call(args, 0);
        args.unshift(msg);
        return args;
      }
    };
    log.setup = function() {
      if (!log.data.profileList.length && (log.data.enabled || !log.data.enableList.length)) { // skip if no logging is enabled
        return log;
      }
      var f = arguments.callee.caller;
      if ("undefined" == typeof f.logThis) { // only execute once
        f.logThis = (true === arguments[0]);
        f.profileThis = false;
        var args = arguments;
        if (log.data.calcuniques) { // this block substantially increases time spent in this function, approx 100x
          try { (0)(); } catch (e) { // cause an exception so we can get the raw stacktrace
            try { // this method only works in firefox
              var r = log.data.regex; // to save on lookups
              var caller = e.stack.split(r.newline)[1].split(r.slash);
              caller = caller[ caller.length - 1 ].split(r.colon);
              f.uniqueKey = "f_" + caller[0].split(r.js)[0] + caller[1];
              args = [].splice.call(arguments, 0);
              args.push(f.uniqueKey);
            } catch(_) {}
          }
        }
        if ("string" == typeof args[0]) {
          var p = args.length;
          while (p-- && !(f.profileThis && f.logThis)) {
            if (-1 != log.data.inArray(args[p], log.data.profileList)) {
              f.profileThis = f.logThis = true;
            } else if (-1 != log.data.inArray(args[p], log.data.enableList)) {
              f.logThis = true;
            }
          }
        }
      }
      return log;
    };
    log.stop = function() {
      log.data.closetocurrent();
      if (1 == log.data.callstack.length) {
        log.data.autoclosefunc();
      }
      return log;
    };
    log.stopAll = log.data.autoclosefunc;
    log.enable = function() { log.data.enabled = true; return log; };//to global enable at startup
    log.debug  = function() { return log.apply(null, log.data.prepend("debug", arguments)); }; // don't use these 4 for now, they don't group properly
    log.info   = function() { return log.apply(null, log.data.prepend("info",  arguments)); };
    log.warn   = function() { return log.apply(null, log.data.prepend("warn",  arguments)); };
    log.error  = function() { return log.apply(null, log.data.prepend("error", arguments)); };
    log.trace  = function() { try { console.trace(); } catch(_) {} return log; }; // just a convenience
  } catch(_) {
    // Placeholders so no errors are raised about missing methods or properties
    log = function() { return log; };
    log.data = { enableList: [], profileList: [], callstack: [] }; //only those vars that ppl might try to dereference
    log.setup = log.stop = log.stopAll = log.enable = log.info = log.warn = log.error = log.trace = function() { return log; };//only public functions
  }
  try {
    var raw_log = document.location.search.match(/log=([^&]*)?/);
    if (raw_log) { log.data.enableList = raw_log[1].split(","); }
    if ( -1 != log.data.inArray("all", log.data.enableList)) {
      log.enable();
    }
  } catch(_) {}
  try {
    var raw_profile = document.location.search.match(/log2=([^&]*)?/);
    if (raw_profile) { log.data.profileList = raw_profile[1].split(","); }
  } catch(_) {}
  try {
    log.data.calcuniques = (document.location.search.match( /loguniques/ ) ||
                            document.location.search.match( /log?.*(?=[=,]f_)/));
  } catch(_) {}
  if (!log.data.enabled && !log.data.enableList.length && !log.data.profileList.length) {
    // No logging is enabled, kill Log, TODO: optimize this opt out, so log doesn't get created in the first place
    log = function() { return log; };
    log.data = { enableList: [], profileList: [], callstack: [] }; //only those vars that ppl might try to dereference
    log.setup = log.stop = log.stopAll = log.enable = log.info = log.warn = log.error = log.trace = function() { return log; };//only public functions
  }
}
// END LOG