text: /**
   * Executes the cucumber scenarios on a pool of workers parallelly
   * @param config {Object} The config object for the execution containing:
   *   config.tags {String[]} The tags that will be passed to cucumber for execution
   *   config.maxWorkerCount {number} The number of parallel threads the scenarios will be executed, by default 5
   *   config.maxRetryCount {number} The number of maximum retries for failed scenarios, by default 0,
   *   config.hideOutput {boolean} The stdout/stderr for the threads can be switched off, by default they are displayed
   *   config.reportPath {String} The path to the final report, by default './reports/report.json'
   *   config.cucumberPath {String} The path to the cucumber executable
   *   config.cucumberOpts {String} The options to start the cucumber framework
   *   config.tempReportPath {String} The path to the temp reports, by default './reports/tmp/'
   *   config.weightingTags {Object} Helper to sort scenarios by duration {pattern: '', default: 1};
   *   config.environment {Object} Key-value pair of environment variables to set, by default empty
   * @constructor
   */
  var ScenariosOnWorkerPool = function (config) {
  
    var
      maxWorkerCount = (config.threads == undefined) ? 5 : config.threads,
      maxRetryCount = (config.retries == undefined) ? 0 : config.retries,
      hideOutput = (config.silent == undefined) ? false : config.silent,
      exec = require('child_process').exec,
      fs = require('fs'),
      REPORT_FILE = config.reportPath || './reports/report.json',
      TEMP_REPORT_PATH = config.tempReportPath || './reports/tmp/',
      RETRY_STR = '_RETRY_',
      activeWorkers = 0,
      allTests = 0,
      failedTests = 0,
      passedTests = 0,
      tagsToGet = {tags: config.tags || []},
      arrayOfScenarioPaths = [],
      testResults = {},
      startTime,
      helper = require('./helper.js'),
      path = require('path'),
      reportCreator = require('./report_creator.js'),
      command = "node " + config.cucumberPath + " " + config.cucumberOpts;
  
    /**
     * Starts to create threads to execute the scenarios. Every scenario runs in its own thread.
     */
    function processScenarios () {
      while (activeWorkers < maxWorkerCount && arrayOfScenarioPaths.length > 0) {
        var
          scenario = arrayOfScenarioPaths.shift(),
          tempReportFile = (TEMP_REPORT_PATH + scenario.replace(/.*\/features\//, '').replace(/:/g, '_').replace('.', '_') + ".json").replace(/\\/g, '/'),
          execution;
  
        // Remove the string from the end of the path indicating it's a rerun if there's any
        scenario = scenario.replace(/_RETRY_\d+/, '');
        helper.createFolderStructure(tempReportFile);
  
        activeWorkers++;
        allTests++;
  
        execution = createExecution(scenario, tempReportFile);
        if (!hideOutput) {
          execution.stdout.pipe(process.stdout);
          execution.stderr.pipe(process.stderr);
        }
      }
    }
  
    /**
     * Creates the child process that will execute the scenario. The callback function for the execution will recursively
     * create scenarios as long as there are new ones to be executed.
     * The last one will call a function to show summary and terminate the execution.
     * @param scenario The 'path:line' format location of the scenario that cucumber will execute
     * @param tempReportFile The location of the temporary report file that will be attached to the report of the whole execution
     */
    function createExecution (scenario, tempReportFile) {
      scenario = path.relative('./', scenario); // on windows cucumber doesn't work well with absolute path (reads all scenarios)
      return exec(command + " --format json:" + tempReportFile + " " + scenario, {maxBuffer: 1024 * 1000}, function (err) {
        if (err) {
          testResults[scenario] = false;
          failedTests++;
          // If retry is enabled and the execution was the first one for the scenario
          if (tempReportFile.indexOf(RETRY_STR) == -1 && maxRetryCount > 0) {
            arrayOfScenarioPaths.push(scenario + RETRY_STR + 1);
            // If the maximum retries were not exceeded yet for the scenario
          } else if (tempReportFile.indexOf(RETRY_STR) != -1 && helper.getRetryCountFromPath(tempReportFile) < maxRetryCount) {
            var count = helper.getRetryCountFromPath(tempReportFile) + 1;
            arrayOfScenarioPaths.push(scenario + RETRY_STR + count);
          }
        } else {
          // If it had undefined step then mark the scenario as failed, otherwise it passed
          if (helper.hadUndefinedStep(tempReportFile)) {
            testResults[scenario] = false;
            failedTests++;
          } else {
            testResults[scenario] = true;
            passedTests++;
          }
        }
        activeWorkers--;
        reportCreator.extendReport(tempReportFile);
        // If there are more tests to execute, or there are other active threads, call the parent function, otherwise finish
        if (arrayOfScenarioPaths.length === 0 && activeWorkers === 0) {
          return result();
        } else {
          return processScenarios();
        }
      });
    }
  
    /**
     * Creates an empty report file for the final report, and sets environment variables listed in config.environment if
     * there is any.
     */
    function initialize () {
      startTime = new Date();
      return helper.getScenarios(tagsToGet, config.weightingTags, config.featuresPaths).then(function (scens) {
        arrayOfScenarioPaths = scens;
        helper.setEnvironmentVariables(config.environment);
        reportCreator.init(REPORT_FILE);
      })
    }
  
    /**
     * Prints the summary to the console, and exits reflecting the outcome of the test execution.
     */
    function result () {
      reportCreator.finalizeReport();
      var overallSuccess = 0,
        failedAfterRetry = 0,
        executionDuration = new Date() - startTime,
        min = Math.floor(executionDuration / 1000 / 60),
        sec = Math.floor((executionDuration / 1000).toFixed(2) - min * 60);
  
      console.log('All tests:                 ' + allTests);
      console.log('Passed tests:              ' + passedTests);
      console.log('Failed tests:              ' + failedTests);
  
      Object.keys(testResults).forEach(function (scenario) {
        if (!testResults[scenario]) {
          overallSuccess = 1;
          failedAfterRetry++;
          console.log(scenario + ' has failed')
        }
      });
      console.log('Failed tests (retry):      ' + failedAfterRetry);
      console.info("Execution time (" + executionDuration + "ms):   " + min + "m " + sec + "s");
      if (config.exit) {
        process.exit(overallSuccess)
      } else {
        return overallSuccess
      }
    }
  
    /**
     * This function is responsible for the execution.
     * It will collect scenarios, execute them, create the report file and terminate the execution at the end.
     */
    this.execute = function () {
      return initialize().then(function () {
        if (arrayOfScenarioPaths.length == 0) {
          return result();
        } else {
          return processScenarios();
        }
      })
    };
  
  };
  
  module.exports = ScenariosOnWorkerPool;
  