text: 'use strict';
  
  Object.defineProperty(exports, "__esModule", {
     value: true
  });
  
  var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();
  
  var _MathUtil = require('typhonjs-escomplex-commons/dist/utils/MathUtil');
  
  var _MathUtil2 = _interopRequireDefault(_MathUtil);
  
  var _ObjectUtil = require('typhonjs-escomplex-commons/dist/utils/ObjectUtil');
  
  var _ObjectUtil2 = _interopRequireDefault(_ObjectUtil);
  
  function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }
  
  function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }
  
  /**
   * Provides default project metrics gathering and calculation.
   *
   * @see https://en.wikipedia.org/wiki/Adjacency_matrix
   * @see https://en.wikipedia.org/wiki/Distance_matrix
   * @see https://en.wikipedia.org/wiki/Floyd%E2%80%93Warshall_algorithm
   */
  var PluginMetricsProject = function () {
     function PluginMetricsProject() {
        _classCallCheck(this, PluginMetricsProject);
     }
  
     _createClass(PluginMetricsProject, [{
        key: 'onConfigure',
  
        // ESComplexProject plugin callbacks -----------------------------------------------------------------------------
  
        /**
         * Loads any default settings that are not already provided by any user options.
         *
         * @param {object}   ev - escomplex plugin event data.
         *
         * The following options are:
         * ```
         * (boolean)   newmi - Boolean indicating whether the maintainability index should be rebased on a scale from
         *                     0 to 100; defaults to false.
         * ```
         */
        value: function onConfigure(ev) {
           ev.data.settings.noCoreSize = typeof ev.data.options.noCoreSize === 'boolean' ? ev.data.options.noCoreSize : false;
        }
  
        /**
         * Performs final calculations based on collected project report data.
         *
         * @param {object}   ev - escomplex plugin event data.
         */
  
     }, {
        key: 'onProjectEnd',
        value: function onProjectEnd(ev) {
           var pathModule = ev.data.pathModule;
           var projectReport = ev.data.projectReport;
  
           var adjacencyMatrix = this._calculateAdjacencyMatrix(pathModule, projectReport);
  
           if (!this.settings.noCoreSize) {
              var visibilityMatrix = this._calculateVisibilityMatrix(projectReport, adjacencyMatrix);
              this._calculateCoreSize(projectReport, visibilityMatrix);
           }
  
           this._calculateAverages(projectReport);
        }
  
        /**
         * Stores settings.
         *
         * @param {object}   ev - escomplex plugin event data.
         */
  
     }, {
        key: 'onProjectStart',
        value: function onProjectStart(ev) {
           /**
            * Stores the settings for all ESComplexProject plugins.
            * @type {object}
            */
           this.settings = ev.data.settings;
        }
  
        // Project metrics calculation -----------------------------------------------------------------------------------
  
        /**
         * Calculates an adjacency matrix for all modules based on ES Module and CommonJS dependencies also storing a
         * compacted while returning the matrix for further calculation. Each row index corresponds to the same module index.
         * Each row entry corresponds to a module index. These relationships dictate the dependencies between all
         * module ModuleReports given the source paths.
         *
         * @param {object}   pathModule - A module that conforms to the Node path API.
         * @param {object}   projectReport - The ProjectResult being processed.
         *
         * @returns {Array<Array<number>>}
         * @private
         */
  
     }, {
        key: '_calculateAdjacencyMatrix',
        value: function _calculateAdjacencyMatrix(pathModule, projectReport) {
           var modules = projectReport.modules;
           var length = modules.length;
  
           var adjacencyMatrix = _MathUtil2.default.create2DArray(length, 0);
  
           var density = 0;
  
           for (var x = 0; x < length; x++) {
              for (var y = 0; y < length; y++) {
                 adjacencyMatrix[x][y] = x !== y && this._doesDependencyExist(pathModule, modules[x], modules[y]) ? 1 : 0;
  
                 if (adjacencyMatrix[x][y] === 1) {
                    density += 1;
                 }
              }
           }
  
           projectReport.adjacencyList = _MathUtil2.default.compactMatrix(adjacencyMatrix);
  
           projectReport.firstOrderDensity = _MathUtil2.default.getPercent(density, length * length);
  
           return adjacencyMatrix;
        }
  
        /**
         * Calculates average ModuleReport metrics that are applicable to ProjectResult.
         *
         * @param {object}   projectReport - The ProjectResult being processed.
         *
         * @private
         */
  
     }, {
        key: '_calculateAverages',
        value: function _calculateAverages(projectReport) {
           var divisor = projectReport.modules.length === 0 ? 1 : projectReport.modules.length;
  
           var moduleAverage = projectReport.moduleAverage;
           var moduleAverageKeys = _ObjectUtil2.default.getAccessorList(moduleAverage);
  
           // Defer to ModuleReport to sum all relevant module metrics applicable to ProjectResult.
           projectReport.modules.forEach(function (module) {
              moduleAverageKeys.forEach(function (averageKey) {
                 var targetValue = _ObjectUtil2.default.safeAccess(module, averageKey, 0);
                 _ObjectUtil2.default.safeSet(moduleAverage, averageKey, targetValue, 'add');
              });
           });
  
           moduleAverageKeys.forEach(function (averageKey) {
              _ObjectUtil2.default.safeSet(moduleAverage, averageKey, divisor, 'div');
           });
        }
  
        /**
         * Calculates core size which is the percentage of modules / files that are both widely depended on and themselves
         * depend on other modules. Lower is better.
         *
         * @param {object}               projectReport - The ProjectResult being processed.
         * @param {Array<Array<number>>} visibilityMatrix - The calculated visibilityMatrix.
         *
         * @private
         */
  
     }, {
        key: '_calculateCoreSize',
        value: function _calculateCoreSize(projectReport, visibilityMatrix) {
           if (projectReport.firstOrderDensity === 0) {
              projectReport.coreSize = 0;
              return;
           }
  
           var length = visibilityMatrix.length;
  
           var fanIn = new Array(length);
           var fanOut = new Array(length);
           var coreSize = 0;
  
           var _loop = function _loop(rowIndex) {
              fanIn[rowIndex] = visibilityMatrix[rowIndex].reduce(function (sum, value, valueIndex) {
                 fanOut[valueIndex] = rowIndex === 0 ? value : fanOut[valueIndex] + value;
                 return sum + value;
              }, 0);
           };
  
           for (var rowIndex = 0; rowIndex < length; rowIndex++) {
              _loop(rowIndex);
           }
  
           // Boundary values can also be chosen by looking for discontinuity in the
           // distribution of values, but to keep it simple the median is used.
           var boundaries = {
              fanIn: _MathUtil2.default.getMedian(fanIn.slice()),
              fanOut: _MathUtil2.default.getMedian(fanOut.slice())
           };
  
           for (var _rowIndex = 0; _rowIndex < length; _rowIndex++) {
              if (fanIn[_rowIndex] >= boundaries.fanIn && fanOut[_rowIndex] >= boundaries.fanOut) {
                 coreSize += 1;
              }
           }
  
           projectReport.coreSize = _MathUtil2.default.getPercent(coreSize, length);
        }
  
        /**
         * Stores a compacted form of the visibility matrix. Each row index corresponds to the same module index.
         * Each row entry corresponds to a module index. These relationships dictate the reverse visibility between all
         * module ModuleReports which may indirectly impact the given module / file. The full matrix is returned for further
         * calculation.
         *
         * Implementation of Floyd Warshall algorithm for calculating visibility matrix in O(n^3) instead of O(n^4) with
         * successive raising of powers.
         *
         * @param {object}               projectReport - The ProjectResult being processed.
         * @param {Array<Array<number>>} adjacencyMatrix - The calculated adjacencyMatrix.
         *
         * @return {Array<Array<number>>}
         * @private
         */
  
     }, {
        key: '_calculateVisibilityMatrix',
        value: function _calculateVisibilityMatrix(projectReport, adjacencyMatrix) {
           var changeCost = 0;
  
           var length = adjacencyMatrix.length;
           var visibilityMatrix = _MathUtil2.default.create2DArray(length, 0);
  
           // Convert adjacency matrix to a distance matrix suitable for the Floyd Warshall algorithm.
           // if i !== j and adjacency matrix value is 0 set distance to Infinity.
           for (var x = 0; x < length; x++) {
              for (var y = 0; y < length; y++) {
                 visibilityMatrix[x][y] = x === y ? 1 : adjacencyMatrix[x][y] || Infinity;
              }
           }
  
           // Floyd Warshall core algorithm
           for (var k = 0; k < length; k++) {
              for (var _x = 0; _x < length; _x++) {
                 for (var _y = 0; _y < length; _y++) {
                    if (visibilityMatrix[_x][_y] > visibilityMatrix[_x][k] + visibilityMatrix[k][_y]) {
                       visibilityMatrix[_x][_y] = visibilityMatrix[_x][k] + visibilityMatrix[k][_y];
                    }
                 }
              }
           }
  
           // Convert back from a distance matrix to adjacency matrix while also calculating change cost.
           for (var _x2 = 0; _x2 < length; _x2++) {
              for (var _y2 = 0; _y2 < length; _y2++) {
                 if (visibilityMatrix[_x2][_y2] < Infinity) {
                    changeCost++;
  
                    if (_x2 !== _y2) {
                       visibilityMatrix[_x2][_y2] = 1;
                    }
                 } else {
                    visibilityMatrix[_x2][_y2] = 0;
                 }
              }
           }
  
           projectReport.visibilityList = _MathUtil2.default.compactMatrix(visibilityMatrix);
  
           projectReport.changeCost = _MathUtil2.default.getPercent(changeCost, length * length);
  
           return visibilityMatrix;
        }
  
        /**
         * Determines if there is at least one dependency that matches `toModuleReport.srcPath` from all the dependencies
         * stored in `fromModuleReport`.
         *
         * @param {object}         pathModule - A module that conforms to the Node path API.
         * @param {ModuleReport}   fromModuleReport - A ModuleReport to match to the srcPath of `toModuleReport`.
         * @param {ModuleReport}   toModuleReport - A ModuleReport providing the `srcPath` to match.
         *
         * @returns {boolean}
         * @private
         */
  
     }, {
        key: '_doesDependencyExist',
        value: function _doesDependencyExist(pathModule, fromModuleReport, toModuleReport) {
           var matchedDependency = false;
           var fromModuleReport_dirname = pathModule.dirname(fromModuleReport.srcPath);
  
           // First test for srcPathAlias which is the case when an NPM or JSPM module has a main entry and is mapped to a
           // given name or alias.
           for (var cntr = 0; cntr < fromModuleReport.dependencies.length; cntr++) {
              var depPath = fromModuleReport.dependencies[cntr].path;
  
              if (typeof toModuleReport.srcPathAlias === 'string' && depPath === toModuleReport.srcPathAlias) {
                 matchedDependency = true;
                 break;
              }
           }
  
           // Exit early if alias match was found above.
           if (matchedDependency) {
              return true;
           }
  
           // Now test for srcPath matches.
           for (var _cntr = 0; _cntr < fromModuleReport.dependencies.length; _cntr++) {
              var _depPath = fromModuleReport.dependencies[_cntr].path;
  
              // If there is no extension provided in the dependency then add the extension of the `to srcPath`.
              if (pathModule.extname(_depPath) === '') {
                 _depPath += pathModule.extname(toModuleReport.srcPath);
              }
  
              // Best case match scenario when dependency matches toModuleReportPath.srcPath.
              if (_depPath === toModuleReport.srcPath) {
                 matchedDependency = true;
                 break;
              }
  
              // Make sure that fromModuleReport dirname has the path separator prepended. This is necessary to make sure
              // pathModule (Node.js path) treats `fromModuleReport_dirname` as the absolute root.
              if (!fromModuleReport_dirname.startsWith(pathModule.sep)) {
                 fromModuleReport_dirname = '' + pathModule.sep + fromModuleReport_dirname;
              }
  
              if (pathModule.resolve(fromModuleReport_dirname, _depPath) === toModuleReport.srcPath) {
                 matchedDependency = true;
                 break;
              }
  
              var toModuleReport_modpath = toModuleReport.srcPath;
  
              // Remove any local directory (`.`) leading character from `toModuleReport_modpath`.
              if (toModuleReport_modpath.startsWith('.')) {
                 toModuleReport_modpath = toModuleReport_modpath.replace(/^\./, '');
              }
  
              // Ensure `toModuleReport_modpath` starts with the path separator.
              if (!toModuleReport_modpath.startsWith(pathModule.sep)) {
                 toModuleReport_modpath = '' + pathModule.sep + toModuleReport_modpath;
              }
  
              if (pathModule.resolve(fromModuleReport_dirname, _depPath) === toModuleReport_modpath) {
                 matchedDependency = true;
                 break;
              }
           }
  
           return matchedDependency;
        }
     }]);
  
     return PluginMetricsProject;
  }();
  
  exports.default = PluginMetricsProject;
  module.exports = exports['default'];