'use strict';

Object.defineProperty(exports, "__esModule", {
  value: true
});

var _regenerator;

function _load_regenerator() {
  return _regenerator = _interopRequireDefault(require('babel-runtime/regenerator'));
}

var getFlattenedDependencies = function () {
  var _ref = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee2(scope, component, graph, cache) {
    var _this = this;

    var id, edges, dependencies, flattenedP, flattened, flattenedUnique;
    return (_regenerator || _load_regenerator()).default.wrap(function _callee2$(_context2) {
      while (1) {
        switch (_context2.prev = _context2.next) {
          case 0:
            id = component.id.toString();

            if (graph.hasNode(id)) {
              _context2.next = 3;
              break;
            }

            return _context2.abrupt('return', new (_bitId || _load_bitId()).BitIds());

          case 3:
            edges = (_graphlib || _load_graphlib()).default.alg.preorder(graph, id);
            dependencies = (_ramda || _load_ramda()).default.tail(edges); // the first item is the component itself

            if (dependencies.length) {
              _context2.next = 7;
              break;
            }

            return _context2.abrupt('return', new (_bitId || _load_bitId()).BitIds());

          case 7:
            flattenedP = dependencies.map(function () {
              var _ref2 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee(dependency) {
                var dependencyBitId, versionDependencies, flattenedDependencies;
                return (_regenerator || _load_regenerator()).default.wrap(function _callee$(_context) {
                  while (1) {
                    switch (_context.prev = _context.next) {
                      case 0:
                        if (!cache[dependency]) {
                          _context.next = 2;
                          break;
                        }

                        return _context.abrupt('return', cache[dependency]);

                      case 2:
                        dependencyBitId = graph.node(dependency);
                        versionDependencies = void 0;
                        _context.prev = 4;
                        _context.next = 7;
                        return scope.importDependencies([dependencyBitId]);

                      case 7:
                        versionDependencies = _context.sent;
                        _context.next = 15;
                        break;

                      case 10:
                        _context.prev = 10;
                        _context.t0 = _context['catch'](4);

                        if (!(_context.t0 instanceof (_exceptions2 || _load_exceptions2()).DependencyNotFound)) {
                          _context.next = 14;
                          break;
                        }

                        return _context.abrupt('return', [dependencyBitId]);

                      case 14:
                        throw _context.t0;

                      case 15:
                        _context.next = 17;
                        return (0, (_flattenDependencies || _load_flattenDependencies()).flattenDependencyIds)(versionDependencies, scope.objects);

                      case 17:
                        flattenedDependencies = _context.sent;

                        // Store the flatten dependencies in cache
                        cache[dependency] = flattenedDependencies;
                        return _context.abrupt('return', flattenedDependencies);

                      case 20:
                      case 'end':
                        return _context.stop();
                    }
                  }
                }, _callee, _this, [[4, 10]]);
              }));

              return function (_x5) {
                return _ref2.apply(this, arguments);
              };
            }());
            _context2.next = 10;
            return Promise.all(flattenedP);

          case 10:
            flattened = _context2.sent;
            flattenedUnique = (_bitId || _load_bitId()).BitIds.fromArray((_ramda || _load_ramda()).default.flatten(flattened)).getUniq();
            // when a component has cycle dependencies, the flattenedDependencies contains the component itself. remove it.

            return _context2.abrupt('return', flattenedUnique.removeIfExistWithoutVersion(component.id));

          case 13:
          case 'end':
            return _context2.stop();
        }
      }
    }, _callee2, this);
  }));

  return function getFlattenedDependencies(_x, _x2, _x3, _x4) {
    return _ref.apply(this, arguments);
  };
}();

var setFutureVersions = function () {
  var _ref3 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee4(componentsToTag, scope, releaseType, exactVersion) {
    var _this2 = this;

    return (_regenerator || _load_regenerator()).default.wrap(function _callee4$(_context4) {
      while (1) {
        switch (_context4.prev = _context4.next) {
          case 0:
            _context4.next = 2;
            return Promise.all(componentsToTag.map(function () {
              var _ref4 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee3(componentToTag) {
                var modelComponent, version;
                return (_regenerator || _load_regenerator()).default.wrap(function _callee3$(_context3) {
                  while (1) {
                    switch (_context3.prev = _context3.next) {
                      case 0:
                        _context3.next = 2;
                        return scope.sources.findOrAddComponent(componentToTag);

                      case 2:
                        modelComponent = _context3.sent;
                        version = modelComponent.getVersionToAdd(releaseType, exactVersion);

                        componentToTag.usedVersion = componentToTag.version;
                        componentToTag.version = version;

                      case 6:
                      case 'end':
                        return _context3.stop();
                    }
                  }
                }, _callee3, _this2);
              }));

              return function (_x10) {
                return _ref4.apply(this, arguments);
              };
            }()));

          case 2:
          case 'end':
            return _context4.stop();
        }
      }
    }, _callee4, this);
  }));

  return function setFutureVersions(_x6, _x7, _x8, _x9) {
    return _ref3.apply(this, arguments);
  };
}();

/**
 * make sure the originallySharedDir was added before saving the component. also, make sure it was
 * not added twice.
 * we need three objects for this:
 * 1) component.pendingVersion => version pending to be saved in the filesystem. we want to make sure it has the added sharedDir.
 * 2) component.componentFromModel => previous version of the component. it has the original sharedDir.
 * 3) component.componentMap => current paths in the filesystem, which don't have the sharedDir.
 *
 * The component may be changed from the componentFromModel. The files may be removed and added and
 * new files may added, so we can't compare the files of componentFromModel to component.
 *
 * What we can do is calculating the sharedDir from component.componentFromModel
 * then, make sure that calculatedSharedDir + pathFromComponentMap === component.pendingVersion
 */


var _path;

function _load_path() {
  return _path = _interopRequireDefault(require('path'));
}

var _ramda;

function _load_ramda() {
  return _ramda = _interopRequireDefault(require('ramda'));
}

var _ramdaAdjunct;

function _load_ramdaAdjunct() {
  return _ramdaAdjunct = _interopRequireWildcard(require('ramda-adjunct'));
}

var _graphlib;

function _load_graphlib() {
  return _graphlib = _interopRequireDefault(require('graphlib'));
}

var _graphlib2;

function _load_graphlib2() {
  return _graphlib2 = require('graphlib');
}

var _pMapSeries;

function _load_pMapSeries() {
  return _pMapSeries = _interopRequireDefault(require('p-map-series'));
}

var _;

function _load_() {
  return _ = require('..');
}

var _consumer;

function _load_consumer() {
  return _consumer = require('../../consumer');
}

var _loaderMessages;

function _load_loaderMessages() {
  return _loaderMessages = require('../../cli/loader/loader-messages');
}

var _component;

function _load_component() {
  return _component = _interopRequireDefault(require('../../consumer/component'));
}

var _component2;

function _load_component2() {
  return _component2 = _interopRequireDefault(require('../models/component'));
}

var _loader;

function _load_loader() {
  return _loader = _interopRequireDefault(require('../../cli/loader'));
}

var _logger;

function _load_logger() {
  return _logger = _interopRequireDefault(require('../../logger/logger'));
}

var _analytics;

function _load_analytics() {
  return _analytics = require('../../analytics/analytics');
}

var _exceptions;

function _load_exceptions() {
  return _exceptions = require('../../consumer/exceptions');
}

var _utils;

function _load_utils() {
  return _utils = require('../../utils');
}

var _source;

function _load_source() {
  return _source = _interopRequireDefault(require('../models/source'));
}

var _exceptions2;

function _load_exceptions2() {
  return _exceptions2 = require('../exceptions');
}

var _bitId;

function _load_bitId() {
  return _bitId = require('../../bit-id');
}

var _flattenDependencies;

function _load_flattenDependencies() {
  return _flattenDependencies = require('../flatten-dependencies');
}

var _validationError;

function _load_validationError() {
  return _validationError = _interopRequireDefault(require('../../error/validation-error'));
}

var _constants;

function _load_constants() {
  return _constants = require('../../constants');
}

var _generalError;

function _load_generalError() {
  return _generalError = _interopRequireDefault(require('../../error/general-error'));
}

var _dependencies;

function _load_dependencies() {
  return _dependencies = require('../../consumer/component/dependencies');
}

var _autoTag;

function _load_autoTag() {
  return _autoTag = require('./auto-tag');
}

function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } }

function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; }

function _asyncToGenerator(fn) { return function () { var gen = fn.apply(this, arguments); return new Promise(function (resolve, reject) { function step(key, arg) { try { var info = gen[key](arg); var value = info.value; } catch (error) { reject(error); return; } if (info.done) { resolve(value); } else { return Promise.resolve(value).then(function (value) { step("next", value); }, function (err) { step("throw", err); }); } } return step("next"); }); }; }

function buildComponentsGraph(components) {
  var setGraphEdges = function setGraphEdges(component, dependencies, graph) {
    var id = component.id.toString();
    dependencies.get().forEach(function (dependency) {
      var depId = dependency.id.toString();
      // save the full BitId of a string id to be able to retrieve it later with no confusion
      if (!graph.hasNode(id)) graph.setNode(id, component.id);
      if (!graph.hasNode(depId)) graph.setNode(depId, dependency.id);
      graph.setEdge(id, depId);
    });
  };

  var graphDeps = new (_graphlib2 || _load_graphlib2()).Graph();
  var graphDevDeps = new (_graphlib2 || _load_graphlib2()).Graph();
  var graphCompilerDeps = new (_graphlib2 || _load_graphlib2()).Graph();
  var graphTesterDeps = new (_graphlib2 || _load_graphlib2()).Graph();
  components.forEach(function (component) {
    setGraphEdges(component, component.dependencies, graphDeps);
    setGraphEdges(component, component.devDependencies, graphDevDeps);
    setGraphEdges(component, component.compilerDependencies, graphCompilerDeps);
    setGraphEdges(component, component.testerDependencies, graphTesterDeps);
  });
  return { graphDeps, graphDevDeps, graphCompilerDeps, graphTesterDeps };
}

function updateDependenciesVersions(componentsToTag) {
  var updateDependencyVersion = function updateDependencyVersion(dependency) {
    var foundDependency = componentsToTag.find(function (component) {
      return component.id.isEqualWithoutVersion(dependency.id);
    });
    if (foundDependency) dependency.id = dependency.id.changeVersion(foundDependency.version);
  };
  componentsToTag.forEach(function (oneComponentToTag) {
    oneComponentToTag.getAllDependencies().forEach(function (dependency) {
      return updateDependencyVersion(dependency);
    });
  });
}

function validateOriginallySharedDir(components) {
  var throwOnError = function throwOnError(expectedPath, actualPath) {
    if (expectedPath !== actualPath) {
      throw new (_validationError || _load_validationError()).default(`failed validating the component paths with sharedDir, expected path ${expectedPath}, got ${actualPath}`);
    }
  };
  var validateComponent = function validateComponent(component) {
    if (!component.componentMap) throw new Error(`componentMap is missing from ${component.id.toString()}`);
    if (!component.componentFromModel) return;
    component.componentFromModel.setOriginallySharedDir();
    var sharedDir = component.componentFromModel.originallySharedDir;
    var pathWithSharedDir = function pathWithSharedDir(pathStr) {
      if (sharedDir && component.componentMap.origin === (_constants || _load_constants()).COMPONENT_ORIGINS.IMPORTED) {
        return (0, (_utils || _load_utils()).pathJoinLinux)(sharedDir, pathStr);
      }
      return pathStr;
    };
    var expectedMainFile = pathWithSharedDir(component.componentMap.mainFile);
    throwOnError(expectedMainFile, component.pendingVersion.mainFile);
    var componentMapFiles = component.componentMap.getAllFilesPaths();
    var componentFiles = component.pendingVersion.files.map(function (file) {
      return file.relativePath;
    });
    componentMapFiles.forEach(function (file) {
      var expectedFile = pathWithSharedDir(file);
      if (!componentFiles.includes(expectedFile)) {
        throw new (_validationError || _load_validationError()).default(`failed validating the component paths, expected a file ${expectedFile} to be in ${componentFiles.toString()} array`);
      }
    });
  };
  components.forEach(function (component) {
    return validateComponent(component);
  });
}

exports.default = function () {
  var _ref6 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee7(_ref5) {
    var _this3 = this;

    var consumerComponents = _ref5.consumerComponents,
        scope = _ref5.scope,
        message = _ref5.message,
        exactVersion = _ref5.exactVersion,
        releaseType = _ref5.releaseType,
        force = _ref5.force,
        consumer = _ref5.consumer,
        _ref5$ignoreNewestVer = _ref5.ignoreNewestVersion,
        ignoreNewestVersion = _ref5$ignoreNewestVer === undefined ? false : _ref5$ignoreNewestVer,
        _ref5$skipTests = _ref5.skipTests,
        skipTests = _ref5$skipTests === undefined ? false : _ref5$skipTests,
        _ref5$verbose = _ref5.verbose,
        verbose = _ref5$verbose === undefined ? false : _ref5$verbose;

    var consumerComponentsIdsMap, componentsToTag, componentsToTagIds, componentsToTagIdsLatest, autoTagCandidates, autoTagComponents, autoTagComponentsLoaded, autoTagConsumerComponents, componentsToBuildAndTest, newestVersionsP, newestVersions, newestVersionsWithoutEmpty, testsResults, testsResultsP, _buildComponentsGraph, graphDeps, graphDevDeps, graphCompilerDeps, graphTesterDeps, dependenciesCache, persistComponent, taggedComponents, autoTaggedComponents;

    return (_regenerator || _load_regenerator()).default.wrap(function _callee7$(_context7) {
      while (1) {
        switch (_context7.prev = _context7.next) {
          case 0:
            (_loader || _load_loader()).default.start((_loaderMessages || _load_loaderMessages()).BEFORE_IMPORT_PUT_ON_SCOPE);
            consumerComponentsIdsMap = {};
            // Concat and unique all the dependencies from all the components so we will not import
            // the same dependency more then once, it's mainly for performance purpose

            consumerComponents.forEach(function (consumerComponent) {
              var componentIdString = consumerComponent.id.toString();
              // Store it in a map so we can take it easily from the sorted array which contain only the id
              consumerComponentsIdsMap[componentIdString] = consumerComponent;
            });
            componentsToTag = (_ramda || _load_ramda()).default.values(consumerComponentsIdsMap); // consumerComponents unique

            componentsToTagIds = componentsToTag.map(function (c) {
              return c.id;
            });
            _context7.next = 7;
            return scope.latestVersions(componentsToTagIds, false);

          case 7:
            componentsToTagIdsLatest = _context7.sent;
            _context7.next = 10;
            return consumer.potentialComponentsForAutoTagging(componentsToTagIdsLatest);

          case 10:
            autoTagCandidates = _context7.sent;
            _context7.next = 13;
            return (0, (_autoTag || _load_autoTag()).getAutoTagPending)(scope, autoTagCandidates, componentsToTagIdsLatest);

          case 13:
            autoTagComponents = _context7.sent;
            _context7.next = 16;
            return consumer.loadComponents(autoTagComponents.map(function (c) {
              return c.toBitId();
            }));

          case 16:
            autoTagComponentsLoaded = _context7.sent;
            autoTagConsumerComponents = autoTagComponentsLoaded.components;
            componentsToBuildAndTest = componentsToTag.concat(autoTagConsumerComponents);

            // check for each one of the components whether it is using an old version

            if (ignoreNewestVersion) {
              _context7.next = 27;
              break;
            }

            newestVersionsP = componentsToBuildAndTest.map(function () {
              var _ref7 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee5(component) {
                var modelComponent, latest;
                return (_regenerator || _load_regenerator()).default.wrap(function _callee5$(_context5) {
                  while (1) {
                    switch (_context5.prev = _context5.next) {
                      case 0:
                        if (!component.componentFromModel) {
                          _context5.next = 9;
                          break;
                        }

                        _context5.next = 3;
                        return scope.getModelComponentIfExist(component.id);

                      case 3:
                        modelComponent = _context5.sent;

                        if (modelComponent) {
                          _context5.next = 6;
                          break;
                        }

                        throw new (_generalError || _load_generalError()).default(`component ${component.id} was not found in the model`);

                      case 6:
                        latest = modelComponent.latest();

                        if (!(latest !== component.version)) {
                          _context5.next = 9;
                          break;
                        }

                        return _context5.abrupt('return', {
                          componentId: component.id.toStringWithoutVersion(),
                          currentVersion: component.version,
                          latestVersion: latest
                        });

                      case 9:
                        return _context5.abrupt('return', null);

                      case 10:
                      case 'end':
                        return _context5.stop();
                    }
                  }
                }, _callee5, _this3);
              }));

              return function (_x12) {
                return _ref7.apply(this, arguments);
              };
            }());
            _context7.next = 23;
            return Promise.all(newestVersionsP);

          case 23:
            newestVersions = _context7.sent;
            newestVersionsWithoutEmpty = newestVersions.filter(function (newest) {
              return newest;
            });

            if ((_ramdaAdjunct || _load_ramdaAdjunct()).isNilOrEmpty(newestVersionsWithoutEmpty)) {
              _context7.next = 27;
              break;
            }

            throw new (_exceptions || _load_exceptions()).NewerVersionFound(newestVersionsWithoutEmpty);

          case 27:

            (_logger || _load_logger()).default.debug('scope.putMany: sequentially build all components');
            (_analytics || _load_analytics()).Analytics.addBreadCrumb('scope.putMany', 'scope.putMany: sequentially build all components');
            _context7.next = 31;
            return scope.buildMultiple(componentsToBuildAndTest, consumer, false, verbose);

          case 31:

            (_logger || _load_logger()).default.debug('scope.putMany: sequentially test all components');
            testsResults = [];

            if (skipTests) {
              _context7.next = 47;
              break;
            }

            testsResultsP = scope.testMultiple({
              components: componentsToBuildAndTest,
              consumer,
              verbose,
              rejectOnFailure: !force
            });
            _context7.prev = 35;
            _context7.next = 38;
            return testsResultsP;

          case 38:
            testsResults = _context7.sent;
            _context7.next = 47;
            break;

          case 41:
            _context7.prev = 41;
            _context7.t0 = _context7['catch'](35);

            if (force) {
              _context7.next = 47;
              break;
            }

            if (verbose) {
              _context7.next = 46;
              break;
            }

            throw new (_exceptions || _load_exceptions()).ComponentSpecsFailed();

          case 46:
            throw _context7.t0;

          case 47:

            (_logger || _load_logger()).default.debug('scope.putMany: sequentially persist all components');
            (_analytics || _load_analytics()).Analytics.addBreadCrumb('scope.putMany', 'scope.putMany: sequentially persist all components');

            // go through all components and find the future versions for them
            _context7.next = 51;
            return setFutureVersions(componentsToTag, scope, releaseType, exactVersion);

          case 51:
            // go through all dependencies and update their versions
            updateDependenciesVersions(componentsToTag);
            // build the dependencies graph
            _buildComponentsGraph = buildComponentsGraph(componentsToTag), graphDeps = _buildComponentsGraph.graphDeps, graphDevDeps = _buildComponentsGraph.graphDevDeps, graphCompilerDeps = _buildComponentsGraph.graphCompilerDeps, graphTesterDeps = _buildComponentsGraph.graphTesterDeps;
            dependenciesCache = {};

            persistComponent = function () {
              var _ref8 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee6(consumerComponent) {
                var addSharedDirAndDistEntry, dists, testResult, flattenedDependencies, flattenedDevDependencies, flattenedCompilerDependencies, flattenedTesterDependencies;
                return (_regenerator || _load_regenerator()).default.wrap(function _callee6$(_context6) {
                  while (1) {
                    switch (_context6.prev = _context6.next) {
                      case 0:
                        // when a component is written to the filesystem, the originallySharedDir may be stripped, if it was, the
                        // originallySharedDir is written in bit.map, and then set in consumerComponent.originallySharedDir when loaded.
                        // similarly, when the dists are written to the filesystem, the dist.entry may be stripped, if it was, the
                        // consumerComponent.dists.distEntryShouldBeStripped is set to true.
                        // because the model always has the paths of the original author, in case part of the path was stripped, add it
                        // back before saving to the model. this way, when the author updates the components, the paths will be correct.
                        addSharedDirAndDistEntry = function addSharedDirAndDistEntry(pathStr) {
                          var withSharedDir = consumerComponent.originallySharedDir ? (_path || _load_path()).default.join(consumerComponent.originallySharedDir, pathStr) : pathStr;
                          var withDistEntry = consumerComponent.dists.distEntryShouldBeStripped ? (_path || _load_path()).default.join(consumer.bitJson.distEntry, withSharedDir) : withSharedDir;
                          return (0, (_utils || _load_utils()).pathNormalizeToLinux)(withDistEntry);
                        };

                        dists = !consumerComponent.dists.isEmpty() && consumerComponent.compiler ? consumerComponent.dists.get().map(function (dist) {
                          return {
                            name: dist.basename,
                            relativePath: addSharedDirAndDistEntry(dist.relative),
                            file: (_source || _load_source()).default.from(dist.contents),
                            test: dist.test
                          };
                        }) : null;
                        testResult = void 0;

                        if (!skipTests) {
                          testResult = testsResults.find(function (result) {
                            return consumerComponent.id.isEqualWithoutScopeAndVersion(result.componentId);
                          });
                        }
                        _context6.next = 6;
                        return getFlattenedDependencies(scope, consumerComponent, graphDeps, dependenciesCache);

                      case 6:
                        flattenedDependencies = _context6.sent;
                        _context6.next = 9;
                        return getFlattenedDependencies(scope, consumerComponent, graphDevDeps, dependenciesCache);

                      case 9:
                        flattenedDevDependencies = _context6.sent;
                        _context6.next = 12;
                        return getFlattenedDependencies(scope, consumerComponent, graphCompilerDeps, dependenciesCache);

                      case 12:
                        flattenedCompilerDependencies = _context6.sent;
                        _context6.next = 15;
                        return getFlattenedDependencies(scope, consumerComponent, graphTesterDeps, dependenciesCache);

                      case 15:
                        flattenedTesterDependencies = _context6.sent;
                        _context6.next = 18;
                        return scope.sources.addSource({
                          source: consumerComponent,
                          flattenedDependencies,
                          flattenedDevDependencies,
                          flattenedCompilerDependencies,
                          flattenedTesterDependencies,
                          message,
                          exactVersion,
                          releaseType,
                          dists,
                          specsResults: testResult ? testResult.specs : undefined
                        });

                      case 18:
                        return _context6.abrupt('return', consumerComponent);

                      case 19:
                      case 'end':
                        return _context6.stop();
                    }
                  }
                }, _callee6, _this3);
              }));

              return function persistComponent(_x13) {
                return _ref8.apply(this, arguments);
              };
            }();

            // Run the persistence one by one not in parallel!


            (_loader || _load_loader()).default.start((_loaderMessages || _load_loaderMessages()).BEFORE_PERSISTING_PUT_ON_SCOPE);

            _context7.next = 58;
            return (0, (_pMapSeries || _load_pMapSeries()).default)(componentsToTag, function (consumerComponent) {
              return persistComponent(consumerComponent);
            });

          case 58:
            taggedComponents = _context7.sent;
            _context7.next = 61;
            return (0, (_autoTag || _load_autoTag()).bumpDependenciesVersions)(scope, autoTagCandidates, taggedComponents);

          case 61:
            autoTaggedComponents = _context7.sent;

            validateOriginallySharedDir(taggedComponents);
            _context7.next = 65;
            return scope.objects.persist();

          case 65:
            return _context7.abrupt('return', { taggedComponents, autoTaggedComponents });

          case 66:
          case 'end':
            return _context7.stop();
        }
      }
    }, _callee7, this, [[35, 41]]);
  }));

  function tagModelComponent(_x11) {
    return _ref6.apply(this, arguments);
  }

  return tagModelComponent;
}();