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

function _load_abstractVinyl() {
  return _abstractVinyl = _interopRequireDefault(require('./abstract-vinyl'));
}

var _symlink;

function _load_symlink() {
  return _symlink = _interopRequireDefault(require('../../../links/symlink'));
}

var _logger;

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

var _removePath;

function _load_removePath() {
  return _removePath = _interopRequireDefault(require('./remove-path'));
}

var _removeFilesAndEmptyDirsRecursively;

function _load_removeFilesAndEmptyDirsRecursively() {
  return _removeFilesAndEmptyDirsRecursively = _interopRequireDefault(require('../../../utils/fs/remove-files-and-empty-dirs-recursively'));
}

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 DataToPersist = function () {
  function DataToPersist() {
    _classCallCheck(this, DataToPersist);

    this.files = [];
    this.symlinks = [];
    this.remove = [];
  }

  _createClass(DataToPersist, [{
    key: 'addFile',
    value: function addFile(file) {
      if (!file) throw new Error('failed adding an empty file into DataToPersist');
      if (!file.path) {
        throw new Error('failed adding a file into DataToPersist as it does not have a path property');
      }
      var existingFileIndex = this.files.findIndex(function (existingFile) {
        return existingFile.path === file.path;
      });
      if (existingFileIndex !== -1) {
        if (file.override) {
          // delete existing file
          this.files.splice(existingFileIndex, 1);
        } else {
          // don't push this one. keep the existing file
          return;
        }
      }
      this._throwForDirectoryCollision(file);
      this.files.push(file);
    }
  }, {
    key: 'addManyFiles',
    value: function addManyFiles() {
      var _this = this;

      var files = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];

      files.forEach(function (file) {
        return _this.addFile(file);
      });
    }
  }, {
    key: 'removePath',
    value: function removePath(pathToRemove) {
      if (!pathToRemove) throw new Error('failed adding a path to remove into DataToPersist');
      this.remove.push(pathToRemove);
    }
  }, {
    key: 'removeManyPaths',
    value: function removeManyPaths() {
      var _this2 = this;

      var pathsToRemove = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];

      pathsToRemove.forEach(function (pathToRemove) {
        return _this2.removePath(pathToRemove);
      });
    }
  }, {
    key: 'addSymlink',
    value: function addSymlink(symlink) {
      if (!symlink.src) throw new Error('failed adding a symlink into DataToPersist, src is empty');
      if (!symlink.dest) throw new Error('failed adding a symlink into DataToPersist, dest is empty');
      this.symlinks.push(symlink);
    }
  }, {
    key: 'addManySymlinks',
    value: function addManySymlinks() {
      var _this3 = this;

      var symlinks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : [];

      symlinks.forEach(function (symlink) {
        return _this3.addSymlink(symlink);
      });
    }
  }, {
    key: 'merge',
    value: function merge(dataToPersist) {
      if (!dataToPersist) return;
      this.addManyFiles(dataToPersist.files);
      this.removeManyPaths(dataToPersist.remove);
      this.addManySymlinks(dataToPersist.symlinks);
    }
  }, {
    key: 'persistAllToFS',
    value: function () {
      var _ref = (0, (_bluebird || _load_bluebird()).coroutine)( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee() {
        return (_regenerator || _load_regenerator()).default.wrap(function _callee$(_context) {
          while (1) {
            switch (_context.prev = _context.next) {
              case 0:
                this._log();
                this._validate();
                // the order is super important. first remove, then create and finally symlink
                _context.next = 4;
                return this._deletePathsFromFS();

              case 4:
                _context.next = 6;
                return this._persistFilesToFS();

              case 6:
                _context.next = 8;
                return this._persistSymlinksToFS();

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

      function persistAllToFS() {
        return _ref.apply(this, arguments);
      }

      return persistAllToFS;
    }()
  }, {
    key: 'persistAllToCapsule',
    value: function () {
      var _ref2 = (0, (_bluebird || _load_bluebird()).coroutine)( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee2() {
        return (_regenerator || _load_regenerator()).default.wrap(function _callee2$(_context2) {
          while (1) {
            switch (_context2.prev = _context2.next) {
              case 0:
                throw new Error('not implemented yet');

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

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

      return persistAllToCapsule;
    }()
  }, {
    key: 'addBasePath',
    value: function addBasePath(basePath) {
      var _this4 = this;

      this.files.forEach(function (file) {
        _this4._assertRelative(file.base);
        file.updatePaths({ newBase: (_path || _load_path()).default.join(basePath, file.base) });
      });
      this.symlinks.forEach(function (symlink) {
        _this4._assertRelative(symlink.src);
        _this4._assertRelative(symlink.dest);
        symlink.src = (_path || _load_path()).default.join(basePath, symlink.src);
        symlink.dest = (_path || _load_path()).default.join(basePath, symlink.dest);
      });
      this.remove.forEach(function (removePath) {
        _this4._assertRelative(removePath.path);
        removePath.path = (_path || _load_path()).default.join(basePath, removePath.path);
      });
    }
    /**
     * helps for debugging
     */

  }, {
    key: 'toConsole',
    value: function toConsole() {
      console.log(`\nfiles: ${this.files.map(function (f) {
        return f.path;
      }).join('\n')}`); // eslint-disable-line no-console
      console.log(`remove: ${this.remove.map(function (r) {
        return r.path;
      }).join('\n')}`); // eslint-disable-line no-console
    }
  }, {
    key: '_persistFilesToFS',
    value: function () {
      var _ref3 = (0, (_bluebird || _load_bluebird()).coroutine)( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee3() {
        return (_regenerator || _load_regenerator()).default.wrap(function _callee3$(_context3) {
          while (1) {
            switch (_context3.prev = _context3.next) {
              case 0:
                return _context3.abrupt('return', Promise.all(this.files.map(function (file) {
                  return file.write();
                })));

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

      function _persistFilesToFS() {
        return _ref3.apply(this, arguments);
      }

      return _persistFilesToFS;
    }()
  }, {
    key: '_persistSymlinksToFS',
    value: function () {
      var _ref4 = (0, (_bluebird || _load_bluebird()).coroutine)( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee4() {
        return (_regenerator || _load_regenerator()).default.wrap(function _callee4$(_context4) {
          while (1) {
            switch (_context4.prev = _context4.next) {
              case 0:
                return _context4.abrupt('return', Promise.all(this.symlinks.map(function (symlink) {
                  return symlink.write();
                })));

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

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

      return _persistSymlinksToFS;
    }()
  }, {
    key: '_deletePathsFromFS',
    value: function () {
      var _ref5 = (0, (_bluebird || _load_bluebird()).coroutine)( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee5() {
        var pathWithRemoveItsDirIfEmptyEnabled, restPaths;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee5$(_context5) {
          while (1) {
            switch (_context5.prev = _context5.next) {
              case 0:
                pathWithRemoveItsDirIfEmptyEnabled = this.remove.filter(function (p) {
                  return p.removeItsDirIfEmpty;
                }).map(function (p) {
                  return p.path;
                });
                restPaths = this.remove.filter(function (p) {
                  return !p.removeItsDirIfEmpty;
                });

                if (!pathWithRemoveItsDirIfEmptyEnabled.length) {
                  _context5.next = 5;
                  break;
                }

                _context5.next = 5;
                return (0, (_removeFilesAndEmptyDirsRecursively || _load_removeFilesAndEmptyDirsRecursively()).default)(pathWithRemoveItsDirIfEmptyEnabled);

              case 5:
                return _context5.abrupt('return', Promise.all(restPaths.map(function (removePath) {
                  return removePath.persistToFS();
                })));

              case 6:
              case 'end':
                return _context5.stop();
            }
          }
        }, _callee5, this);
      }));

      function _deletePathsFromFS() {
        return _ref5.apply(this, arguments);
      }

      return _deletePathsFromFS;
    }()
  }, {
    key: '_validate',
    value: function _validate() {
      // it's important to make sure that all paths are absolute before writing them to the
      // filesystem. relative paths won't work when running bit commands from an inner dir
      var validateAbsolutePath = function validateAbsolutePath(pathToValidate) {
        if (!(_path || _load_path()).default.isAbsolute(pathToValidate)) {
          throw new Error(`DataToPersist expects ${pathToValidate} to be absolute, got relative`);
        }
      };
      this.files.forEach(function (file) {
        validateAbsolutePath(file.path);
      });
      this.remove.forEach(function (removePath) {
        validateAbsolutePath(removePath.path);
      });
      this.symlinks.forEach(function (symlink) {
        validateAbsolutePath(symlink.src);
        validateAbsolutePath(symlink.dest);
      });
    }
  }, {
    key: '_log',
    value: function _log() {
      if (this.remove.length) {
        var pathToDeleteStr = this.remove.map(function (r) {
          return r.path;
        }).join('\n');
        (_logger || _load_logger()).default.debug(`DateToPersist, paths-to-delete:\n${pathToDeleteStr}`);
      }
      if (this.files.length) {
        var filesToWriteStr = this.files.map(function (f) {
          return f.path;
        }).join('\n');
        (_logger || _load_logger()).default.debug(`DateToPersist, paths-to-write:\n${filesToWriteStr}`);
      }
      if (this.symlinks.length) {
        var symlinksStr = this.symlinks.map(function (symlink) {
          return `src: ${symlink.src}, dest: ${symlink.dest}`;
        }).join('\n');
        (_logger || _load_logger()).default.debug(`DateToPersist, symlinks:\n${symlinksStr}`);
      }
    }
  }, {
    key: '_assertRelative',
    value: function _assertRelative(pathToCheck) {
      if ((_path || _load_path()).default.isAbsolute(pathToCheck)) {
        throw new Error(`DataToPersist expects ${pathToCheck} to be relative, but found it absolute`);
      }
    }
    /**
     * prevent adding a file which later on will cause an error "EEXIST: file already exists, mkdir {dirname}".
     * this happens one a file is a directory name of the other file.
     * e.g. adding these two files, will cause the error above: "bar/foo" and "bar"
     *
     * to check for this possibility, we need to consider two scenarios:
     * 1) "bar/foo" is there and now adding "bar" => check whether one of the files starts with "bar/"
     * 2) "bar" is there and now adding "bar/foo" => check whether this file "bar/foo" starts with one of the files with '/'
     * practically, it runs `("bar/foo".startsWith("bar/"))` for both cases above.
     */

  }, {
    key: '_throwForDirectoryCollision',
    value: function _throwForDirectoryCollision(file) {
      var directoryCollision = this.files.find(function (f) {
        return f.path.startsWith(`${file.path}${(_path || _load_path()).default.sep}`) || `${file.path}`.startsWith(`${f.path}${(_path || _load_path()).default.sep}`);
      });
      if (directoryCollision) {
        throw new Error(`unable to add the file "${file.path}", because another file "${directoryCollision.path}" is going to be written.
one of them is a directory of the other one, and is not possible to have them both`);
      }
    }
  }]);

  return DataToPersist;
}();

exports.default = DataToPersist;