'use strict';

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

var _regenerator;

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

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

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

var _path;

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

var _object;

function _load_object() {
  return _object = _interopRequireDefault(require('./object'));
}

var _rawObject;

function _load_rawObject() {
  return _rawObject = _interopRequireDefault(require('./raw-object'));
}

var _ref7;

function _load_ref() {
  return _ref7 = _interopRequireDefault(require('./ref'));
}

var _constants;

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

var _exceptions;

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

var _utils;

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

var _scope;

function _load_scope() {
  return _scope = require('../../scope');
}

var _models;

function _load_models() {
  return _models = require('../models');
}

var _logger;

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

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 _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var OBJECTS_BACKUP_DIR = `${(_constants || _load_constants()).OBJECTS_DIR}.bak`;

var Repository = function () {
  function Repository(scope) {
    var objectTypes = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : [];

    _classCallCheck(this, Repository);

    this.objects = [];
    this._cache = {};

    this.scope = scope;
    // TODO: use typesToObject from object-registrar
    this.types = objectTypes.reduce(function (map, objectType) {
      map[objectType.name] = objectType;
      return map;
    }, {});
  }

  _createClass(Repository, [{
    key: 'ensureDir',
    value: function ensureDir() {
      return (0, (_utils || _load_utils()).mkdirp)(this.getPath());
    }
  }, {
    key: 'getPath',
    value: function getPath() {
      return (_path || _load_path()).default.join(this.scope.getPath(), (_constants || _load_constants()).OBJECTS_DIR);
    }
  }, {
    key: 'getBackupPath',
    value: function getBackupPath(dirName) {
      var backupPath = (_path || _load_path()).default.join(this.scope.getPath(), OBJECTS_BACKUP_DIR);
      return dirName ? (_path || _load_path()).default.join(backupPath, dirName) : backupPath;
    }
  }, {
    key: 'getLicense',
    value: function getLicense() {
      return this.scope.scopeJson.getPopulatedLicense();
    }
  }, {
    key: 'getScopeMetaObject',
    value: function getScopeMetaObject() {
      var _this = this;

      return this.getLicense().then(function (license) {
        return (_models || _load_models()).ScopeMeta.fromObject({ license, name: _this.scope.name }).compress();
      });
    }
  }, {
    key: 'objectPath',
    value: function objectPath(ref) {
      var hash = ref.toString();
      return (_path || _load_path()).default.join(this.getPath(), hash.slice(0, 2), hash.slice(2));
    }
  }, {
    key: 'load',
    value: function load(ref) {
      var _this2 = this;

      if (this.getCache(ref)) return Promise.resolve(this.getCache(ref));

      return (0, (_utils || _load_utils()).readFile)(this.objectPath(ref)).then(function (fileContents) {
        return (_object || _load_object()).default.parseObject(fileContents, _this2.types);
      }).catch(function (err) {
        if (err.code === 'ENOENT') {
          (_logger || _load_logger()).default.debug(`Failed finding a ref file ${_this2.objectPath(ref)}.`);
        } else {
          (_logger || _load_logger()).default.error(`Failed reading a ref file ${_this2.objectPath(ref)}. Error: ${err.message}`);
        }
        return null;
      });
    }
  }, {
    key: 'list',
    value: function () {
      var _ref = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee() {
        var _this3 = this;

        var refs;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee$(_context) {
          while (1) {
            switch (_context.prev = _context.next) {
              case 0:
                _context.next = 2;
                return this.listRefs();

              case 2:
                refs = _context.sent;
                return _context.abrupt('return', Promise.all(refs.map(function (ref) {
                  return _this3.load(ref);
                })));

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

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

      return list;
    }()
  }, {
    key: 'listRefs',
    value: function () {
      var _ref2 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee2() {
        var matches, refs;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee2$(_context2) {
          while (1) {
            switch (_context2.prev = _context2.next) {
              case 0:
                _context2.next = 2;
                return (0, (_utils || _load_utils()).glob)((_path || _load_path()).default.join('*', '*'), { cwd: this.getPath() });

              case 2:
                matches = _context2.sent;
                refs = matches.map(function (str) {
                  var hash = str.replace((_path || _load_path()).default.sep, '');
                  return new (_ref7 || _load_ref()).default(hash);
                });
                return _context2.abrupt('return', refs);

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

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

      return listRefs;
    }()
  }, {
    key: 'listRawObjects',
    value: function () {
      var _ref3 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee4() {
        var _this4 = this;

        var refs;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee4$(_context4) {
          while (1) {
            switch (_context4.prev = _context4.next) {
              case 0:
                _context4.next = 2;
                return this.listRefs();

              case 2:
                refs = _context4.sent;
                return _context4.abrupt('return', Promise.all(refs.map(function () {
                  var _ref4 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee3(ref) {
                    var buffer, bitRawObject;
                    return (_regenerator || _load_regenerator()).default.wrap(function _callee3$(_context3) {
                      while (1) {
                        switch (_context3.prev = _context3.next) {
                          case 0:
                            _context3.prev = 0;
                            _context3.next = 3;
                            return _this4.loadRaw(ref);

                          case 3:
                            buffer = _context3.sent;
                            _context3.next = 6;
                            return (_rawObject || _load_rawObject()).default.fromDeflatedBuffer(buffer, ref.hash, _this4.types);

                          case 6:
                            bitRawObject = _context3.sent;
                            return _context3.abrupt('return', bitRawObject);

                          case 10:
                            _context3.prev = 10;
                            _context3.t0 = _context3['catch'](0);

                            (_logger || _load_logger()).default.error(`Couldn't load the ref ${ref} this object is probably corrupted and should be delete`);
                            return _context3.abrupt('return', null);

                          case 14:
                          case 'end':
                            return _context3.stop();
                        }
                      }
                    }, _callee3, _this4, [[0, 10]]);
                  }));

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

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

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

      return listRawObjects;
    }()
  }, {
    key: 'listComponents',
    value: function () {
      var _ref5 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee5() {
        var includeSymlinks = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : true;
        var filterComponents;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee5$(_context5) {
          while (1) {
            switch (_context5.prev = _context5.next) {
              case 0:
                filterComponents = function filterComponents(refs) {
                  return refs.filter(function (ref) {
                    return includeSymlinks ? ref instanceof (_models || _load_models()).Component || ref instanceof (_models || _load_models()).Symlink : ref instanceof (_models || _load_models()).Component;
                  });
                };

                if (!includeSymlinks) {
                  _context5.next = 7;
                  break;
                }

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

                _context5.next = 5;
                return this.list().then(filterComponents);

              case 5:
                this._cacheListComponentsWithSymlinks = _context5.sent;

              case 6:
                return _context5.abrupt('return', this._cacheListComponentsWithSymlinks);

              case 7:
                if (this._cacheListComponentsWithoutSymlinks) {
                  _context5.next = 11;
                  break;
                }

                _context5.next = 10;
                return this.list().then(filterComponents);

              case 10:
                this._cacheListComponentsWithoutSymlinks = _context5.sent;

              case 11:
                return _context5.abrupt('return', this._cacheListComponentsWithoutSymlinks);

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

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

      return listComponents;
    }()
  }, {
    key: 'remove',
    value: function remove(ref) {
      this.removeFromCache(ref);
      var pathToDelete = this.objectPath(ref);
      (_logger || _load_logger()).default.debug(`repository.remove: deleting ${pathToDelete}`);
      return (0, (_utils || _load_utils()).removeFile)(pathToDelete, true);
    }
  }, {
    key: 'removeMany',
    value: function removeMany(refs) {
      var _this5 = this;

      return Promise.all(refs.map(function (ref) {
        return _this5.remove(ref);
      }));
    }
  }, {
    key: 'loadRaw',
    value: function loadRaw(ref) {
      return (0, (_utils || _load_utils()).readFile)(this.objectPath(ref));
    }
  }, {
    key: 'loadRawObject',
    value: function () {
      var _ref6 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee6(ref) {
        var buffer, bitRawObject;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee6$(_context6) {
          while (1) {
            switch (_context6.prev = _context6.next) {
              case 0:
                _context6.next = 2;
                return this.loadRaw(ref);

              case 2:
                buffer = _context6.sent;
                _context6.next = 5;
                return (_rawObject || _load_rawObject()).default.fromDeflatedBuffer(buffer, ref.hash, this.types);

              case 5:
                bitRawObject = _context6.sent;
                return _context6.abrupt('return', bitRawObject);

              case 7:
              case 'end':
                return _context6.stop();
            }
          }
        }, _callee6, this);
      }));

      function loadRawObject(_x4) {
        return _ref6.apply(this, arguments);
      }

      return loadRawObject;
    }()
  }, {
    key: 'loadSync',
    value: function loadSync(ref) {
      var throws = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : true;

      try {
        var objectFile = (_fsExtra || _load_fsExtra()).default.readFileSync(this.objectPath(ref));
        return (_object || _load_object()).default.parseSync(objectFile, this.types);
      } catch (err) {
        if (throws) {
          throw new (_exceptions || _load_exceptions()).HashNotFound(ref.toString());
        }
        return null;
      }
    }
  }, {
    key: 'setCache',
    value: function setCache(object) {
      this._cache[object.hash().toString()] = object;
      return this;
    }
  }, {
    key: 'getCache',
    value: function getCache(ref) {
      return this._cache[ref.toString()];
    }
  }, {
    key: 'removeFromCache',
    value: function removeFromCache(ref) {
      delete this._cache[ref.toString()];
    }
  }, {
    key: 'backup',
    value: function backup(dirName) {
      var backupDir = this.getBackupPath(dirName);
      var objectsDir = this.getPath();
      (_logger || _load_logger()).default.debug(`making a backup of all objects from ${objectsDir} to ${backupDir}`);
      (_fsExtra || _load_fsExtra()).default.emptyDirSync(backupDir);
      (_fsExtra || _load_fsExtra()).default.copySync(objectsDir, backupDir);
    }
  }, {
    key: 'add',
    value: function add(object) {
      if (!object) return this;
      // leave the following commented log message, it is very useful for debugging but too verbose when not needed.
      // logger.debug(`repository: adding object ${object.hash().toString()} which consist of the following id: ${object.id()}`);
      this.objects.push(object);
      this.setCache(object);
      return this;
    }
  }, {
    key: 'addMany',
    value: function addMany(objects) {
      var _this6 = this;

      if (!objects || !objects.length) return this;
      objects.forEach(function (obj) {
        return _this6.add(obj);
      });
      return this;
    }

    /**
     * alias to `load`
     */

  }, {
    key: 'findOne',
    value: function findOne(ref) {
      return this.load(ref);
    }
  }, {
    key: 'findMany',
    value: function findMany(refs) {
      var _this7 = this;

      return Promise.all(refs.map(function (ref) {
        return _this7.load(ref);
      }));
    }
  }, {
    key: 'persist',
    value: function persist() {
      var _this8 = this;

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

      (_logger || _load_logger()).default.debug(`repository: persisting ${this.objects.length} objects, with validate = ${validate.toString()}`);
      // @TODO handle failures
      if (!validate) this.objects.map(function (object) {
        return object.validateBeforePersist = false;
      });
      return Promise.all(this.objects.map(function (object) {
        return _this8.persistOne(object);
      }));
    }
  }, {
    key: 'persistOne',
    value: function persistOne(object) {
      var _this9 = this;

      return object.compress().then(function (contents) {
        var options = {};
        if (_this9.scope.groupName) options.gid = (0, (_utils || _load_utils()).resolveGroupId)(_this9.scope.groupName);
        var objectPath = _this9.objectPath(object.hash());
        (_logger || _load_logger()).default.debug(`writing an object into ${objectPath}`);
        return (0, (_utils || _load_utils()).writeFile)(objectPath, contents, options);
      });
    }
  }]);

  return Repository;
}();

exports.default = Repository;