/*!
 * QUnit CLI Boilerplate
 * Copyright 2011-2012 John-David Dalton <http://allyoucanleet.com/>
 * Based on a gist by Jörn Zaefferer <https://gist.github.com/722381>
 * Available under MIT license <http://mths.be/mit>
 */
;(function(global) {

  /** Add `console.log()` support for Narwhal, Rhino, and RingoJS */
  global.console || (global.console = { 'log': global.print });

  /** Reduce global.QUnit.QUnit -> global.QUnit */
  global.QUnit && (QUnit = QUnit.QUnit || QUnit);

  /*--------------------------------------------------------------------------*/

  /** Used as a horizontal rule in console output */
  var hr = '----------------------------------------';

  /** Shortcut used to convert array-like objects to arrays */
  var slice = [].slice;

  /** Used to resolve a value's internal [[Class]] */
  var toString = {}.toString;

  /*--------------------------------------------------------------------------*/

  /**
   * An iteration utility for arrays.
   * @private
   * @param {Array} array The array to iterate over.
   * @param {Function} callback The function called per iteration.
   */
  function each(array, callback) {
    var index = -1,
        length = array.length;

    while (++index < length) {
      callback(array[index], index, array);
    }
  }

  /**
   * Checks if the specified `value` is a function.
   * @private
   * @param {Mixed} value The value to check.
   * @returns {Boolean} Returns `true` if `value` is a function, else `false`.
   */
  function isFunction(value) {
    return toString.call(value) == '[object Function]';
  }

  /*--------------------------------------------------------------------------*/

  /**
   * A logging callback triggered when all testing is completed.
   * @memberOf QUnit
   * @param {Object} details An object with properties `failed`, `passed`, 'runtime', and `total`.
   */
  function done(details) {
    // stop `asyncTest()` from erroneously calling `done()` twice in environments w/o timeouts
    if (!QUnit.doneCalled) {
      console.log(hr);
      console.log('    PASS: ' + details.passed + '  FAIL: ' + details.failed + '  TOTAL: ' + details.total);
      console.log('    Finished in ' + details.runtime + ' milliseconds.');
      console.log(hr);

      // exit out of Rhino
      try { quit(); } catch(e) { }
      // exit out of Node.js
      try { process.exit(); } catch(e) { }
      // prevent multiple calls to `done()`
      QUnit.doneCalled = true;
    }
  }

  /**
   * A logging callback triggered after every assertion.
   * @memberOf QUnit
   * @param {Object} details An object with properties `actual`, `expected`, `message`, and `result`.
   */
  function log(details) {
    var expected = details.expected,
        result = details.result,
        type = typeof expected != 'undefined' ? 'EQ' : 'OK',
        assertion = [
          result ? 'PASS' : 'FAIL',
          type,
          details.message || 'ok'
        ];

    if (!result && type == 'EQ') {
      assertion.push('Expected: ' + expected + ', Actual: ' + details.actual);
    }
    QUnit.config.testStats.assertions.push(assertion.join(' | '));
  }

  /**
   * A logging callback triggered at the start of every test module.
   * @memberOf QUnit
   * @param {Object} details An object with property `name`.
   */
  function moduleStart(details) {
    console.log(hr);
    console.log(details.name);
    console.log(hr);
  }

  /**
   * Converts an object into a string representation.
   * @memberOf QUnit
   * @type Function
   * @param {Object} object The object to stringify.
   * @returns {String} The result string.
   */
  var parseObject = (function() {
    var _parseObject = QUnit.jsDump.parsers.object;
    return function(object) {
      // fork to support Rhino's error objects
      if (typeof object.rhinoException == 'object') {
        return object.name +
          ' { message: "' + object.message +
          '", fileName: "' + object.fileName +
          '", lineNumber: ' + object.lineNumber + ' }';
      }
      return _parseObject(object);
    };
  }());

  /**
   * A logging callback triggered after a test is completed.
   * @memberOf QUnit
   * @param {Object} details An object with properties `failed`, `name`, `passed`, and `total`.
   */
  function testDone(details) {
    var assertions = QUnit.config.testStats.assertions,
        name = details.name;

    if (details.failed > 0) {
      console.log(' FAIL - '+ name);
      each(assertions, function(value) {
        console.log('    ' + value);
      });
    } else {
      console.log(' PASS - ' + name);
    }
    assertions.length = 0;
  }

  /*--------------------------------------------------------------------------*/

  /**
   * Timeout fallbacks based on the work of Andrea Giammarchi and Weston C.
   * @see https://github.com/WebReflection/wru/blob/master/src/rhinoTimers.js
   * @see http://stackoverflow.com/questions/2261705/how-to-run-a-javascript-function-asynchronously-without-using-settimeout
   */
  (function() {
    var timer,
        counter = 0,
        ids = {};

    /**
     * Schedules timer-based callbacks.
     * @private
     * @param {Function} fn The function to call.
     * @oaram {Number} delay The number of milliseconds to delay the `fn` call.
     * @param [arg1, arg2, ...] Arguments to invoke `fn` with.
     * @param {Boolean} repeated A flag to specify whether `fn` is called repeatedly.
     * @returns {Number} The the ID of the timeout.
     */
    function schedule(fn, delay, args, repeated) {
      var task = ids[++counter] = new JavaAdapter(java.util.TimerTask, {
        'run': function() {
          fn.apply(global, args);
        }
      });
      // support non-functions
      if (!isFunction(fn)) {
        fn = (function(code) {
          code = String(code);
          return function() { eval(code); };
        }(fn));
      }
      // used by setInterval
      if (repeated) {
        timer.schedule(task, delay, delay);
      }
      // used by setTimeout
      else {
        timer.schedule(task, delay);
      }
      return counter;
    }

    /**
     * Clears the delay set by `setInterval` or `setTimeout`.
     * @memberOf global
     * @param {Number} id The ID of the timeout to be cleared.
     */
    function clearTimer(id) {
      if (ids[id]) {
        ids[id].cancel();
        timer.purge();
        delete ids[id];
      }
    }

    /**
     * Executes a code snippet or function repeatedly, with a delay between each call.
     * @memberOf global
     * @param {Function|String} fn The function to call or string to evaluate.
     * @oaram {Number} delay The number of milliseconds to delay each `fn` call.
     * @param [arg1, arg2, ...] Arguments to invoke `fn` with.
     * @returns {Number} The the ID of the timeout.
     */
    function setInterval(fn, delay) {
      return schedule(fn, delay, slice.call(arguments, 2), true);
    }

    /**
     * Executes a code snippet or a function after specified delay.
     * @memberOf global
     * @param {Function|String} fn The function to call or string to evaluate.
     * @oaram {Number} delay The number of milliseconds to delay the `fn` call.
     * @param [arg1, arg2, ...] Arguments to invoke `fn` with.
     * @returns {Number} The the ID of the timeout.
     */
    function setTimeout(fn, delay) {
      return schedule(fn, delay, slice.call(arguments, 2));
    }

    // expose methods
    try {
      timer = new java.util.Timer;
      isFunction(global.clearInterval) || (global.clearInterval = clearTimer);
      isFunction(global.clearTimeout) || (global.clearTimeout = clearTimer);
      isFunction(global.setInterval) || (global.setInterval = setInterval);
      isFunction(global.setTimeout) || (global.setTimeout = setTimeout);
    } catch(e) {}
  }());

  /*--------------------------------------------------------------------------*/

  /**
   * An object used to hold information about the current running test.
   * @memberOf QUnit.config
   * @type Object
   */
  QUnit.config.testStats = {

    /**
     * An array of test summaries (pipe separated).
     * @memberOf QUnit.config.testStats
     * @type Array
     */
    'assertions': []
  };

  // add shortcuts to the global
  // exclude `module` because some environments have it as a built-in object
  each(['asyncTest', 'deepEqual', 'equal', 'equals', 'expect', 'notDeepEqual',
        'notEqual', 'notStrictEqual', 'ok', 'raises', 'same', 'start', 'stop',
        'strictEqual', 'test'], function(name) {
    global[name] = QUnit[name];
  });

  // don't forget to call `QUnit.start()` from another file
  QUnit.done = done;
  QUnit.log = log;
  QUnit.moduleStart = moduleStart;
  QUnit.testDone = testDone;
  QUnit.jsDump.parsers.object = parseObject;
  QUnit.init();

}(typeof global == 'object' && global || this));