const istanbul = require('istanbul-api');
const util = require('./util');

const BROWSER_PLACEHOLDER = '%browser%';

function checkThresholds(thresholds, summary) {
  const failedTypes = [];

  Object.keys(thresholds).forEach(key => {
    const coverage = summary[key].pct;
    if (coverage < thresholds[key]) {
      failedTypes.push(key);
    }
  });

  return failedTypes;
}

function CoverageIstanbulReporter(baseReporterDecorator, logger, config) {
  baseReporterDecorator(this);

  // Copied from https://github.com/angular/angular-cli/pull/9529/files
  // Fixes https://github.com/mattlewis92/karma-coverage-istanbul-reporter/issues/44
  const reporterName = 'coverage-istanbul';
  const hasTrailingReporters =
    config.reporters.slice(-1).pop() !== reporterName;
  if (hasTrailingReporters) {
    this.writeCommonMsg = () => {};
  }

  const log = logger.create('reporter.coverage-istanbul');
  const browserCoverage = new WeakMap();
  const coverageConfig = Object.assign({}, config.coverageIstanbulReporter);

  function addCoverage(coverageMap, browser) {
    const coverage = browserCoverage.get(browser);
    browserCoverage.delete(browser);

    if (!coverage) {
      return;
    }

    Object.keys(coverage).forEach(filename => {
      const fileCoverage = coverage[filename];
      if (fileCoverage.inputSourceMap && coverageConfig.fixWebpackSourcePaths) {
        fileCoverage.inputSourceMap = util.fixWebpackSourcePaths(
          fileCoverage.inputSourceMap,
          config.webpack
        );
      }
      if (
        coverageConfig.skipFilesWithNoCoverage &&
        Object.keys(fileCoverage.statementMap).length === 0 &&
        Object.keys(fileCoverage.fnMap).length === 0 &&
        Object.keys(fileCoverage.branchMap).length === 0
      ) {
        log.debug(`File [${filename}] ignored, nothing could be mapped`);
      } else {
        coverageMap.addFileCoverage(fileCoverage);
      }
    });
  }

  function logThresholdMessage(thresholds, message) {
    if (thresholds.emitWarning) {
      log.warn(message);
    } else {
      log.error(message);
    }
  }

  function createReport(browserOrBrowsers, results) {
    const reportConfigOverride =
      !coverageConfig.combineBrowserReports && coverageConfig.dir
        ? {
            dir: coverageConfig.dir.replace(
              BROWSER_PLACEHOLDER,
              browserOrBrowsers.name
            )
          }
        : {};

    const reportConfig = istanbul.config.loadObject({
      verbose: coverageConfig.verbose === true,
      reporting: Object.assign({}, coverageConfig, reportConfigOverride)
    });
    const reportTypes = reportConfig.reporting.config.reports;

    const reporter = istanbul.createReporter(reportConfig);
    reporter.addAll(reportTypes);

    const coverageMap = istanbul.libCoverage.createCoverageMap();
    const sourceMapStore = istanbul.libSourceMaps.createSourceMapStore();

    if (coverageConfig.combineBrowserReports) {
      browserOrBrowsers.forEach(browser => addCoverage(coverageMap, browser));
    } else {
      addCoverage(coverageMap, browserOrBrowsers);
    }

    const remappedCoverageMap = sourceMapStore.transformCoverage(coverageMap)
      .map;

    if (!coverageConfig.skipFilesWithNoCoverage) {
      coverageMap.files().forEach(path => {
        if (!(path in remappedCoverageMap)) {
          // Re-add empty coverage record
          remappedCoverageMap.addFileCoverage(path);
        }
      });
    }

    log.debug('Writing coverage reports:', reportTypes);
    reporter.write(remappedCoverageMap);

    const userThresholds = coverageConfig.thresholds;

    const thresholds = {
      emitWarning: false,
      global: {
        statements: 0,
        lines: 0,
        branches: 0,
        functions: 0
      },
      each: {
        statements: 0,
        lines: 0,
        branches: 0,
        functions: 0,
        overrides: {}
      }
    };

    if (userThresholds) {
      if (userThresholds.global || userThresholds.each) {
        Object.assign(thresholds.global, userThresholds.global);
        Object.assign(thresholds.each, userThresholds.each);
        if (userThresholds.emitWarning === true) {
          thresholds.emitWarning = true;
        }
      } else {
        Object.assign(thresholds.global, userThresholds);
      }
    }

    let thresholdCheckFailed = false;

    // Adapted from https://github.com/istanbuljs/nyc/blob/98ebdff573be91e1098bb7259776a9082a5c1ce1/index.js#L463-L478
    const globalSummary = remappedCoverageMap.getCoverageSummary();
    const failedGlobalTypes = checkThresholds(thresholds.global, globalSummary);
    failedGlobalTypes.forEach(type => {
      thresholdCheckFailed = true;
      logThresholdMessage(
        thresholds,
        `Coverage for ${type} (${
          globalSummary[type].pct
        }%) does not meet global threshold (${thresholds.global[type]}%)`
      );
    });

    remappedCoverageMap.files().forEach(file => {
      const fileThresholds = Object.assign(
        {},
        thresholds.each,
        util.overrideThresholds(
          file,
          thresholds.each.overrides,
          config.basePath
        )
      );
      delete fileThresholds.overrides;
      const fileSummary = remappedCoverageMap.fileCoverageFor(file).toSummary()
        .data;
      const failedFileTypes = checkThresholds(fileThresholds, fileSummary);

      failedFileTypes.forEach(type => {
        thresholdCheckFailed = true;
        if (coverageConfig.fixWebpackSourcePaths) {
          file = util.fixWebpackFilePath(file);
        }
        logThresholdMessage(
          thresholds,
          `Coverage for ${type} (${
            fileSummary[type].pct
          }%) in file ${file} does not meet per file threshold (${
            fileThresholds[type]
          }%)`
        );
      });
    });

    if (thresholdCheckFailed && results && !thresholds.emitWarning) {
      results.exitCode = 1;
    }
  }

  this.onBrowserComplete = function(browser, result) {
    if (result && result.coverage) {
      browserCoverage.set(browser, result.coverage);
    }
  };

  const baseReporterOnRunComplete = this.onRunComplete;
  this.onRunComplete = function(browsers, results) {
    // eslint-disable-next-line prefer-rest-params
    baseReporterOnRunComplete.apply(this, arguments);

    if (coverageConfig.combineBrowserReports) {
      createReport(browsers, results);
    } else {
      browsers.forEach(browser => {
        createReport(browser, results);
      });
    }
  };
}

CoverageIstanbulReporter.$inject = [
  'baseReporterDecorator',
  'logger',
  'config'
];

module.exports = {
  'reporter:coverage-istanbul': ['type', CoverageIstanbulReporter]
};
