'use strict';

/* eslint no-unused-vars: off */
/* eslint-env commonjs */

/**
 * Shim process.stdout.
 */

process.stdout = require('browser-stdout')({label: false});

var parseQuery = require('./lib/browser/parse-query');
var highlightTags = require('./lib/browser/highlight-tags');
var Mocha = require('./lib/mocha');

/**
 * Create a Mocha instance.
 *
 * @return {undefined}
 */

var mocha = new Mocha({reporter: 'html'});

/**
 * Save timer references to avoid Sinon interfering (see GH-237).
 */

var Date = global.Date;
var setTimeout = global.setTimeout;
var setInterval = global.setInterval;
var clearTimeout = global.clearTimeout;
var clearInterval = global.clearInterval;

var uncaughtExceptionHandlers = [];

var originalOnerrorHandler = global.onerror;

/**
 * Remove uncaughtException listener.
 * Revert to original onerror handler if previously defined.
 */

process.removeListener = function(e, fn) {
  if (e === 'uncaughtException') {
    if (originalOnerrorHandler) {
      global.onerror = originalOnerrorHandler;
    } else {
      global.onerror = function() {};
    }
    var i = uncaughtExceptionHandlers.indexOf(fn);
    if (i !== -1) {
      uncaughtExceptionHandlers.splice(i, 1);
    }
  }
};

/**
 * Implements listenerCount for 'uncaughtException'.
 */

process.listenerCount = function(name) {
  if (name === 'uncaughtException') {
    return uncaughtExceptionHandlers.length;
  }
  return 0;
};

/**
 * Implements uncaughtException listener.
 */

process.on = function(e, fn) {
  if (e === 'uncaughtException') {
    global.onerror = function(err, url, line) {
      fn(new Error(err + ' (' + url + ':' + line + ')'));
      return !mocha.options.allowUncaught;
    };
    uncaughtExceptionHandlers.push(fn);
  }
};

process.listeners = function(e) {
  if (e === 'uncaughtException') {
    return uncaughtExceptionHandlers;
  }
  return [];
};

// The BDD UI is registered by default, but no UI will be functional in the
// browser without an explicit call to the overridden `mocha.ui` (see below).
// Ensure that this default UI does not expose its methods to the global scope.
mocha.suite.removeAllListeners('pre-require');

var immediateQueue = [];
var immediateTimeout;

function timeslice() {
  var immediateStart = new Date().getTime();
  while (immediateQueue.length && new Date().getTime() - immediateStart < 100) {
    immediateQueue.shift()();
  }
  if (immediateQueue.length) {
    immediateTimeout = setTimeout(timeslice, 0);
  } else {
    immediateTimeout = null;
  }
}

/**
 * High-performance override of Runner.immediately.
 */

Mocha.Runner.immediately = function(callback) {
  immediateQueue.push(callback);
  if (!immediateTimeout) {
    immediateTimeout = setTimeout(timeslice, 0);
  }
};

/**
 * Function to allow assertion libraries to throw errors directly into mocha.
 * This is useful when running tests in a browser because window.onerror will
 * only receive the 'message' attribute of the Error.
 */
mocha.throwError = function(err) {
  uncaughtExceptionHandlers.forEach(function(fn) {
    fn(err);
  });
  throw err;
};

/**
 * Override ui to ensure that the ui functions are initialized.
 * Normally this would happen in Mocha.prototype.loadFiles.
 */

mocha.ui = function(ui) {
  Mocha.prototype.ui.call(this, ui);
  this.suite.emit('pre-require', global, null, this);
  return this;
};

/**
 * Setup mocha with the given setting options.
 */

mocha.setup = function(opts) {
  if (typeof opts === 'string') {
    opts = {ui: opts};
  }
  if (opts.delay === true) {
    this.delay();
  }
  var self = this;
  Object.keys(opts)
    .filter(function(opt) {
      return opt !== 'delay';
    })
    .forEach(function(opt) {
      if (Object.prototype.hasOwnProperty.call(opts, opt)) {
        self[opt](opts[opt]);
      }
    });
  return this;
};

/**
 * Run mocha, returning the Runner.
 */

mocha.run = function(fn) {
  var options = mocha.options;
  mocha.globals('location');

  var query = parseQuery(global.location.search || '');
  if (query.grep) {
    mocha.grep(query.grep);
  }
  if (query.fgrep) {
    mocha.fgrep(query.fgrep);
  }
  if (query.invert) {
    mocha.invert();
  }

  return Mocha.prototype.run.call(mocha, function(err) {
    // The DOM Document is not available in Web Workers.
    var document = global.document;
    if (
      document &&
      document.getElementById('mocha') &&
      options.noHighlighting !== true
    ) {
      highlightTags('code');
    }
    if (fn) {
      fn(err);
    }
  });
};

/**
 * Expose the process shim.
 * https://github.com/mochajs/mocha/pull/916
 */

Mocha.process = process;

/**
 * Expose mocha.
 */

global.Mocha = Mocha;
global.mocha = mocha;

// this allows test/acceptance/required-tokens.js to pass; thus,
// you can now do `const describe = require('mocha').describe` in a
// browser context (assuming browserification).  should fix #880
module.exports = Object.assign(mocha, global);
