var fs = require('fs');
var path = require('path');
var Q = require('q');
var clone = require('lodash.clone');
var defaults = require('lodash.defaultsdeep');

var Runner = require('../run.js');
var Logger = require('../../util/logger.js');
var Utils = require('../../util/utils.js');
var Selenium = require('../selenium.js');
var ChildProcess = require('./child-process.js');
var ErrorHandler = require('./errorhandler.js');

var SETTINGS_DEPRECTED_VAL = './settings.json';
var SETTINGS_JS_FILE = './nightwatch.conf.js';

function CliRunner(argv) {
  this.settings = null;
  this.argv = argv;
  this.test_settings = null;
  this.output_folder = '';
  this.parallelMode = false;
  this.runningProcesses = {};
  this.cli = require('./cli.js');
}

CliRunner.prototype = {
  /**
   * @param {function} [done]
   * @deprecated - use .setup instead
   * @returns {CliRunner}
   */
  init : function(done) {
    this
      .readSettings()
      .parseTestSettings({}, done);

    return this;
  },

  /**
   * @param {object} [settings]
   * @param {function} [done]
   * @returns {CliRunner}
   */
  setup : function(settings, done) {
    this
      .readSettings()
      .parseTestSettings(settings, done);

    return this;
  },

  /**
   * Read the provided config json file; defaults to settings.json if one isn't provided
   */
  readSettings : function() {
    // use default nightwatch.json file if we haven't received another value

    if (this.cli.command('config').isDefault(this.argv.config)) {
      var defaultValue = this.cli.command('config').defaults();
      var localJsValue = path.resolve(SETTINGS_JS_FILE);

      if (Utils.fileExistsSync(SETTINGS_JS_FILE)) {
        this.argv.config = localJsValue;
      } else if (Utils.fileExistsSync(defaultValue)) {
        this.argv.config = path.join(path.resolve('./'), this.argv.config);
      } else if (Utils.fileExistsSync(SETTINGS_DEPRECTED_VAL)) {
        this.argv.config = path.join(path.resolve('./'), SETTINGS_DEPRECTED_VAL);
      } else {
        var binFolder = path.resolve(__dirname + '/../../../bin');
        var defaultFile = path.join(binFolder, this.argv.config);
        if (Utils.fileExistsSync(defaultFile)) {
          this.argv.config = defaultFile;
        } else {
          this.argv.config = path.join(binFolder, SETTINGS_DEPRECTED_VAL);
        }
      }
    } else {
      this.argv.config = path.resolve(this.argv.config);
    }

    this.argv.env = typeof this.argv.env == 'string' ? this.argv.env : 'default';

    // reading the settings file
    try {
      this.settings = require(this.argv.config);
      this.replaceEnvVariables();

      this.manageSelenium = !this.isParallelMode() && this.settings.selenium &&
        this.settings.selenium.start_process || false;

      if (typeof this.settings.src_folders == 'string') {
        this.settings.src_folders = [this.settings.src_folders];
      }

      this.settings.output = this.settings.output || typeof this.settings.output == 'undefined';
      this.settings.detailed_output = this.settings.detailed_output || typeof this.settings.detailed_output == 'undefined';
    } catch (ex) {
      Logger.error(ex.stack);
      this.settings = {};
    }

    return this;
  },

  /**
   * Looks for pattern ${VAR_NAME} in settings
   * @param {Object} [target]
   */
  replaceEnvVariables : function(target) {
    target = target || this.settings;
    for (var key in target) {
      switch(typeof target[key]) {
      case 'object':
        this.replaceEnvVariables(target[key]);
        break;
      case 'string':
        target[key] = target[key].replace(/\$\{(\w+)\}/g, function(match, varName) {
          return process.env[varName] || '${' + varName + '}';
        });
        break;
      }
    }

    return this;
  },


  /**
   * Reads globals from an external js or json file set in the settings file
   * @return {CliRunner}
   */
  readExternalGlobals : function () {
    if (!this.settings.globals_path) {
      return this;
    }

    var fullPath = path.resolve(this.settings.globals_path);

    try {
      var externalGlobals = require(fullPath);

      if (!externalGlobals) {
        return this;
      }

      // if we already have globals, make a copy of them
      var globals = this.test_settings.globals ? clone(this.test_settings.globals, true) : {};

      if (externalGlobals.hasOwnProperty(this.argv.env)) {
        for (var prop in externalGlobals[this.argv.env]) {
          externalGlobals[prop] = externalGlobals[this.argv.env][prop];
        }
      }

      defaults(globals, externalGlobals);
      this.test_settings.globals = globals;

      return this;
    } catch (err) {
      var originalMsg = Logger.colors.red(err.name + ': ' + err.message);

      err.name = 'Error reading external global file failed';
      err.message = 'using '+ fullPath + ':\n' + originalMsg;

      throw err;
    }
  },

  /**
   * @returns {CliRunner}
   */
  setOutputFolder : function() {
    var isDisabled = this.settings.output_folder === false && typeof(this.test_settings.output_folder) == 'undefined' ||
      this.test_settings.output_folder === false;
    var isDefault = this.cli.command('output').isDefault(this.argv.output);
    var folder = this.test_settings.output_folder || this.settings.output_folder;

    this.output_folder = isDisabled ? false : (isDefault && folder || this.argv.output);

    return this;
  },

  singleTestRun: function () {
    return typeof this.argv.test == 'string';
  },

  singleSourceFile: function() {
    if (this.singleTestRun()) {
      return fs.statSync(this.argv.test).isFile();
    }

    return (Array.isArray(this.argv._source) && this.argv._source.length == 1) && fs.statSync(this.argv._source[0]).isFile();
  },

  getTestSourceForSingle: function(targetPath) {
    var testsource;

    if (targetPath && path.resolve(targetPath) === targetPath) {
      testsource = targetPath;
    } else {
      testsource = path.join(process.cwd(), targetPath);
    }

    if (testsource.substr(-3) != '.js') {
      testsource += '.js';
    }

    return testsource;
  },

  /**
   * Returns the path where the tests are located
   * @returns {*}
   */
  getTestSource : function() {
    var testsource;

    if (this.isTestWorker() || this.singleSourceFile()) {
      testsource = this.getTestSourceForSingle(this.argv.test || this.argv._source[0]);
    } else {
      if (this.argv.testcase) {
        this.argv.testcase = null;

        if (this.test_settings.output) {
          ErrorHandler.logWarning('Option --testcase used without --test is ignored.');
        }
      }

      if (Array.isArray(this.argv._source) && (this.argv._source.length > 0)) {
        testsource = this.argv._source;
      } else if (this.argv.group) {

        // add support for multiple groups
        if (typeof this.argv.group == 'string') {
          this.argv.group = this.argv.group.split(',');
        }

        var groupTestsource = this.findGroupPathMultiple(this.argv.group);

        // If a group does not exist in the multiple src folder case, it is removed
        // from the test path list.
        if (this.settings.src_folders.length === 1) {

          // any incorrect path here will result in a run error
          testsource = groupTestsource;

        } else {

          // only when all groups fail to match will there be a run error
          testsource = groupTestsource.filter(Utils.dirExistsSync);

          if (this.argv.verbose) {
            var ignoredSource = groupTestsource.filter(function(path) {
              return testsource.indexOf(path) === -1;
            });

            if (ignoredSource.length) {
              Logger.warn('The following group paths were not found and will be excluded from the run:\n - ' + ignoredSource.join('\n - '));
            }
          }
        }

      } else {
        testsource = this.settings.src_folders;
      }
    }

    return testsource;
  },

  /**
   * Gets test paths from each of the src folders for a single group.
   *
   * @param {string} groupName
   * @return {Array}
   */
  findGroupPath : function(groupName) {
    var fullGroupPath = path.resolve(groupName);

    // for each src folder, append the group to the path
    // to resolve the full test path

    return this.settings.src_folders.map(function(srcFolder) {

      var fullSrcFolder = path.resolve(srcFolder);
      if (fullGroupPath.indexOf(fullSrcFolder) === 0) {
        return groupName;
      }

      return path.join(srcFolder, groupName);
    });
  },

  /**
   * Gets test paths for tests from any number of groups.
   *
   * @param {Array} groups
   */
  findGroupPathMultiple : function(groups) {
    var paths = [];

    groups.forEach(function(groupName) {

      // findGroupPath will return an array of paths

      paths = paths.concat(this.findGroupPath(groupName));

    }.bind(this));

    return paths;
  },

  /**
   * Starts the selenium server process
   * @param {function} [callback]
   * @returns {CliRunner}
   */
  startSelenium : function(callback) {
    callback = callback || function() {};

    if (this.isTestWorker()) {
      callback();
      return this;
    }

    var globalsContext = this.test_settings && this.test_settings.globals || null;
    // adding a link to the test_settings object on the globals context
    if (globalsContext) {
      globalsContext.test_settings = this.test_settings;
    }

    var beforeGlobal = Utils.checkFunction('before', globalsContext) || function(done) {done();};
    var self = this;
    if (!this.manageSelenium) {
      beforeGlobal.call(globalsContext, function() {
        callback();
      });
      return this;
    }

    this.settings.parallelMode = this.parallelMode;

    beforeGlobal.call(globalsContext, function() {
      Selenium.startServer(self.settings, function(error, child, error_out, exitcode) {
        if (error) {
          console.error('There was an error while starting the Selenium server:');
          ErrorHandler.handle({
            message : error_out
          });

          return;
        }
        callback();
      });
    });

    return this;
  },

  /**
   * Stops the selenium server if it is running
   * @return {*|promise}
   */
  stopSelenium : function() {
    var deferred = Q.defer();

    if (this.manageSelenium) {
      Selenium.stopServer(function() {
        deferred.resolve();
      });
    } else {
      deferred.resolve();
    }

    return deferred.promise;
  },

  /**
   * Starts the test runner
   *
   * @param {function} [finished] optional callback
   * @returns {CliRunner}
   */
  runTests : function(finished) {
    if (this.parallelMode) {
      return this;
    }

    var self = this;
    var handleError = ErrorHandler.handle;

    this.startSelenium(function() {
      self.runner(function(err, results) {
        self.stopSelenium().then(function() {
          if (self.isTestWorker()) {
            handleError(err, results, finished);
            return;
          }

          self.runGlobalAfter().then(function (ex) {
            handleError(ex || err, results, finished);
          });
        });
      });
    });

    return this;
  },

  runner : function(fn) {
    var testRunner = this.settings.test_runner || 'default';
    var testRunnerType = testRunner.type ? testRunner.type : testRunner;

    // getTestSource() will throw on an error, so we need
    // to wrap and pass along any error that does occur
    // to the callback fn

    var source;
    try {
      source = this.getTestSource();
    } catch (err) {
      fn(err, {});
      return;
    }

    switch (testRunnerType) {
      case 'default':
        var runner = new Runner(source, this.test_settings, {
          output_folder : this.output_folder,
          src_folders : this.settings.src_folders,
          live_output : this.settings.live_output,
          detailed_output : this.settings.detailed_output,
          start_session: this.startSession,
          reporter : this.argv.reporter,
          testcase : this.argv.testcase,
          end_session_on_fail : this.endSessionOnFail,
          retries : this.argv.retries,
          test_worker : this.isTestWorker(),
          suite_retries : this.argv.suiteRetries
        }, fn);
        return runner.run();

      case 'mocha':
        var MochaNightwatch = require('mocha-nightwatch');
        var mochaOpts = testRunner.options || {
            ui : 'bdd'
          };

        var mocha = new MochaNightwatch(mochaOpts);
        var nightwatch = require('../../index.js');

        Runner.readPaths(source, this.test_settings, function(error, modulePaths) {
          if (error) {
            fn(error, {});
            return;
          }
          modulePaths.forEach(function(file) {
            mocha.addFile(file);
          });

          mocha.run(nightwatch, this.test_settings, function(failures) {
            fn(null, {
              failed : failures
            });

            if (failures) {
              process.exit(10);
            }
          });

        }.bind(this));

        return mocha;
    }
  },

  inheritFromDefaultEnv : function() {
    if (this.argv.env == 'default') {
      return;
    }
    var defaultEnv = this.settings.test_settings['default'] || {};
    defaults(this.test_settings, defaultEnv);

    return this;
  },

  runGlobalAfter : function() {
    var deferred = Q.defer();

    var globalsContext = this.test_settings && this.test_settings.globals || null;
    var afterGlobal = Utils.checkFunction('after', globalsContext) || function(done) {done();};
    try {
      afterGlobal.call(globalsContext, function done() {
        deferred.resolve(null);
      });
    } catch (ex) {
      deferred.resolve(ex);
    }

    return deferred.promise;
  },

  /**
   * Validates and parses the test settings
   * @param {object} [settings]
   * @param {function} [done]
   * @returns {CliRunner}
   */
  parseTestSettings : function(settings, done) {
    // checking if the env passed is valid
    if (!this.settings.test_settings) {
      throw new Error('No testing environment specified.');
    }

    var envs = this.argv.env.split(',');
    for (var i = 0; i < envs.length; i++) {
      if (!(envs[i] in this.settings.test_settings)) {
        throw new Error('Invalid testing environment specified: ' + envs[i]);
      }
    }

    if (envs.length > 1) {
      this.setupParallelMode(envs, done);
      return this;
    }

    this.initTestSettings(this.argv.env, settings);

    if (this.parallelModeWorkers()) {
      this.setupParallelMode(null, done);
    }

    return this;
  },

  setGlobalOutputOptions: function () {
    this.test_settings.output = this.test_settings.output || (this.settings.output && typeof this.test_settings.output == 'undefined');
    this.test_settings.silent = this.test_settings.silent || typeof this.test_settings.silent == 'undefined';
    if (this.argv.verbose) {
      this.test_settings.silent = false;
    }

    return this;
  },

  /**
   * Sets the specific test settings for the specified environment
   * @param {string} [env]
   * @param {object} [settings]
   * @returns {CliRunner}
   */
  initTestSettings : function(env, settings) {
    // picking the environment specific test settings
    this.test_settings = env && this.settings.test_settings[env] || {};

    if (env) {
      this.test_settings.custom_commands_path = this.settings.custom_commands_path || '';
      this.test_settings.custom_assertions_path = this.settings.custom_assertions_path || '';
      this.test_settings.page_objects_path = this.settings.page_objects_path || '';

      this.inheritFromDefaultEnv();
      this.updateTestSettings(settings || {});
      this.readExternalGlobals();
    }

    this.setOutputFolder();
    this.setGlobalOutputOptions();

    if (typeof this.test_settings.test_workers != 'undefined') {
      this.settings.test_workers = this.test_settings.test_workers;
    } else if (typeof this.settings.test_workers != 'undefined') {
      this.test_settings.test_workers = this.settings.test_workers;
    }

    if (typeof this.test_settings.test_runner != 'undefined') {
      this.settings.test_runner = this.test_settings.test_runner;
    }

    if (typeof this.test_settings.detailed_output != 'undefined') {
      this.settings.detailed_output = this.test_settings.detailed_output;
    }

    if (typeof this.argv.skipgroup == 'string') {
      this.test_settings.skipgroup = this.argv.skipgroup.split(',');
    }

    if (this.argv.filter) {
      this.test_settings.filename_filter = this.argv.filter;
    }

    if (this.argv.tag) {
      this.test_settings.tag_filter = this.argv.tag;
    }

    if (typeof this.argv.skiptags == 'string') {
      this.test_settings.skiptags = this.argv.skiptags.split(',');
    }

    return this;
  },

  /**
   *
   * @param {Object} [test_settings]
   * @returns {CliRunner}
   */
  updateTestSettings : function(test_settings) {
    if (this.parallelMode && !this.parallelModeWorkers()) {
      return this;
    }

    if (test_settings && typeof test_settings == 'object') {
      for (var key in test_settings) {
        this.test_settings[key] = test_settings[key];
      }
    }

    this.settings.selenium = this.settings.selenium || {};

    // overwrite selenium settings per environment
    if (Utils.isObject(this.test_settings.selenium)) {
      for (var prop in this.test_settings.selenium) {
        this.settings.selenium[prop] = this.test_settings.selenium[prop];
      }
    }

    this.manageSelenium = !this.isParallelMode() && this.settings.selenium.start_process || false;
    this.startSession = this.settings.selenium.start_session || typeof this.settings.selenium.start_session == 'undefined';

    this.mergeSeleniumOptions();
    this.disableCliColorsIfNeeded();

    this.endSessionOnFail = typeof this.settings.end_session_on_fail == 'undefined' || this.settings.end_session_on_fail;
    if (typeof this.test_settings.end_session_on_fail != 'undefined') {
      this.endSessionOnFail = this.test_settings.end_session_on_fail;
    }
    return this;
  },

  disableCliColorsIfNeeded : function() {
    if (this.settings.disable_colors || this.test_settings.disable_colors) {
      Logger.disableColors();
    }
    return this;
  },

  /**
   * Backwards compatible method which attempts to merge deprecated driver specific options for selenium
   * @returns {CliRunner}
   */
  mergeSeleniumOptions : function() {
    if (!this.manageSelenium) {
      return this;
    }
    this.settings.selenium.cli_args = this.settings.selenium.cli_args || {};
    this.mergeCliArgs();

    var deprecationNotice = function(propertyName, newSettingName) {
      ErrorHandler.logWarning('DEPRECATION NOTICE: Property ' + propertyName + ' is deprecated since v0.5. Please' +
        ' use the "cli_args" object on the "selenium" property to define "' + newSettingName + '". E.g.:');
      var demoObj = '{\n' +
        '  "cli_args": {\n' +
        '    "'+ Logger.colors.yellow(newSettingName) +'": "<VALUE>"\n' +
        '  }\n' +
        '}';

      console.log(demoObj, '\n');
    };

    if (this.test_settings.firefox_profile) {
      deprecationNotice('firefox_profile', 'webdriver.firefox.profile');
      this.settings.selenium.cli_args['webdriver.firefox.profile'] = this.test_settings.firefox_profile;
    }
    if (this.test_settings.chrome_driver) {
      deprecationNotice('chrome_driver', 'webdriver.chrome.driver');
      this.settings.selenium.cli_args['webdriver.chrome.driver'] = this.test_settings.chrome_driver;
    }
    if (this.test_settings.ie_driver) {
      deprecationNotice('ie_driver', 'webdriver.ie.driver');
      this.settings.selenium.cli_args['webdriver.ie.driver'] = this.test_settings.ie_driver;
    }

    return this;
  },

  /**
   * Merge current environment specific cli_args into the main cli_args object to be passed to selenium
   *
   * @returns {CliRunner}
   */
  mergeCliArgs : function() {
    if (Utils.isObject(this.test_settings.cli_args)) {
      for (var prop in this.test_settings.cli_args) {
        if (this.test_settings.cli_args.hasOwnProperty(prop)) {
          this.settings.selenium.cli_args[prop] = this.test_settings.cli_args[prop];
        }
      }
    }
    return this;
  },

  ////////////////////////////////////////////////////////////////////////////////////
  // Parallelism related
  ////////////////////////////////////////////////////////////////////////////////////
  isParallelMode : function() {
    return process.env.__NIGHTWATCH_PARALLEL_MODE === '1';
  },

  isTestWorker : function() {
    return this.isParallelMode() && this.argv['test-worker'];
  },

  parallelModeWorkers: function () {
    return !this.isParallelMode() && !this.singleSourceFile() && (this.settings.test_workers === true ||
      Utils.isObject(this.settings.test_workers) && this.settings.test_workers.enabled);
  },

  /**
   * Enables parallel execution mode
   * @param {Array} envs
   * @param {function} [done]
   * @returns {CliRunner}
   */
  setupParallelMode : function(envs, done) {
    this.parallelMode = true;
    var self = this;

    this.startSelenium(function() {
      self.startChildProcesses(envs, function(code) {
        self.stopSelenium().then(function() {
          if (self.parallelModeWorkers()) {
            return self.runGlobalAfter();
          }

          return true;
        }).then(function() {
          if (done) {
            try {
              done(self.childProcessOutput, code);
            } catch (err) {
              done(err);
            }
          }

          if (code) {
            process.exit(code);
          }
        });

      });
    });

    return this;
  },

  getAvailableColors : function () {
    var availColors = [
      ['red', 'light_gray'],
      ['green', 'black'],
      ['blue', 'light_gray'],
      ['magenta', 'light_gray']
    ];
    var currentIndex = availColors.length, temporaryValue, randomIndex;

    // While there remain elements to shuffle...
    while (0 !== currentIndex) {
      randomIndex = Math.floor(Math.random() * currentIndex);
      currentIndex -= 1;

      // And swap it with the current element.
      temporaryValue = availColors[currentIndex];
      availColors[currentIndex] = availColors[randomIndex];
      availColors[randomIndex] = temporaryValue;
    }
    return availColors;
  },

  /**
   * Start a new child process for each environment
   * @param {Array} envs
   * @param {function} doneCallback
   */
  startChildProcesses : function(envs, doneCallback) {
    doneCallback = doneCallback || function() {};

    var availColors = this.getAvailableColors();

    this.childProcessOutput = {};
    ChildProcess.prevIndex = 0;

    var args = this.getChildProcessArgs(envs);

    if (envs === null) {
      this.startTestWorkers(availColors, args, doneCallback);
      return this;
    }

    this.startEnvChildren(envs, availColors, args, doneCallback);
  },

  startEnvChildren : function(envs, availColors, args, doneCallback) {
    var self = this;
    var globalExitCode = 0;

    envs.forEach(function(environment, index) {
      self.childProcessOutput[environment] = [];
      var childArgs = args.slice();
      childArgs.push('--env', environment);

      var child = new ChildProcess(environment, index, self.childProcessOutput[environment], self.settings, childArgs);
      child.setLabel(environment + ' environment');
      self.runningProcesses[child.itemKey] = child;
      self.runningProcesses[child.itemKey].run(availColors, function(output, exitCode) {
        if (exitCode > 0) {
          globalExitCode = exitCode;
        }
        if (self.processesRunning() === 0) {
          if (!self.settings.live_output) {
            self.printChildProcessOutput();
          }
          doneCallback(globalExitCode);
        }
      });
    });
  },

  getChildProcessArgs : function(envs) {
    var childProcessArgs = [];
    var arg;

    for (var i = 2; i < process.argv.length; i++) {
      arg = process.argv[i];
      if (envs && (arg == '-e' || arg == '--env')) {
        i++;
      } else {
        childProcessArgs.push(arg);
      }
    }

    return childProcessArgs;
  },

  startTestWorkers : function(availColors, args, doneCallback) {
    var workerCount = this.getTestWorkersCount();
    var source = this.getTestSource();
    var self = this;
    var globalExitCode = 0;
    var globalStartTime = new Date().getTime();
    var globalResults = {
      errmessages : [],
      modules : {},
      passed : 0,
      failed : 0,
      errors : 0,
      skipped : 0,
      tests : 0
    };

    var Reporter = require('../../runner/reporter.js');
    var globalReporter = new Reporter(globalResults, {}, globalStartTime, {start_session: this.startSession});

    Runner.readPaths(source, this.test_settings, function(error, modulePaths) {
      if (error) {
        ErrorHandler.handle(error, null, doneCallback);
        return;
      }

      var remaining = modulePaths.length;
      Utils.processAsyncQueue(workerCount, modulePaths, function(modulePath, index, next) {
        var outputLabel = Utils.getModuleKey(modulePath, self.settings.src_folders, modulePaths);
        var childOutput = self.childProcessOutput[outputLabel] = [];

        // arguments to the new child process, essentially running a single test suite
        var childArgs = args.slice();
        childArgs.push('--test', modulePath, '--test-worker');

        var settings = clone(self.settings);
        settings.output = settings.output && settings.detailed_output;

        var child = new ChildProcess(outputLabel, index, childOutput, settings, childArgs);
        child.setLabel(outputLabel);
        child.on('result', function(childResult) {
          switch (childResult.type) {
            case 'testsuite_finished':
              globalResults.modules[childResult.moduleKey] = childResult.results;
              if (childResult.errmessages.length) {
                globalResults.errmessages.concat(childResult.errmessages);
              }

              globalResults.passed += childResult.passed;
              globalResults.failed += childResult.failed;
              globalResults.errors += childResult.errors;
              globalResults.skipped += childResult.skipped;
              globalResults.tests += childResult.tests;

              self.printChildProcessOutput(childResult.itemKey);
              break;
            case 'testsuite_started':

              break;
          }

        });

        self.runningProcesses[child.itemKey] = child;
        self.runningProcesses[child.itemKey].run(availColors, function (output, exitCode) {
          if (exitCode > 0) {
            globalExitCode = exitCode;
          }
          remaining -=1;
          if (remaining > 0) {
            next();
          } else {

            if (!self.settings.live_output) {
              globalReporter.printTotalResults();
            }

            doneCallback(globalExitCode);
          }
        });
      });
    });
  },

  printChildProcessOutput : function(label) {
    var self = this;

    if (label) {
      self.childProcessOutput[label] = self.childProcessOutput[label].filter(function(item) {
        return item !== '';
      }).map(function(item) {
        if (item == '\\n') {
          item = '\n';
        }

        return item;
      });

      self.childProcessOutput[label].forEach(function(output) {
        process.stdout.write(output + '\n');
      });

      self.childProcessOutput[label].length = 0;
      return;
    }

    Object.keys(this.childProcessOutput).forEach(function(environment) {
      self.printChildProcessOutput(environment);
    });
  },

  getTestWorkersCount : function() {
    var workers = 1;
    if (this.settings.test_workers === true || this.settings.test_workers.workers === 'auto') {
      workers = require('os').cpus().length;
    } else if ('number' === typeof this.settings.test_workers.workers) {
      workers = this.settings.test_workers.workers;
    }

    return workers;
  },

  processesRunning : function() {
    var running = 0;
    for (var item in this.runningProcesses) {
      if (this.runningProcesses.hasOwnProperty(item) && this.runningProcesses[item].processRunning) {
        running += 1;
      }
    }
    return running;
  }
};

module.exports = CliRunner;
