'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 _ramda;

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

var _fsExtra;

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

var _path;

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

var _logger;

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

var _constants;

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

var _utils;

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

var _bitId;

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

var _addComponents;

function _load_addComponents() {
  return _addComponents = _interopRequireDefault(require('../component-ops/add-components'));
}

var _exceptions;

function _load_exceptions() {
  return _exceptions = require('../component-ops/add-components/exceptions');
}

var _generalError;

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

var _validationError;

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

var _componentNotFoundInPath;

function _load_componentNotFoundInPath() {
  return _componentNotFoundInPath = _interopRequireDefault(require('../component/exceptions/component-not-found-in-path'));
}

var _configDir;

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

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 ComponentMap = function () {
  // relevant for authored components only, it helps finding out whether a component has a scope
  // a wrapper directory needed when a user adds a package.json file to the component root so then it won't collide with Bit generated one
  // wether the compiler / tester are detached from the workspace global configuration
  // needed when there is a build process involved
  // relevant for AUTHORED only when a component was added as a directory, used for tracking changes in that dir
  function ComponentMap(_ref) {
    var id = _ref.id,
        files = _ref.files,
        mainFile = _ref.mainFile,
        rootDir = _ref.rootDir,
        trackDir = _ref.trackDir,
        configDir = _ref.configDir,
        origin = _ref.origin,
        dependencies = _ref.dependencies,
        mainDistFile = _ref.mainDistFile,
        originallySharedDir = _ref.originallySharedDir,
        wrapDir = _ref.wrapDir,
        detachedCompiler = _ref.detachedCompiler,
        detachedTester = _ref.detachedTester;

    _classCallCheck(this, ComponentMap);

    var confDir = void 0;
    if (configDir && typeof configDir === 'string') {
      confDir = new (_configDir || _load_configDir()).default(configDir);
    } else if (configDir && configDir instanceof (_configDir || _load_configDir()).default) {
      confDir = configDir.clone();
    }
    this.id = id;
    this.files = files;
    this.mainFile = mainFile;
    this.rootDir = rootDir;
    this.trackDir = trackDir;
    this.configDir = confDir;
    this.origin = origin;
    this.dependencies = dependencies;
    this.mainDistFile = mainDistFile;
    this.originallySharedDir = originallySharedDir;
    this.wrapDir = wrapDir;
    this.detachedCompiler = detachedCompiler;
    this.detachedTester = detachedTester;
  } // directory shared among a component and its dependencies by the original author. Relevant for IMPORTED only
  // needed for the link process
  // always set for IMPORTED and NESTED.
  // reason why trackDir and not re-use rootDir is because using rootDir requires all paths to be
  // relative to rootDir for consistency, then, when saving into the model changing them back to
  // be relative to consumer-root. (we can't save in the model relative to rootDir, otherwise the
  // dependencies paths won't work).


  _createClass(ComponentMap, [{
    key: 'toPlainObject',
    value: function toPlainObject() {
      var res = {
        id: this.id,
        files: this.files,
        mainFile: this.mainFile,
        rootDir: this.rootDir,
        trackDir: this.trackDir,
        configDir: this.configDir ? this.configDir.linuxDirPath : undefined,
        origin: this.origin,
        dependencies: this.dependencies,
        mainDistFile: this.mainDistFile,
        originallySharedDir: this.originallySharedDir,
        wrapDir: this.wrapDir,
        detachedCompiler: this.detachedCompiler,
        detachedTester: this.detachedTester,
        exported: this.exported
      };
      var notNil = function notNil(val) {
        return !(_ramda || _load_ramda()).default.isNil(val);
      };
      res = (_ramda || _load_ramda()).default.filter(notNil, res);
      return res;
    }
  }, {
    key: 'setMarkAsChangedCb',
    value: function setMarkAsChangedCb(markAsChangedBinded) {
      this.markBitMapChangedCb = markAsChangedBinded;
    }
  }, {
    key: '_findFile',
    value: function _findFile(fileName) {
      var _this = this;

      return this.files.find(function (file) {
        var filePath = _this.rootDir ? (0, (_utils || _load_utils()).pathJoinLinux)(_this.rootDir, file.relativePath) : file.relativePath;
        return filePath === fileName;
      });
    }
  }, {
    key: 'updateFileLocation',
    value: function updateFileLocation(fileFrom, fileTo) {
      fileFrom = (0, (_utils || _load_utils()).pathNormalizeToLinux)(fileFrom);
      fileTo = (0, (_utils || _load_utils()).pathNormalizeToLinux)(fileTo);
      var currentFile = this._findFile(fileFrom);
      var changes = [];
      if (currentFile) {
        var _rootDir = this.rootDir;
        var newLocation = _rootDir ? ComponentMap.getPathWithoutRootDir(_rootDir, fileTo) : fileTo;
        (_logger || _load_logger()).default.debug(`updating file location from ${currentFile.relativePath} to ${newLocation}`);
        if (this.mainFile === currentFile.relativePath) this.mainFile = newLocation;
        changes.push({ from: currentFile.relativePath, to: newLocation });
        currentFile.relativePath = newLocation;
        currentFile.name = (_path || _load_path()).default.basename(newLocation);
      }
      this.validate();
      return changes;
    }
  }, {
    key: 'updateDirLocation',
    value: function updateDirLocation(dirFrom, dirTo) {
      var _this2 = this;

      dirFrom = (0, (_utils || _load_utils()).pathNormalizeToLinux)(dirFrom);
      dirTo = (0, (_utils || _load_utils()).pathNormalizeToLinux)(dirTo);
      var changes = [];
      if (this.rootDir && this.rootDir.startsWith(dirFrom)) {
        var _rootDir2 = this.rootDir;
        var newRootDir = _rootDir2.replace(dirFrom, dirTo);
        var newRootDirNormalized = (0, (_utils || _load_utils()).pathNormalizeToLinux)(newRootDir);
        changes.push({ from: _rootDir2, to: newRootDirNormalized });
        (_logger || _load_logger()).default.debug(`updating rootDir location from ${_rootDir2} to ${newRootDirNormalized}`);
        this.rootDir = newRootDirNormalized;
        return changes;
      }
      this.files.forEach(function (file) {
        var filePath = _this2.rootDir ? (_path || _load_path()).default.join(_this2.rootDir, file.relativePath) : file.relativePath;
        if (filePath.startsWith(dirFrom)) {
          var fileTo = filePath.replace(dirFrom, dirTo);
          var newLocation = _this2.rootDir ? ComponentMap.getPathWithoutRootDir(_this2.rootDir, fileTo) : fileTo;
          (_logger || _load_logger()).default.debug(`updating file location from ${file.relativePath} to ${newLocation}`);
          if (_this2.mainFile === file.relativePath) _this2.mainFile = newLocation;
          changes.push({ from: file.relativePath, to: newLocation });
          file.relativePath = newLocation;
        }
      });
      if (this.origin === (_constants || _load_constants()).COMPONENT_ORIGINS.AUTHORED && this.trackDir && this.trackDir === dirFrom) {
        this.trackDir = dirTo;
      }
      this.validate();
      return changes;
    }
  }, {
    key: 'getFilesRelativeToConsumer',
    value: function getFilesRelativeToConsumer() {
      var _this3 = this;

      return this.files.map(function (file) {
        return _this3.rootDir ? (0, (_utils || _load_utils()).pathJoinLinux)(_this3.rootDir, file.relativePath) : file.relativePath;
      });
    }
  }, {
    key: 'getAllFilesPaths',
    value: function getAllFilesPaths() {
      return this.files.map(function (file) {
        return file.relativePath;
      });
    }
  }, {
    key: 'getFilesGroupedByBeingTests',
    value: function getFilesGroupedByBeingTests() {
      var allFiles = [];
      var nonTestsFiles = [];
      var testsFiles = [];
      this.files.forEach(function (file) {
        allFiles.push(file.relativePath);
        if (file.test) testsFiles.push(file.relativePath);else nonTestsFiles.push(file.relativePath);
      });
      return { allFiles, nonTestsFiles, testsFiles };
    }

    /**
     * if one of the added files is outside of the trackDir, remove the trackDir attribute
     */

  }, {
    key: 'removeTrackDirIfNeeded',
    value: function removeTrackDirIfNeeded() {
      if (!this.trackDir) return;
      if (this.origin !== (_constants || _load_constants()).COMPONENT_ORIGINS.AUTHORED) {
        this.trackDir = undefined;
        return;
      }
      var _iteratorNormalCompletion = true;
      var _didIteratorError = false;
      var _iteratorError = undefined;

      try {
        for (var _iterator = this.files[Symbol.iterator](), _step; !(_iteratorNormalCompletion = (_step = _iterator.next()).done); _iteratorNormalCompletion = true) {
          var file = _step.value;

          if (!file.relativePath.startsWith(this.trackDir)) {
            // Make sure we are not getting to case where we have config dir with {COMPONENT_DIR} but there is no component dir
            // This might happen in the following case:
            // User add a folder to bit which create the track dir
            // Then the user eject the config to that dir
            // Then the user adding a new file to that component which is outside of the component dir
            // This will remove the trackDir, so just before the remove we resolve it
            if (this.configDir) {
              this.configDir = this.configDir.getResolved({ componentDir: this.trackDir });
            }
            this.trackDir = undefined;
            return;
          }
        }
      } catch (err) {
        _didIteratorError = true;
        _iteratorError = err;
      } finally {
        try {
          if (!_iteratorNormalCompletion && _iterator.return) {
            _iterator.return();
          }
        } finally {
          if (_didIteratorError) {
            throw _iteratorError;
          }
        }
      }
    }

    /**
     * directory to track for changes (such as files added/renamed)
     */

  }, {
    key: 'getTrackDir',
    value: function getTrackDir() {
      if (this.origin === (_constants || _load_constants()).COMPONENT_ORIGINS.AUTHORED) return this.trackDir;
      if (this.origin === (_constants || _load_constants()).COMPONENT_ORIGINS.IMPORTED) {
        return this.wrapDir ? (0, (_utils || _load_utils()).pathJoinLinux)(this.rootDir, this.wrapDir) : this.rootDir;
      }
      // DO NOT track nested components!
      return null;
    }

    /**
     * this.rootDir is not defined for author. instead, the current workspace is the rootDir
     * also, for imported environments (compiler/tester) components the rootDir is empty
     */

  }, {
    key: 'getRootDir',
    value: function getRootDir() {
      return this.rootDir || '.';
    }

    /**
     * directory of the component (root / track)
     */

  }, {
    key: 'getComponentDir',
    value: function getComponentDir() {
      if (this.origin === (_constants || _load_constants()).COMPONENT_ORIGINS.AUTHORED) return this.trackDir;
      return this.rootDir;
    }
  }, {
    key: 'setConfigDir',
    value: function setConfigDir(val) {
      if (val === null || val === undefined) {
        delete this.configDir;
        this.markBitMapChangedCb();
        return;
      }
      this.markBitMapChangedCb();
      this.configDir = new (_configDir || _load_configDir()).default(val);
    }
  }, {
    key: 'deleteConfigDirIfNotExists',
    value: function () {
      var _ref2 = (0, (_bluebird || _load_bluebird()).coroutine)( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee() {
        var resolvedDir, isExist;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee$(_context) {
          while (1) {
            switch (_context.prev = _context.next) {
              case 0:
                resolvedDir = this.getBaseConfigDir();

                if (!resolvedDir) {
                  _context.next = 6;
                  break;
                }

                _context.next = 4;
                return (_fsExtra || _load_fsExtra()).default.exists(resolvedDir);

              case 4:
                isExist = _context.sent;

                if (!isExist) {
                  this.setConfigDir(null);
                }

              case 6:
              case 'end':
                return _context.stop();
            }
          }
        }, _callee, this);
      }));

      function deleteConfigDirIfNotExists() {
        return _ref2.apply(this, arguments);
      }

      return deleteConfigDirIfNotExists;
    }()

    /**
     * Get resolved base config dir (the dir where the bit.json is) after resolving the DSL
     */

  }, {
    key: 'getBaseConfigDir',
    value: function getBaseConfigDir() {
      if (!this.configDir) return null;
      var componentDir = this.getComponentDir();
      var configDir = this.configDir && this.configDir.getResolved({ componentDir }).getEnvTypeCleaned().linuxDirPath;
      return configDir;
    }

    /**
     * in case new files were created in the track-dir directory, add them to the component-map
     * so then they'll be tracked by bitmap
     */

  }, {
    key: 'trackDirectoryChanges',
    value: function () {
      var _ref3 = (0, (_bluebird || _load_bluebird()).coroutine)( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee2(consumer, id) {
        var trackDir, trackDirAbsolute, trackDirRelative, addParams, numOfFilesBefore, addContext, addComponents;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee2$(_context2) {
          while (1) {
            switch (_context2.prev = _context2.next) {
              case 0:
                trackDir = this.getTrackDir();

                if (!trackDir) {
                  _context2.next = 22;
                  break;
                }

                trackDirAbsolute = (_path || _load_path()).default.join(consumer.getPath(), trackDir);
                trackDirRelative = (_path || _load_path()).default.relative(process.cwd(), trackDirAbsolute);

                if ((_fsExtra || _load_fsExtra()).default.existsSync(trackDirAbsolute)) {
                  _context2.next = 6;
                  break;
                }

                throw new (_componentNotFoundInPath || _load_componentNotFoundInPath()).default(trackDirRelative);

              case 6:
                addParams = {
                  componentPaths: [trackDirRelative || '.'],
                  id: id.toString(),
                  override: false, // this makes sure to not override existing files of componentMap
                  trackDirFeature: true,
                  origin: this.origin
                };
                numOfFilesBefore = this.files.length;
                addContext = { consumer };
                addComponents = new (_addComponents || _load_addComponents()).default(addContext, addParams);
                _context2.prev = 10;
                _context2.next = 13;
                return addComponents.add();

              case 13:
                _context2.next = 21;
                break;

              case 15:
                _context2.prev = 15;
                _context2.t0 = _context2['catch'](10);

                if (!(_context2.t0 instanceof (_exceptions || _load_exceptions()).NoFiles || _context2.t0 instanceof (_exceptions || _load_exceptions()).EmptyDirectory)) {
                  _context2.next = 20;
                  break;
                }

                _context2.next = 21;
                break;

              case 20:
                throw _context2.t0;

              case 21:
                if (this.files.length > numOfFilesBefore) {
                  (_logger || _load_logger()).default.info(`new file(s) have been added to .bitmap for ${id.toString()}`);
                  consumer.bitMap.hasChanged = true;
                }

              case 22:
              case 'end':
                return _context2.stop();
            }
          }
        }, _callee2, this, [[10, 15]]);
      }));

      function trackDirectoryChanges(_x, _x2) {
        return _ref3.apply(this, arguments);
      }

      return trackDirectoryChanges;
    }()
  }, {
    key: 'removeFiles',
    value: function removeFiles(files) {
      var relativePaths = files.map(function (file) {
        return file.relativePath;
      });
      this.files = this.files.reduce(function (accumulator, file) {
        return relativePaths.includes(file.relativePath) ? accumulator : accumulator.concat(file);
      }, []);
      this.validate();
    }
  }, {
    key: 'sort',
    value: function sort() {
      this.files = (_ramda || _load_ramda()).default.sortBy((_ramda || _load_ramda()).default.prop('relativePath'), this.files);
    }
  }, {
    key: 'clone',
    value: function clone() {
      // $FlowFixMe - there is some issue with the config dir type
      return new ComponentMap(this);
    }
  }, {
    key: 'validate',
    value: function validate() {
      var _this4 = this;

      var errorMessage = `failed adding or updating a ${(_constants || _load_constants()).BIT_MAP} record of ${this.id.toString()}.`;
      if (!this.mainFile) throw new (_validationError || _load_validationError()).default(`${errorMessage} mainFile attribute is missing`);
      if (!(0, (_utils || _load_utils()).isValidPath)(this.mainFile)) {
        throw new (_validationError || _load_validationError()).default(`${errorMessage} mainFile attribute ${this.mainFile} is invalid`);
      }
      // if it's an environment component (such as compiler) the rootDir is empty
      if (!this.rootDir && this.origin === (_constants || _load_constants()).COMPONENT_ORIGINS.NESTED) {
        throw new (_validationError || _load_validationError()).default(`${errorMessage} rootDir attribute is missing`);
      }
      // $FlowFixMe
      if (this.rootDir && !(0, (_utils || _load_utils()).isValidPath)(this.rootDir)) {
        throw new (_validationError || _load_validationError()).default(`${errorMessage} rootDir attribute ${this.rootDir} is invalid`);
      }
      if (this.rootDir && this.origin === (_constants || _load_constants()).COMPONENT_ORIGINS.AUTHORED) {
        throw new (_validationError || _load_validationError()).default(`${errorMessage} rootDir attribute should not be set for AUTHORED component`);
      }
      if (this.trackDir && this.origin !== (_constants || _load_constants()).COMPONENT_ORIGINS.AUTHORED) {
        throw new (_validationError || _load_validationError()).default(`${errorMessage} trackDir attribute should be set for AUTHORED component only`);
      }
      if (this.originallySharedDir && this.origin !== (_constants || _load_constants()).COMPONENT_ORIGINS.IMPORTED) {
        throw new (_validationError || _load_validationError()).default(`${errorMessage} originallySharedDir attribute should be set for IMPORTED components only`);
      }

      if (!this.files || !this.files.length) throw new (_validationError || _load_validationError()).default(`${errorMessage} files list is missing`);
      this.files.forEach(function (file) {
        if (!(0, (_utils || _load_utils()).isValidPath)(file.relativePath)) {
          throw new (_validationError || _load_validationError()).default(`${errorMessage} file path ${file.relativePath} is invalid`);
        }
      });
      var foundMainFile = this.files.find(function (file) {
        return file.relativePath === _this4.mainFile;
      });
      if (!foundMainFile || (_ramda || _load_ramda()).default.isEmpty(foundMainFile)) {
        throw new (_validationError || _load_validationError()).default(`${errorMessage} mainFile ${this.mainFile} is not in the files list`);
      }
      var filesPaths = this.files.map(function (file) {
        return file.relativePath;
      });
      var duplicateFiles = filesPaths.filter(function (file) {
        return filesPaths.filter(function (f) {
          return file.toLowerCase() === f.toLowerCase();
        }).length > 1;
      });
      if (duplicateFiles.length) {
        throw new (_validationError || _load_validationError()).default(`${errorMessage} the following files are duplicated ${duplicateFiles.join(', ')}`);
      }
      if (this.trackDir) {
        var _trackDir = this.trackDir;
        this.files.forEach(function (file) {
          if (!file.relativePath.startsWith(_trackDir)) {
            throw new (_validationError || _load_validationError()).default(`${errorMessage} a file path ${file.relativePath} is not in the trackDir ${_trackDir}`);
          }
        });
      }
    }
  }], [{
    key: 'fromJson',
    value: function fromJson(componentMapObj) {
      return new ComponentMap(componentMapObj);
    }
  }, {
    key: 'getPathWithoutRootDir',
    value: function getPathWithoutRootDir(rootDir, filePath) {
      var newPath = (0, (_utils || _load_utils()).pathRelativeLinux)(rootDir, filePath);
      if (newPath.startsWith('..')) {
        // this is forbidden for security reasons. Allowing files to be written outside the components directory may
        // result in overriding OS files.
        throw new (_generalError || _load_generalError()).default(`unable to add file ${filePath} because it's located outside the component root dir ${rootDir}`);
      }
      return newPath;
    }
  }, {
    key: 'changeFilesPathAccordingToItsRootDir',
    value: function changeFilesPathAccordingToItsRootDir(existingRootDir, files) {
      var _this5 = this;

      var changes = [];
      files.forEach(function (file) {
        var newPath = _this5.getPathWithoutRootDir(existingRootDir, file.relativePath);
        changes.push({ from: file.relativePath, to: newPath });
        file.relativePath = newPath;
      });
      return changes;
    }
  }]);

  return ComponentMap;
}();

exports.default = ComponentMap;