text: 'use strict';
  
  const path = require('path');
  const _ = require('lodash');
  const chalk = require('chalk');
  const Promise = require('bluebird');
  const ReportBuilderFactory = require('../../report-builder-factory');
  const EventSource = require('../event-source');
  const utils = require('../../server-utils');
  const {findTestResult} = require('./utils');
  const {findNode} = require('../../../lib/static/modules/utils');
  const reporterHelper = require('../../reporter-helpers');
  
  module.exports = class ToolRunner {
      static create(paths, tool, configs) {
          return new this(paths, tool, configs);
      }
  
      constructor(paths, tool, {program: globalOpts, pluginConfig, options: guiOpts}) {
          this._toolName = globalOpts.name();
  
          this._testFiles = [].concat(paths);
          this._tool = tool;
          this._tree = null;
          this._collection = null;
  
          this._globalOpts = globalOpts;
          this._guiOpts = guiOpts;
          this._reportPath = pluginConfig.path;
  
          this._eventSource = new EventSource();
          this._reportBuilder = ReportBuilderFactory.create(this._toolName, tool, pluginConfig);
          this._reportBuilder.setApiValues(tool.htmlReporter.values);
      }
  
      get config() {
          return this._tool.config;
      }
  
      get tree() {
          return this._tree;
      }
  
      initialize() {
          return this._readTests()
              .then((collection) => {
                  this._collection = collection;
  
                  this._handleRunnableCollection();
                  this._subscribeOnEvents();
              });
      }
  
      _readTests() {
          const {grep, set: sets, browser: browsers} = this._globalOpts;
  
          return this._tool.readTests(this._testFiles, {grep, sets, browsers});
      }
  
      finalize() {
          this._reportBuilder.saveDataFileSync();
      }
  
      addClient(connection) {
          this._eventSource.addConnection(connection);
      }
  
      sendClientEvent(event, data) {
          this._eventSource.emit(event, data);
      }
  
      updateReferenceImage(tests) {
          const reportBuilder = this._reportBuilder;
  
          return Promise.map(tests, (test) => {
              const updateResult = this._prepareUpdateResult(test);
              const formattedResult = reportBuilder.format(updateResult);
  
              return Promise.map(updateResult.imagesInfo, (imageInfo) => {
                  const {stateName} = imageInfo;
  
                  return reporterHelper.updateReferenceImage(formattedResult, this._reportPath, stateName)
                      .then(() => {
                          const result = _.extend(updateResult, {refImg: imageInfo.expectedImg});
  
                          this._emitUpdateReference(result, stateName);
                      });
              }).then(() => {
                  reportBuilder.addUpdated(updateResult);
  
                  return findTestResult(reportBuilder.getSuites(), formattedResult.prepareTestResult());
              });
          });
      }
  
      _fillTestsTree() {
          const {autoRun} = this._guiOpts;
          this._tree = Object.assign(this._reportBuilder.getResult(), {gui: true, autoRun});
          this._tree.suites = this._applyReuseData(this._tree.suites);
      }
  
      _applyReuseData(testSuites) {
          if (!testSuites) {
              return;
          }
  
          const reuseData = this._loadReuseData();
  
          if (_.isEmpty(reuseData.suites)) {
              return testSuites;
          }
  
          return testSuites.map((suite) => applyReuse(reuseData)(suite));
      }
  
      _loadReuseData() {
          try {
              return utils.require(path.resolve(this._reportPath, 'data'));
          } catch (e) {
              utils.logger.warn(chalk.yellow(`Nothing to reuse in ${this._reportPath}`));
              return {};
          }
      }
  };
  
  function applyReuse(reuseData) {
      let isBrowserResultReused = false;
  
      const reuseBrowserResult = (suite) => {
          if (suite.children) {
              suite.children = suite.children.map(reuseBrowserResult);
  
              return isBrowserResultReused
                  ? _.set(suite, 'status', getReuseStatus(reuseData.suites, suite))
                  : suite;
          }
  
          return _.set(suite, 'browsers', suite.browsers.map((bro) => {
              const browserResult = getReuseBrowserResult(reuseData.suites, suite.suitePath, bro.name);
  
              if (browserResult) {
                  isBrowserResultReused = true;
                  suite.status = getReuseStatus(reuseData.suites, suite);
              }
  
              return _.extend(bro, browserResult);
          }));
      };
  
      return reuseBrowserResult;
  }
  
  function getReuseStatus(reuseSuites, {suitePath, status: defaultStatus}) {
      const reuseNode = findNode(reuseSuites, suitePath);
      return _.get(reuseNode, 'status', defaultStatus);
  }
  
  function getReuseBrowserResult(reuseSuites, suitePath, browserId) {
      const reuseNode = findNode(reuseSuites, suitePath);
      return _.find(_.get(reuseNode, 'browsers'), {name: browserId});
  }
  