'use strict';

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

var _regenerator;

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

var _bluebird;

function _load_bluebird() {
  return _bluebird = require('bluebird');
}

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 _path;

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

var _stringFormat;

function _load_stringFormat() {
  return _stringFormat = _interopRequireDefault(require('string-format'));
}

var _fsExtra;

function _load_fsExtra() {
  return _fsExtra = _interopRequireDefault(require('fs-extra'));
}

var _ramda;

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

var _commentJson;

function _load_commentJson() {
  return _commentJson = _interopRequireDefault(require('comment-json'));
}

var _logger;

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

var _constants;

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

var _exceptions;

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

var _bitId;

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

var _utils;

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

var _componentMap;

function _load_componentMap() {
  return _componentMap = _interopRequireDefault(require('./component-map'));
}

var _generalError;

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

var _invalidConfigDir;

function _load_invalidConfigDir() {
  return _invalidConfigDir = _interopRequireDefault(require('./exceptions/invalid-config-dir'));
}

var _config;

function _load_config() {
  return _config = _interopRequireDefault(require('../config'));
}

var _configDir;

function _load_configDir() {
  return _configDir = _interopRequireDefault(require('./config-dir'));
}

var _workspaceConfig;

function _load_workspaceConfig() {
  return _workspaceConfig = _interopRequireDefault(require('../config/workspace-config'));
}

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"); } }

var BitMap = function () {
  // path => componentId
  function BitMap(projectRoot, mapPath, version) {
    var _this = this;

    _classCallCheck(this, BitMap);

    this._invalidateCache = function () {
      _this.paths = {};
      _this.pathsLowerCase = {};
      _this._cacheIds = {};
      _this.allTrackDirs = undefined;
    };

    this.projectRoot = projectRoot;
    this.mapPath = mapPath;
    this.components = {};
    this.hasChanged = false;
    this.version = version;
    this.paths = {};
    this.pathsLowerCase = {};
    this._cacheIds = {};
    this.markAsChangedBinded = this.markAsChanged.bind(this);
  } // path => componentId


  _createClass(BitMap, [{
    key: 'markAsChanged',
    value: function markAsChanged() {
      this.hasChanged = true;
      this._invalidateCache();
    }
  }, {
    key: 'setComponent',
    value: function setComponent(bitId, componentMap) {
      var id = bitId.toString();
      if (!bitId.hasVersion() && bitId.scope) {
        throw new (_generalError || _load_generalError()).default(`invalid bitmap id ${id}, a component must have a version when a scope-name is included`);
      }
      if (componentMap.origin !== (_constants || _load_constants()).COMPONENT_ORIGINS.NESTED) {
        // make sure there are no duplications (same name)
        var similarIds = this.findSimilarIds(bitId, true);
        if (similarIds.length) {
          throw new (_generalError || _load_generalError()).default(`your id ${id} is duplicated with ${similarIds.toString()}`);
        }
      }

      componentMap.id = bitId;
      this.components[id] = componentMap;
      this.markAsChanged();
    }
  }, {
    key: 'setComponentProp',
    value: function setComponentProp(id, propName, val) {
      var componentMap = this.getComponent(id, { ignoreScopeAndVersion: true });
      // $FlowFixMe
      componentMap[propName] = val;
      this.markAsChanged();
      return componentMap;
    }
  }, {
    key: 'isEmpty',
    value: function isEmpty() {
      return (_ramda || _load_ramda()).default.isEmpty(this.components);
    }
  }, {
    key: 'removeComponentProp',
    value: function removeComponentProp(id, propName) {
      var componentMap = this.getComponent(id, { ignoreScopeAndVersion: true });
      // $FlowFixMe
      delete componentMap[propName];
      this.markAsChanged();
      return componentMap;
    }
  }, {
    key: 'loadComponents',
    value: function loadComponents(componentsJson) {
      var _this2 = this;

      Object.keys(componentsJson).forEach(function (componentId) {
        var componentFromJson = componentsJson[componentId];
        var idHasScope = function idHasScope() {
          if (componentFromJson.origin !== (_constants || _load_constants()).COMPONENT_ORIGINS.AUTHORED) return true;
          if ('exported' in componentFromJson) {
            return componentFromJson.exported;
          }
          // backward compatibility
          return (_bitId || _load_bitId()).BitId.parseObsolete(componentId).hasScope();
        };
        componentFromJson.id = (_bitId || _load_bitId()).BitId.parse(componentId, idHasScope());
        var componentMap = (_componentMap || _load_componentMap()).default.fromJson(componentsJson[componentId]);
        componentMap.setMarkAsChangedCb(_this2.markAsChangedBinded);
        _this2.components[componentId] = componentMap;
      });
    }
  }, {
    key: 'getAllComponents',
    value: function getAllComponents(origin) {
      if (!origin) return this.components;
      var isOriginMatch = function isOriginMatch(component) {
        return component.origin === origin;
      };
      // $FlowFixMe we know origin is an array in that case
      var isOriginMatchArray = function isOriginMatchArray(component) {
        return origin.includes(component.origin);
      };
      var filter = Array.isArray(origin) ? isOriginMatchArray : isOriginMatch;
      return (_ramda || _load_ramda()).default.filter(filter, this.components);
    }

    /**
     * We should ignore ejected config files and dirs
     * Files might be on the root dir then we need to ignore them directly by taking them from the bit.json
     * They might be in internal dirs then we need to ignore the dir completely
     */

  }, {
    key: 'getConfigDirsAndFilesToIgnore',
    value: function () {
      var _ref = (0, (_bluebird || _load_bluebird()).coroutine)( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee2(consumerPath, consumerConfig) {
        var _this3 = this;

        var ignoreList, populateIgnoreListP;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee2$(_context2) {
          while (1) {
            switch (_context2.prev = _context2.next) {
              case 0:
                ignoreList = {
                  files: [],
                  dirs: []
                };
                populateIgnoreListP = (_ramda || _load_ramda()).default.values(this.components).map(function () {
                  var _ref2 = (0, (_bluebird || _load_bluebird()).coroutine)( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee(component) {
                    var configDir, componentDir, resolvedBaseConfigDir, fullConfigDir, componentPkgJsonDir, componentConfig, compilerObj, compilerFilesObj, testerObj, testerFilesObj, compilerFiles, testerFiles, toIgnore;
                    return (_regenerator || _load_regenerator()).default.wrap(function _callee$(_context) {
                      while (1) {
                        switch (_context.prev = _context.next) {
                          case 0:
                            configDir = component.configDir;
                            componentDir = component.getComponentDir();

                            if (!(configDir && componentDir)) {
                              _context.next = 18;
                              break;
                            }

                            resolvedBaseConfigDir = component.getBaseConfigDir() || '';
                            fullConfigDir = (_path || _load_path()).default.join(consumerPath, resolvedBaseConfigDir);
                            componentPkgJsonDir = component.rootDir ? (_path || _load_path()).default.join(consumerPath, component.rootDir) : null;
                            _context.next = 8;
                            return (_config || _load_config()).default.load(componentPkgJsonDir, fullConfigDir, consumerConfig);

                          case 8:
                            componentConfig = _context.sent;
                            compilerObj = (_ramda || _load_ramda()).default.values(componentConfig.compiler)[0];
                            compilerFilesObj = compilerObj && compilerObj.files ? compilerObj.files : undefined;
                            testerObj = (_ramda || _load_ramda()).default.values(componentConfig.tester)[0];
                            testerFilesObj = testerObj && testerObj.files ? testerObj.files : undefined;
                            compilerFiles = compilerFilesObj ? (_ramda || _load_ramda()).default.values(compilerFilesObj) : [];
                            testerFiles = testerFilesObj ? (_ramda || _load_ramda()).default.values(testerFilesObj) : [];
                            // R.values above might return array of something which is not string
                            // Which will not be ok with the input of resolveIgnoreFilesAndDirs

                            toIgnore = BitMap.resolveIgnoreFilesAndDirs(configDir.linuxDirPath, componentDir,
                            // $FlowFixMe - see comment above
                            compilerFiles,
                            // $FlowFixMe - see comment above
                            testerFiles);

                            ignoreList.files = ignoreList.files.concat(toIgnore.files);
                            ignoreList.dirs = ignoreList.dirs.concat(toIgnore.dirs);

                          case 18:
                          case 'end':
                            return _context.stop();
                        }
                      }
                    }, _callee, _this3);
                  }));

                  return function (_x3) {
                    return _ref2.apply(this, arguments);
                  };
                }());
                _context2.next = 4;
                return Promise.all(populateIgnoreListP);

              case 4:
                return _context2.abrupt('return', ignoreList);

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

      function getConfigDirsAndFilesToIgnore(_x, _x2) {
        return _ref.apply(this, arguments);
      }

      return getConfigDirsAndFilesToIgnore;
    }()
  }, {
    key: 'getAllBitIds',
    value: function getAllBitIds(origin) {
      var _this4 = this;

      var ids = function ids(componentMaps) {
        return (_bitId || _load_bitId()).BitIds.fromArray(componentMaps.map(function (c) {
          return c.id;
        }));
      };
      var getIdsOfOrigin = function getIdsOfOrigin(oneOrigin) {
        var cacheKey = oneOrigin || 'all';
        if (_this4._cacheIds[cacheKey]) return _this4._cacheIds[cacheKey];
        var allComponents = (_ramda || _load_ramda()).default.values(_this4.components);
        var components = oneOrigin ? allComponents.filter(function (c) {
          return c.origin === oneOrigin;
        }) : allComponents;
        var componentIds = ids(components);
        _this4._cacheIds[cacheKey] = componentIds;
        return componentIds;
      };

      if (!origin) return getIdsOfOrigin();
      return (_bitId || _load_bitId()).BitIds.fromArray((_ramda || _load_ramda()).default.flatten(origin.map(function (oneOrigin) {
        return getIdsOfOrigin(oneOrigin);
      })));
    }

    /**
     * get existing bitmap bit-id by bit-id.
     * throw an exception if not found
     * @see also getBitIdIfExist
     */

  }, {
    key: 'getBitId',
    value: function getBitId(bitId) {
      var _ref3 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
          _ref3$ignoreVersion = _ref3.ignoreVersion,
          ignoreVersion = _ref3$ignoreVersion === undefined ? false : _ref3$ignoreVersion,
          _ref3$ignoreScopeAndV = _ref3.ignoreScopeAndVersion,
          ignoreScopeAndVersion = _ref3$ignoreScopeAndV === undefined ? false : _ref3$ignoreScopeAndV;

      if (!(bitId instanceof (_bitId || _load_bitId()).BitId)) {
        throw new TypeError(`BitMap.getBitId expects bitId to be an instance of BitId, instead, got ${bitId}`);
      }
      var allIds = this.getAllBitIds();
      var exactMatch = allIds.search(bitId);
      if (exactMatch) return exactMatch;
      if (ignoreVersion) {
        var matchWithoutVersion = allIds.searchWithoutVersion(bitId);
        if (matchWithoutVersion) return matchWithoutVersion;
      }
      if (ignoreScopeAndVersion) {
        var matchWithoutScopeAndVersion = allIds.searchWithoutScopeAndVersion(bitId);
        if (matchWithoutScopeAndVersion) return matchWithoutScopeAndVersion;
      }
      throw new (_exceptions || _load_exceptions()).MissingBitMapComponent(bitId.toString());
    }

    /**
     * get existing bitmap bit-id by bit-id
     * don't throw an exception if not found
     * @see also getBitId
     */

  }, {
    key: 'getBitIdIfExist',
    value: function getBitIdIfExist(bitId) {
      var _ref4 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
          _ref4$ignoreVersion = _ref4.ignoreVersion,
          ignoreVersion = _ref4$ignoreVersion === undefined ? false : _ref4$ignoreVersion,
          _ref4$ignoreScopeAndV = _ref4.ignoreScopeAndVersion,
          ignoreScopeAndVersion = _ref4$ignoreScopeAndV === undefined ? false : _ref4$ignoreScopeAndV;

      try {
        var existingBitId = this.getBitId(bitId, { ignoreVersion, ignoreScopeAndVersion });
        return existingBitId;
      } catch (err) {
        if (err instanceof (_exceptions || _load_exceptions()).MissingBitMapComponent) return null;
        throw err;
      }
    }

    /**
     * get componentMap from bitmap by bit-id.
     * throw an exception if not found.
     * @see also getComponentIfExist
     */

  }, {
    key: 'getComponent',
    value: function getComponent(bitId) {
      var _ref5 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
          _ref5$ignoreVersion = _ref5.ignoreVersion,
          ignoreVersion = _ref5$ignoreVersion === undefined ? false : _ref5$ignoreVersion,
          _ref5$ignoreScopeAndV = _ref5.ignoreScopeAndVersion,
          ignoreScopeAndVersion = _ref5$ignoreScopeAndV === undefined ? false : _ref5$ignoreScopeAndV;

      var existingBitId = this.getBitId(bitId, { ignoreVersion, ignoreScopeAndVersion });
      return this.components[existingBitId.toString()];
    }

    /**
     * get componentMap from bitmap by bit-id
     * don't throw an exception if not found
     * @see also getComponent
     */

  }, {
    key: 'getComponentIfExist',
    value: function getComponentIfExist(bitId) {
      var _ref6 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
          _ref6$ignoreVersion = _ref6.ignoreVersion,
          ignoreVersion = _ref6$ignoreVersion === undefined ? false : _ref6$ignoreVersion,
          _ref6$ignoreScopeAndV = _ref6.ignoreScopeAndVersion,
          ignoreScopeAndVersion = _ref6$ignoreScopeAndV === undefined ? false : _ref6$ignoreScopeAndV;

      try {
        var componentMap = this.getComponent(bitId, { ignoreVersion, ignoreScopeAndVersion });
        return componentMap;
      } catch (err) {
        if (err instanceof (_exceptions || _load_exceptions()).MissingBitMapComponent) return null;
        throw err;
      }
    }
  }, {
    key: 'getNonNestedComponentIfExist',
    value: function getNonNestedComponentIfExist(bitId) {
      var nonNestedIds = this.getAllBitIds([(_constants || _load_constants()).COMPONENT_ORIGINS.IMPORTED, (_constants || _load_constants()).COMPONENT_ORIGINS.AUTHORED]);
      var id = nonNestedIds.searchWithoutScopeAndVersion(bitId);
      if (!id) return null;
      return this.getComponent(id);
    }
  }, {
    key: 'getComponentPreferNonNested',
    value: function getComponentPreferNonNested(bitId) {
      return this.getNonNestedComponentIfExist(bitId) || this.getComponentIfExist(bitId, { ignoreVersion: true });
    }
  }, {
    key: 'getAuthoredAndImportedBitIds',
    value: function getAuthoredAndImportedBitIds() {
      return this.getAllBitIds([(_constants || _load_constants()).COMPONENT_ORIGINS.AUTHORED, (_constants || _load_constants()).COMPONENT_ORIGINS.IMPORTED]);
    }
  }, {
    key: 'getAuthoredExportedComponents',
    value: function getAuthoredExportedComponents() {
      var authoredIds = this.getAllBitIds([(_constants || _load_constants()).COMPONENT_ORIGINS.AUTHORED]);
      return authoredIds.filter(function (id) {
        return id.hasScope();
      });
    }
  }, {
    key: 'validateConfigDir',
    value: function validateConfigDir(compId, configDir) {
      var components = this.getAllComponents();
      if (configDir.startsWith('./')) {
        configDir = configDir.replace('./', '');
      }
      var comps = (_ramda || _load_ramda()).default.pickBy(function (component) {
        var compDir = component.getComponentDir();
        if (compDir && (0, (_utils || _load_utils()).pathIsInside)(configDir, compDir)) {
          return true;
        }
        var compConfigDir = component.configDir && component.configDir instanceof (_configDir || _load_configDir()).default ? component.configDir.getResolved({ componentDir: compDir || '' }).getEnvTypeCleaned().linuxDirPath : null;
        if (compConfigDir && (0, (_utils || _load_utils()).pathIsInside)(configDir, compConfigDir)) {
          return true;
        }
        return false;
      }, components);
      if (!(_ramda || _load_ramda()).default.isEmpty(comps)) {
        var _id = (_ramda || _load_ramda()).default.keys(comps)[0];
        var stringId = (_bitId || _load_bitId()).BitId.parse(_id).toStringWithoutVersion();
        if (compId !== stringId) {
          throw new (_invalidConfigDir || _load_invalidConfigDir()).default(stringId);
        }
      }
      return true;
    }
  }, {
    key: '_makePathRelativeToProjectRoot',
    value: function _makePathRelativeToProjectRoot(pathToChange) {
      var absolutePath = (_path || _load_path()).default.resolve(pathToChange);
      return (_path || _load_path()).default.relative(this.projectRoot, absolutePath);
    }
  }, {
    key: '_searchMainFile',
    value: function _searchMainFile(baseMainFile, files, rootDir) {
      // search for an exact relative-path
      var mainFileFromFiles = files.find(function (file) {
        return file.relativePath === baseMainFile;
      });
      if (mainFileFromFiles) return baseMainFile;
      if (rootDir) {
        var mainFileUsingRootDir = files.find(function (file) {
          return (0, (_utils || _load_utils()).pathJoinLinux)(rootDir, file.relativePath) === baseMainFile;
        });
        if (mainFileUsingRootDir) return mainFileUsingRootDir.relativePath;
      }
      // search for a file-name
      var potentialMainFiles = files.filter(function (file) {
        return file.name === baseMainFile;
      });
      if (!potentialMainFiles.length) return null;
      // when there are several files that met the criteria, choose the closer to the root
      var sortByNumOfDirs = function sortByNumOfDirs(a, b) {
        return a.relativePath.split((_constants || _load_constants()).DEFAULT_SEPARATOR).length - b.relativePath.split((_constants || _load_constants()).DEFAULT_SEPARATOR).length;
      };
      potentialMainFiles.sort(sortByNumOfDirs);
      mainFileFromFiles = (_ramda || _load_ramda()).default.head(potentialMainFiles);
      return mainFileFromFiles.relativePath;
    }
  }, {
    key: '_getMainFile',
    value: function _getMainFile(mainFile, componentIdStr) {
      var _this5 = this;

      var componentMap = this.components[componentIdStr];
      var files = componentMap.files.filter(function (file) {
        return !file.test;
      });
      // scenario 1) user entered mainFile => search the mainFile in the files array
      if (mainFile) {
        var foundMainFile = this._searchMainFile(mainFile, files, componentMap.rootDir);
        if (foundMainFile) return foundMainFile;
        throw new (_exceptions || _load_exceptions()).MissingMainFile(componentIdStr, mainFile, files.map(function (file) {
          return (_path || _load_path()).default.normalize(file.relativePath);
        }));
      }
      // scenario 2) user didn't enter mainFile and the component has only one file => use that file as the main file.
      if (files.length === 1) return files[0].relativePath;
      // scenario 3) user didn't enter mainFile and the component has multiple files => search for default main files (such as index.js)
      var searchResult = void 0;
      (_constants || _load_constants()).DEFAULT_INDEX_EXTS.forEach(function (ext) {
        // TODO: can be improved - stop loop if finding main file
        if (!searchResult) {
          var mainFileNameToSearch = `${(_constants || _load_constants()).DEFAULT_INDEX_NAME}.${ext}`;
          searchResult = _this5._searchMainFile(mainFileNameToSearch, files, componentMap.rootDir);
        }
      });
      if (searchResult) return searchResult;
      var mainFileString = `${(_constants || _load_constants()).DEFAULT_INDEX_NAME}.[${(_constants || _load_constants()).DEFAULT_INDEX_EXTS.join(', ')}]`;
      throw new (_exceptions || _load_exceptions()).MissingMainFile(componentIdStr, mainFileString, files.map(function (file) {
        return (_path || _load_path()).default.normalize(file.relativePath);
      }));
    }

    /**
     * find ids that have the same name but different version
     * if compareWithoutScope is false, the scope should be identical in addition to the name
     */

  }, {
    key: 'findSimilarIds',
    value: function findSimilarIds(id) {
      var compareWithoutScope = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;

      var allIds = this.getAllBitIds([(_constants || _load_constants()).COMPONENT_ORIGINS.IMPORTED, (_constants || _load_constants()).COMPONENT_ORIGINS.AUTHORED]);
      var similarIds = allIds.filter(function (existingId) {
        var isSimilar = compareWithoutScope ? existingId.isEqualWithoutScopeAndVersion(id) : existingId.isEqualWithoutVersion(id);
        return isSimilar && !existingId.isEqual(id);
      });
      return (_bitId || _load_bitId()).BitIds.fromArray(similarIds);
    }
  }, {
    key: 'deleteOlderVersionsOfComponent',
    value: function deleteOlderVersionsOfComponent(componentId) {
      var _this6 = this;

      var similarIds = this.findSimilarIds(componentId);
      similarIds.forEach(function (id) {
        var idStr = id.toString();
        (_logger || _load_logger()).default.debugAndAddBreadCrumb('BitMap.deleteOlderVersionsOfComponent', 'deleting an older version {idStr} of an existing component {componentId}', { idStr, componentId: componentId.toString() });
        _this6._removeFromComponentsArray(id);
      });
    }

    /**
     * --- Don't use this function when you have the ID parsed. Use this.getBitId() instead ---
     *
     * id entered by the user may or may not include scope-name
     * search for a similar id in the bitmap and return the full BitId
     */

  }, {
    key: 'getExistingBitId',
    value: function getExistingBitId(id) {
      var shouldThrow = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;

      if (!(_ramda || _load_ramda()).default.is(String, id)) {
        throw new TypeError(`BitMap.getExistingBitId expects id to be a string, instead, got ${typeof id}`);
      }
      var components = (_ramda || _load_ramda()).default.values(this.components);
      var idHasVersion = id.includes((_constants || _load_constants()).VERSION_DELIMITER);

      // start with a more strict comparison. assume the id from the user has a scope name
      var componentWithScope = components.find(function (componentMap) {
        return idHasVersion ? componentMap.id.toString() === id : componentMap.id.toStringWithoutVersion() === id;
      });
      if (componentWithScope) return componentWithScope.id;

      // continue with searching without the scope name
      var idWithoutVersion = (_bitId || _load_bitId()).BitId.getStringWithoutVersion(id);
      var componentWithoutScope = components.find(function (componentMap) {
        return idHasVersion ? componentMap.id.toStringWithoutScope() === id : componentMap.id.toStringWithoutScopeAndVersion() === idWithoutVersion;
      });
      if (componentWithoutScope) return componentWithoutScope.id;
      if (shouldThrow) {
        throw new (_exceptions || _load_exceptions()).MissingBitMapComponent(id);
      }
      return null;
    }

    /**
     * check if both arrays are equal according to their 'relativePath', regardless the order
     */

  }, {
    key: '_areFilesArraysEqual',
    value: function _areFilesArraysEqual(filesA, filesB) {
      if (filesA.length !== filesB.length) return false;
      var cmp = function cmp(x, y) {
        return x.relativePath === y.relativePath;
      };
      var diff = (_ramda || _load_ramda()).default.differenceWith(cmp, filesA, filesB);
      if (!diff.length) return true;
      return false;
    }

    /**
     * add files from filesB that are not in filesA
     */

  }, {
    key: 'mergeFilesArray',
    value: function mergeFilesArray(filesA, filesB) {
      return (_ramda || _load_ramda()).default.unionWith((_ramda || _load_ramda()).default.eqBy((_ramda || _load_ramda()).default.prop('relativePath')), filesA, filesB);
    }

    /**
     * if an existing file is for example uppercase and the new file is lowercase it has different
     * behavior according to the OS. some OS are case sensitive, some are not.
     * it's safer to avoid saving both files and instead, replacing the old file with the new one.
     * in case a file has replaced and it is also a mainFile, replace the mainFile as well
     */

  }, {
    key: '_updateFilesWithCurrentLetterCases',
    value: function _updateFilesWithCurrentLetterCases(componentId, newFiles) {
      var currentComponentMap = this.components[componentId];
      var currentFiles = currentComponentMap.files;
      currentFiles.forEach(function (currentFile) {
        var sameFile = newFiles.find(function (newFile) {
          return newFile.relativePath.toLowerCase() === currentFile.relativePath.toLowerCase();
        });
        if (sameFile && currentFile.relativePath !== sameFile.relativePath) {
          if (currentComponentMap.mainFile === currentFile.relativePath) {
            currentComponentMap.mainFile = sameFile.relativePath;
          }
          currentFile.relativePath = sameFile.relativePath;
        }
      });
    }
  }, {
    key: 'addComponent',
    value: function addComponent(_ref7) {
      var componentId = _ref7.componentId,
          files = _ref7.files,
          mainFile = _ref7.mainFile,
          origin = _ref7.origin,
          rootDir = _ref7.rootDir,
          configDir = _ref7.configDir,
          trackDir = _ref7.trackDir,
          override = _ref7.override,
          originallySharedDir = _ref7.originallySharedDir,
          wrapDir = _ref7.wrapDir;

      var componentIdStr = componentId.toString();
      (_logger || _load_logger()).default.debug(`adding to bit.map ${componentIdStr}`);
      if (this.components[componentIdStr]) {
        (_logger || _load_logger()).default.info(`bit.map: updating an exiting component ${componentIdStr}`);
        var existingRootDir = this.components[componentIdStr].rootDir;
        if (existingRootDir) (_componentMap || _load_componentMap()).default.changeFilesPathAccordingToItsRootDir(existingRootDir, files);
        if (override) {
          this.components[componentIdStr].files = files;
        } else {
          this._updateFilesWithCurrentLetterCases(componentIdStr, files);
          // override the current componentMap.files with the given files argument
          this.components[componentIdStr].files = (_ramda || _load_ramda()).default.unionWith((_ramda || _load_ramda()).default.eqBy((_ramda || _load_ramda()).default.prop('relativePath')), files, this.components[componentIdStr].files);
        }
        if (mainFile) {
          this.components[componentIdStr].mainFile = this._getMainFile((0, (_utils || _load_utils()).pathNormalizeToLinux)(mainFile), componentIdStr);
        }
      } else {
        if (origin === (_constants || _load_constants()).COMPONENT_ORIGINS.IMPORTED || origin === (_constants || _load_constants()).COMPONENT_ORIGINS.AUTHORED) {
          // if there are older versions, the user is updating an existing component, delete old ones from bit.map
          this.deleteOlderVersionsOfComponent(componentId);
        }
        // $FlowFixMe not easy to fix, we can't instantiate ComponentMap with mainFile because we don't have it yet
        var componentMap = new (_componentMap || _load_componentMap()).default({ files, origin });
        componentMap.setMarkAsChangedCb(this.markAsChangedBinded);
        this.setComponent(componentId, componentMap);
        this.components[componentIdStr].mainFile = this._getMainFile((0, (_utils || _load_utils()).pathNormalizeToLinux)(mainFile), componentIdStr);
      }
      if (rootDir) {
        this.components[componentIdStr].rootDir = (0, (_utils || _load_utils()).pathNormalizeToLinux)(rootDir);
      }
      if (configDir) {
        this.components[componentIdStr].configDir = configDir;
      }
      if (trackDir) {
        this.components[componentIdStr].trackDir = (0, (_utils || _load_utils()).pathNormalizeToLinux)(trackDir);
      }
      if (wrapDir) {
        this.components[componentIdStr].wrapDir = wrapDir;
      }
      this.components[componentIdStr].removeTrackDirIfNeeded();
      if (originallySharedDir) {
        this.components[componentIdStr].originallySharedDir = originallySharedDir;
      }
      this.components[componentIdStr].sort();
      this.components[componentIdStr].validate();
      this.markAsChanged();
      return this.components[componentIdStr];
    }
  }, {
    key: 'addFilesToComponent',
    value: function addFilesToComponent(_ref8) {
      var componentId = _ref8.componentId,
          files = _ref8.files;

      var componentIdStr = componentId.toString();
      if (!this.components[componentIdStr]) {
        throw new (_generalError || _load_generalError()).default(`unable to add files to a non-exist component ${componentIdStr}`);
      }
      var existingRootDir = this.components[componentIdStr].rootDir;
      if (existingRootDir) (_componentMap || _load_componentMap()).default.changeFilesPathAccordingToItsRootDir(existingRootDir, files);
      if (this._areFilesArraysEqual(this.components[componentIdStr].files, files)) {
        return this.components[componentIdStr];
      }
      // do not override existing files, only add new files
      (_logger || _load_logger()).default.info(`bit.map: updating an exiting component ${componentIdStr}`);
      this.components[componentIdStr].files = (_ramda || _load_ramda()).default.unionWith((_ramda || _load_ramda()).default.eqBy((_ramda || _load_ramda()).default.prop('relativePath')), this.components[componentIdStr].files, files);
      this.components[componentIdStr].sort();
      this.components[componentIdStr].validate();
      this.markAsChanged();
      return this.components[componentIdStr];
    }
  }, {
    key: '_removeFromComponentsArray',
    value: function _removeFromComponentsArray(componentId) {
      delete this.components[componentId.toString()];
      this.markAsChanged();
    }
  }, {
    key: 'removeComponent',
    value: function removeComponent(bitId) {
      var bitmapComponent = this.getBitIdIfExist(bitId, { ignoreScopeAndVersion: true });
      if (bitmapComponent) this._removeFromComponentsArray(bitmapComponent);
      return bitmapComponent;
    }
  }, {
    key: 'removeComponents',
    value: function removeComponents(ids) {
      var _this7 = this;

      return ids.map(function (id) {
        return _this7.removeComponent(id);
      });
    }
  }, {
    key: 'addMainDistFileToComponent',
    value: function addMainDistFileToComponent(id, distFilesPaths) {
      var _this8 = this;

      if (!this.components[id]) {
        (_logger || _load_logger()).default.warn(`unable to find the component ${id} in bit.map file`);
        return;
      }
      var distFilesPathsNormalized = distFilesPaths.map(function (filePath) {
        return (0, (_utils || _load_utils()).pathNormalizeToLinux)(filePath);
      });

      var mainDistFile = distFilesPathsNormalized.find(function (distFile) {
        return distFile.endsWith(_this8.components[id].mainFile);
      });
      if (!mainDistFile) {
        (_logger || _load_logger()).default.warn(`unable to find the main dist file of component ${id}. Dist files: ${distFilesPathsNormalized.join(', ')}`);
        return;
      }
      this.components[id].mainDistFile = this._makePathRelativeToProjectRoot(mainDistFile);
      this.markAsChanged();
    }
  }, {
    key: 'isExistWithSameVersion',
    value: function isExistWithSameVersion(id) {
      return id.hasVersion() && this.components[id.toString()];
    }

    /**
     * needed after exporting or tagging a component.
     * We don't support export/tag of nested components, only authored or imported. For authored/imported components, could be
     * in the file-system only one instance with the same component-name. As a result, we can strip the
     * scope-name and the version, find the older version in bit.map and update the id with the new one.
     */

  }, {
    key: 'updateComponentId',
    value: function updateComponentId(id) {
      var updateScopeOnly = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;

      var newIdString = id.toString();
      var similarIds = this.findSimilarIds(id, true);
      if (!similarIds.length) {
        (_logger || _load_logger()).default.debug(`bit-map: no need to update ${newIdString}`);
        return id;
      }
      if (similarIds.length > 1) {
        throw new (_generalError || _load_generalError()).default(`Your ${(_constants || _load_constants()).BIT_MAP} file has more than one version of ${id.toStringWithoutScopeAndVersion()} and they
      are authored or imported. This scenario is not supported`);
      }
      var oldId = similarIds[0];
      var oldIdStr = oldId.toString();
      var newId = updateScopeOnly ? oldId.changeScope(id.scope) : id;
      if (newId.isEqual(oldId)) {
        (_logger || _load_logger()).default.debug(`bit-map: no need to update ${oldIdStr}`);
        return oldId;
      }
      (_logger || _load_logger()).default.debug(`BitMap: updating an older component ${oldIdStr} with a newer component ${newId.toString()}`);
      var componentMap = this.components[oldIdStr];
      if (componentMap.origin === (_constants || _load_constants()).COMPONENT_ORIGINS.NESTED) {
        throw new Error('updateComponentId should not manipulate Nested components');
      }
      this._removeFromComponentsArray(oldId);
      this.setComponent(newId, componentMap);
      this.markAsChanged();
      return newId;
    }

    /**
     * Return a potential componentMap if file is supposed to be part of it
     * by a path exist in the files object
     *
     * @param {string} componentPath relative to consumer - as stored in bit.map files object
     * @returns {ComponentMap} componentMap
     */

  }, {
    key: 'getComponentObjectOfFileByPath',
    value: function getComponentObjectOfFileByPath(componentPath) {
      var _this9 = this;

      var components = this.getAllComponents();
      return (_ramda || _load_ramda()).default.pickBy(function (component) {
        return (0, (_utils || _load_utils()).pathIsInside)(componentPath, component.rootDir || _this9.projectRoot);
      }, components);
    }

    /**
     * Return a component id as listed in bit.map file
     * by a path exist in the files object
     *
     * @param {string} componentPath relative to consumer - as stored in bit.map files object
     * @returns {BitId} component id
     * @memberof BitMap
     */

  }, {
    key: 'getComponentIdByPath',
    value: function getComponentIdByPath(componentPath) {
      var caseSensitive = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;

      this._populateAllPaths();
      return caseSensitive ? this.paths[componentPath] : this.pathsLowerCase[componentPath.toLowerCase()];
    }
  }, {
    key: '_populateAllPaths',
    value: function _populateAllPaths() {
      var _this10 = this;

      if ((_ramda || _load_ramda()).default.isEmpty(this.paths)) {
        Object.keys(this.components).forEach(function (componentId) {
          var component = _this10.components[componentId];
          component.files.forEach(function (file) {
            var relativeToConsumer = component.rootDir ? (0, (_utils || _load_utils()).pathJoinLinux)(component.rootDir, file.relativePath) : file.relativePath;
            _this10.paths[relativeToConsumer] = component.id;
            _this10.pathsLowerCase[relativeToConsumer.toLowerCase()] = component.id;
          });
        });
      }
    }
  }, {
    key: 'getAllTrackDirs',
    value: function getAllTrackDirs() {
      var _this11 = this;

      if (!this.allTrackDirs) {
        this.allTrackDirs = {};
        Object.keys(this.components).forEach(function (componentId) {
          var component = _this11.components[componentId];
          if (!component.trackDir) return;
          // $FlowFixMe
          _this11.allTrackDirs[component.trackDir] = component.id;
        });
      }
      return this.allTrackDirs;
    }
  }, {
    key: 'updatePathLocation',
    value: function updatePathLocation(from, to, existingPath) {
      var _this12 = this;

      var isPathDir = (0, (_utils || _load_utils()).isDir)(existingPath);
      var allChanges = [];
      Object.keys(this.components).forEach(function (componentId) {
        var componentMap = _this12.components[componentId];
        var changes = isPathDir ? componentMap.updateDirLocation(from, to) : componentMap.updateFileLocation(from, to);
        if (changes && changes.length) allChanges.push({ id: componentMap.id.clone(), changes });
      });
      if ((_ramda || _load_ramda()).default.isEmpty(allChanges)) {
        var errorMsg = isPathDir ? `directory ${from} is not a tracked component` : `the file ${existingPath} is untracked`;
        throw new (_generalError || _load_generalError()).default(errorMsg);
      }

      this.markAsChanged();
      return allChanges;
    }

    /**
     * remove the id property before saving the components to the file as they are redundant with the keys
     */

  }, {
    key: 'toObjects',
    value: function toObjects() {
      var _this13 = this;

      var components = {};
      Object.keys(this.components).forEach(function (id) {
        var componentMap = _this13.components[id].clone();
        if (componentMap.origin === (_constants || _load_constants()).COMPONENT_ORIGINS.AUTHORED) {
          componentMap.exported = componentMap.id.hasScope();
        }
        delete componentMap.id;
        components[id] = componentMap.toPlainObject();
      });

      return (0, (_utils || _load_utils()).sortObject)(components);
    }

    /**
     * do not call this function directly, let consumer.onDestroy() call it.
     * consumer.onDestroy() is being called (manually) at the end of the command process.
     * the risk of calling this method in other places is a parallel writing of this file, which
     * may result in a damaged file
     */

  }, {
    key: 'write',
    value: function () {
      var _ref9 = (0, (_bluebird || _load_bluebird()).coroutine)( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee3() {
        var bitMapContent;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee3$(_context3) {
          while (1) {
            switch (_context3.prev = _context3.next) {
              case 0:
                if (this.hasChanged) {
                  _context3.next = 2;
                  break;
                }

                return _context3.abrupt('return', null);

              case 2:
                (_logger || _load_logger()).default.debug('writing to bit.map');
                bitMapContent = this.getContent();
                return _context3.abrupt('return', (0, (_utils || _load_utils()).outputFile)({ filePath: this.mapPath, content: JSON.stringify(bitMapContent, null, 4) }));

              case 5:
              case 'end':
                return _context3.stop();
            }
          }
        }, _callee3, this);
      }));

      function write() {
        return _ref9.apply(this, arguments);
      }

      return write;
    }()
  }, {
    key: 'getContent',
    value: function getContent() {
      var bitMapContent = Object.assign({}, this.toObjects(), { version: this.version });
      return bitMapContent;
    }
  }], [{
    key: 'load',
    value: function load(dirPath) {
      var _BitMap$getBitMapLoca = BitMap.getBitMapLocation(dirPath),
          currentLocation = _BitMap$getBitMapLoca.currentLocation,
          defaultLocation = _BitMap$getBitMapLoca.defaultLocation;

      var mapFileContent = BitMap.loadRawSync(dirPath);
      if (!mapFileContent || !currentLocation) {
        return new BitMap(dirPath, defaultLocation, (_constants || _load_constants()).BIT_VERSION);
      }
      var componentsJson = void 0;
      try {
        componentsJson = (_commentJson || _load_commentJson()).default.parse(mapFileContent.toString('utf8'), null, true);
      } catch (e) {
        (_logger || _load_logger()).default.error(e);
        throw new (_exceptions || _load_exceptions()).InvalidBitMap(currentLocation, e.message);
      }
      var version = componentsJson.version;
      // Don't treat version like component
      delete componentsJson.version;

      var bitMap = new BitMap(dirPath, currentLocation, version);
      bitMap.loadComponents(componentsJson);
      return bitMap;
    }
  }, {
    key: 'loadRawSync',
    value: function loadRawSync(dirPath) {
      var _BitMap$getBitMapLoca2 = BitMap.getBitMapLocation(dirPath),
          currentLocation = _BitMap$getBitMapLoca2.currentLocation;

      if (!currentLocation) {
        (_logger || _load_logger()).default.info(`bit.map: unable to find an existing ${(_constants || _load_constants()).BIT_MAP} file. Will create a new one if needed`);
        return undefined;
      }
      var mapFileContent = (_fsExtra || _load_fsExtra()).default.readFileSync(currentLocation);
      return mapFileContent;
    }
  }, {
    key: 'getBitMapLocation',
    value: function getBitMapLocation(dirPath) {
      var defaultLocation = (_path || _load_path()).default.join(dirPath, (_constants || _load_constants()).BIT_MAP);
      var oldLocation = (_path || _load_path()).default.join(dirPath, (_constants || _load_constants()).OLD_BIT_MAP);
      var getCurrentLocation = function getCurrentLocation() {
        if ((_fsExtra || _load_fsExtra()).default.existsSync(defaultLocation)) return defaultLocation;
        if ((_fsExtra || _load_fsExtra()).default.existsSync(oldLocation)) return oldLocation;
        return null;
      };
      var currentLocation = getCurrentLocation();
      return { currentLocation, defaultLocation };
    }

    /**
     * if resetHard, delete the bitMap file.
     * Otherwise, try to load it and only if the file is corrupted then delete it.
     */

  }, {
    key: 'reset',
    value: function reset(dirPath, resetHard) {
      var bitMapPath = (_path || _load_path()).default.join(dirPath, (_constants || _load_constants()).BIT_MAP);
      var deleteBitMapFile = function deleteBitMapFile() {
        (_logger || _load_logger()).default.info(`deleting the bitMap file at ${bitMapPath}`);
        (_fsExtra || _load_fsExtra()).default.removeSync(bitMapPath);
      };
      if (resetHard) {
        deleteBitMapFile();
        return;
      }
      try {
        BitMap.load(dirPath);
      } catch (err) {
        if (err instanceof (_exceptions || _load_exceptions()).InvalidBitMap) {
          deleteBitMapFile();
          return;
        }
        throw err;
      }
    }

    /**
     * Return files and dirs which need to be ignored since they are config files / dirs
     * @param {*} configDir
     * @param {*} rootDir
     * @param {*} compilerFilesPaths
     * @param {*} testerFilesPaths
     */

  }, {
    key: 'resolveIgnoreFilesAndDirs',
    value: function resolveIgnoreFilesAndDirs(configDir, rootDir) {
      var compilerFilesPaths = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : [];
      var testerFilesPaths = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : [];

      var ignoreList = {
        files: [],
        dirs: []
      };
      if (!configDir) return ignoreList;
      if (configDir.startsWith(`{${(_constants || _load_constants()).COMPONENT_DIR}}`)) {
        var resolvedConfigDir = (0, (_stringFormat || _load_stringFormat()).default)(configDir, { [(_constants || _load_constants()).COMPONENT_DIR]: rootDir, ENV_TYPE: '' });
        var allEnvFilesPaths = compilerFilesPaths.concat(testerFilesPaths);
        allEnvFilesPaths.forEach(function (file) {
          var ignoreFile = (0, (_utils || _load_utils()).pathJoinLinux)(resolvedConfigDir, file);
          ignoreList.files.push(ignoreFile);
        });
        var configDirWithoutCompDir = (0, (_stringFormat || _load_stringFormat()).default)(configDir, { [(_constants || _load_constants()).COMPONENT_DIR]: '', ENV_TYPE: '{ENV_TYPE}' });
        // There is nested folders to ignore
        if (configDirWithoutCompDir !== '' && configDirWithoutCompDir !== '/') {
          var configDirWithoutCompAndEnvsDir = (0, (_stringFormat || _load_stringFormat()).default)(configDir, { [(_constants || _load_constants()).COMPONENT_DIR]: '', ENV_TYPE: '' });
          // There is nested folder which is not the env folders - ignore it completely
          if (configDirWithoutCompAndEnvsDir !== '' && configDirWithoutCompAndEnvsDir !== '/') {
            var resolvedDirWithoutEnvType = (0, (_stringFormat || _load_stringFormat()).default)(configDir, { [(_constants || _load_constants()).COMPONENT_DIR]: rootDir, ENV_TYPE: '' });
            ignoreList.dirs.push((0, (_utils || _load_utils()).stripTrailingChar)(resolvedDirWithoutEnvType, '/'));
          } else {
            var resolvedCompilerConfigDir = (0, (_stringFormat || _load_stringFormat()).default)(configDir, {
              [(_constants || _load_constants()).COMPONENT_DIR]: rootDir,
              ENV_TYPE: (_constants || _load_constants()).COMPILER_ENV_TYPE
            });
            var resolvedTesterConfigDir = (0, (_stringFormat || _load_stringFormat()).default)(configDir, { [(_constants || _load_constants()).COMPONENT_DIR]: rootDir, ENV_TYPE: (_constants || _load_constants()).TESTER_ENV_TYPE });
            ignoreList.dirs.push(resolvedCompilerConfigDir, resolvedTesterConfigDir);
          }
        }
      } else {
        // Ignore the whole dir since this dir is only for config files
        var dirToIgnore = (0, (_stringFormat || _load_stringFormat()).default)(configDir, { ENV_TYPE: '' });
        ignoreList.dirs.push(dirToIgnore);
      }
      return ignoreList;
    }

    /**
     * this is a temporarily method until ConfigDir class is merged into master
     */

  }, {
    key: 'parseConfigDir',
    value: function parseConfigDir(configDir, rootDir) {
      var configDirResolved = {};
      configDirResolved.compiler = configDir.getResolved({
        componentDir: rootDir,
        envType: (_constants || _load_constants()).COMPILER_ENV_TYPE
      }).linuxDirPath;
      configDirResolved.tester = configDir.getResolved({ componentDir: rootDir, envType: (_constants || _load_constants()).TESTER_ENV_TYPE }).linuxDirPath;
      return configDirResolved;
    }
  }]);

  return BitMap;
}();

exports.default = BitMap;