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

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

var _utils;

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

var _objects;

function _load_objects() {
  return _objects = require('../objects');
}

var _componentObjects;

function _load_componentObjects() {
  return _componentObjects = _interopRequireDefault(require('../component-objects'));
}

var _scope;

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

var _constants;

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

var _exceptions;

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

var _models;

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

var _bitId;

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

var _component;

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

var _globalConfig;

function _load_globalConfig() {
  return _globalConfig = _interopRequireWildcard(require('../../api/consumer/lib/global-config'));
}

var _logger;

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

var _repository;

function _load_repository() {
  return _repository = _interopRequireDefault(require('../objects/repository'));
}

var _abstractVinyl;

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

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

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

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

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var SourceRepository = function () {
  function SourceRepository(scope) {
    _classCallCheck(this, SourceRepository);

    this.scope = scope;
  }

  _createClass(SourceRepository, [{
    key: 'objects',
    value: function objects() {
      return this.scope.objects;
    }
  }, {
    key: 'findComponent',
    value: function () {
      var _ref = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee(component) {
        var foundComponent;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee$(_context) {
          while (1) {
            switch (_context.prev = _context.next) {
              case 0:
                _context.prev = 0;
                _context.next = 3;
                return this.objects().findOne(component.hash());

              case 3:
                foundComponent = _context.sent;

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

                return _context.abrupt('return', foundComponent);

              case 6:
                _context.next = 11;
                break;

              case 8:
                _context.prev = 8;
                _context.t0 = _context['catch'](0);

                (_logger || _load_logger()).default.error(`findComponent got an error ${_context.t0}`);

              case 11:
                (_logger || _load_logger()).default.debug(`failed finding a component ${component.id()} with hash: ${component.hash().toString()}`);
                return _context.abrupt('return', null);

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

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

      return findComponent;
    }()
  }, {
    key: 'getMany',
    value: function getMany(ids) {
      var _this = this;

      (_logger || _load_logger()).default.debug(`sources.getMany, Ids: ${ids.join(', ')}`);
      return Promise.all(ids.map(function (id) {
        return _this.get(id).then(function (component) {
          return {
            id,
            component
          };
        });
      }));
    }
  }, {
    key: 'get',
    value: function () {
      var _ref2 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee2(bitId) {
        var component, foundComponent, realComponentId, msg, version;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee2$(_context2) {
          while (1) {
            switch (_context2.prev = _context2.next) {
              case 0:
                component = (_models || _load_models()).Component.fromBitId(bitId);
                _context2.next = 3;
                return this.findComponent(component);

              case 3:
                foundComponent = _context2.sent;

                if (!(foundComponent instanceof (_models || _load_models()).Symlink)) {
                  _context2.next = 9;
                  break;
                }

                realComponentId = foundComponent.getRealComponentId();
                _context2.next = 8;
                return this.findComponent((_models || _load_models()).Component.fromBitId(realComponentId));

              case 8:
                foundComponent = _context2.sent;

              case 9:
                if (!(foundComponent && bitId.hasVersion())) {
                  _context2.next = 20;
                  break;
                }

                msg = `found ${bitId.toStringWithoutVersion()}, however version ${bitId.getVersion().versionNum}`;

                if (foundComponent.versions[bitId.version]) {
                  _context2.next = 14;
                  break;
                }

                (_logger || _load_logger()).default.debug(`${msg} is not in the component versions array`);
                return _context2.abrupt('return', null);

              case 14:
                _context2.next = 16;
                return this.objects().findOne(foundComponent.versions[bitId.version]);

              case 16:
                version = _context2.sent;

                if (version) {
                  _context2.next = 20;
                  break;
                }

                (_logger || _load_logger()).default.debug(`${msg} object was not found on the filesystem`);
                return _context2.abrupt('return', null);

              case 20:
                return _context2.abrupt('return', foundComponent);

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

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

      return get;
    }()
  }, {
    key: 'getObjects',
    value: function getObjects(id) {
      var _this2 = this;

      return this.get(id).then(function (component) {
        if (!component) throw new (_exceptions || _load_exceptions()).ComponentNotFound(id.toString());
        return component.collectObjects(_this2.objects());
      });
    }
  }, {
    key: 'findOrAddComponent',
    value: function findOrAddComponent(props) {
      var comp = (_models || _load_models()).Component.from(props);
      return this.findComponent(comp).then(function (component) {
        if (!component) return comp;
        return component;
      });
    }
  }, {
    key: 'modifyCIProps',
    value: function modifyCIProps(_ref3) {
      var source = _ref3.source,
          ciProps = _ref3.ciProps;

      var objectRepo = this.objects();

      return this.findOrAddComponent(source).then(function (component) {
        return component.loadVersion(component.latest(), objectRepo).then(function (version) {
          version.setCIProps(ciProps);
          return objectRepo.persistOne(version);
        });
      });
    }
  }, {
    key: 'modifySpecsResults',
    value: function modifySpecsResults(_ref4) {
      var source = _ref4.source,
          specsResults = _ref4.specsResults;

      var objectRepo = this.objects();

      return this.findOrAddComponent(source).then(function (component) {
        return component.loadVersion(component.latest(), objectRepo).then(function (version) {
          version.setSpecsResults(specsResults);
          return objectRepo.persistOne(version);
        });
      });
    }

    // TODO: This should treat dist as an array

  }, {
    key: 'updateDist',
    value: function updateDist(_ref5) {
      var source = _ref5.source;

      var objectRepo = this.objects();

      return this.findOrAddComponent(source).then(function (component) {
        return component.loadVersion(component.latest(), objectRepo).then(function (version) {
          var dist = source.dist ? (_models || _load_models()).Source.from((0, (_utils || _load_utils()).bufferFrom)(source.dist.toString())) : null;
          version.setDist(dist);
          objectRepo.add(dist).add(version);
          return objectRepo.persist();
        });
      });
    }

    /**
     * Given a consumer-component object, returns the Version representation.
     * Useful for saving into the model or calculation the hash for comparing with other Version object.
     *
     * Warning: Do not change anything on the consumerComponent instance! Only use its cloned.
     *
     * @param consumerComponent
     * @param consumer
     * @param message
     * @param flattenedDependencies
     * @param dists
     * @param specsResults
     * @return {Promise.<{version: Version, dists: *, files: *}>}
     */

  }, {
    key: 'consumerComponentToVersion',
    value: function () {
      var _ref7 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee3(_ref6) {
        var consumerComponent = _ref6.consumerComponent,
            versionFromModel = _ref6.versionFromModel,
            message = _ref6.message,
            flattenedDependencies = _ref6.flattenedDependencies,
            flattenedDevDependencies = _ref6.flattenedDevDependencies,
            flattenedCompilerDependencies = _ref6.flattenedCompilerDependencies,
            flattenedTesterDependencies = _ref6.flattenedTesterDependencies,
            dists = _ref6.dists,
            specsResults = _ref6.specsResults;
        var clonedComponent, setEol, files, compilerFiles, testerFiles, username, email, version;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee3$(_context3) {
          while (1) {
            switch (_context3.prev = _context3.next) {
              case 0:
                // $FlowFixMe
                clonedComponent = consumerComponent.clone();

                setEol = function setEol(files) {
                  if (!files) return;
                  var result = files.map(function (file) {
                    file.file = (_models || _load_models()).Source.from((_utils || _load_utils()).eol.lf(file.contents, file.relative));
                    return file;
                  });
                  return result;
                };

                files = consumerComponent.files && consumerComponent.files.length ? consumerComponent.files.map(function (file) {
                  return {
                    name: file.basename,
                    relativePath: clonedComponent.addSharedDir(file.relative),
                    file: (_models || _load_models()).Source.from((_utils || _load_utils()).eol.lf(file.contents, file.relative)),
                    test: file.test
                  };
                }) : null;
                compilerFiles = setEol((_ramda || _load_ramda()).default.path(['compiler', 'files'], consumerComponent));
                testerFiles = setEol((_ramda || _load_ramda()).default.path(['tester', 'files'], consumerComponent));
                username = (_globalConfig || _load_globalConfig()).getSync((_constants || _load_constants()).CFG_USER_NAME_KEY);
                email = (_globalConfig || _load_globalConfig()).getSync((_constants || _load_constants()).CFG_USER_EMAIL_KEY);


                clonedComponent.mainFile = clonedComponent.addSharedDir(clonedComponent.mainFile);
                clonedComponent.getAllDependencies().forEach(function (dependency) {
                  dependency.relativePaths.forEach(function (relativePath) {
                    if (!relativePath.isCustomResolveUsed) {
                      // for isCustomResolveUsed it was never stripped
                      relativePath.sourceRelativePath = clonedComponent.addSharedDir(relativePath.sourceRelativePath);
                    }
                  });
                });
                version = (_models || _load_models()).Version.fromComponent({
                  component: clonedComponent,
                  versionFromModel,
                  files,
                  dists,
                  flattenedDependencies,
                  flattenedDevDependencies,
                  flattenedCompilerDependencies,
                  flattenedTesterDependencies,
                  specsResults,
                  message,
                  username,
                  email
                });
                // $FlowFixMe it's ok to override the pendingVersion attribute

                consumerComponent.pendingVersion = version; // helps to validate the version against the consumer-component

                return _context3.abrupt('return', { version, files, compilerFiles, testerFiles });

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

      function consumerComponentToVersion(_x3) {
        return _ref7.apply(this, arguments);
      }

      return consumerComponentToVersion;
    }()
  }, {
    key: 'addSource',
    value: function () {
      var _ref9 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee4(_ref8) {
        var source = _ref8.source,
            flattenedDependencies = _ref8.flattenedDependencies,
            flattenedDevDependencies = _ref8.flattenedDevDependencies,
            flattenedCompilerDependencies = _ref8.flattenedCompilerDependencies,
            flattenedTesterDependencies = _ref8.flattenedTesterDependencies,
            message = _ref8.message,
            exactVersion = _ref8.exactVersion,
            releaseType = _ref8.releaseType,
            dists = _ref8.dists,
            specsResults = _ref8.specsResults;

        var objectRepo, component, versionRef, versionFromModel, _ref10, version, files, compilerFiles, testerFiles;

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

                // if a component exists in the model, add a new version. Otherwise, create a new component on the model

                _context4.next = 3;
                return this.findOrAddComponent(source);

              case 3:
                component = _context4.sent;

                // TODO: instead of doing that like this we should use:
                // const versionFromModel = await component.loadVersion(source.usedVersion, this.scope.objects);
                // it looks like it's exactly the same code but it's not working from some reason
                versionRef = component.versions[source.usedVersion];
                versionFromModel = void 0;

                if (!versionRef) {
                  _context4.next = 10;
                  break;
                }

                _context4.next = 9;
                return this.scope.getObject(versionRef.hash);

              case 9:
                versionFromModel = _context4.sent;

              case 10:
                _context4.next = 12;
                return this.consumerComponentToVersion({
                  consumerComponent: source,
                  versionFromModel,
                  message,
                  flattenedDependencies,
                  flattenedDevDependencies,
                  flattenedCompilerDependencies,
                  flattenedTesterDependencies,
                  dists,
                  specsResults
                });

              case 12:
                _ref10 = _context4.sent;
                version = _ref10.version;
                files = _ref10.files;
                compilerFiles = _ref10.compilerFiles;
                testerFiles = _ref10.testerFiles;

                component.addVersion(version, releaseType, exactVersion);
                objectRepo.add(version).add(component);

                if (files) files.forEach(function (file) {
                  return objectRepo.add(file.file);
                });
                if (dists) dists.forEach(function (dist) {
                  return objectRepo.add(dist.file);
                });
                if (compilerFiles) compilerFiles.forEach(function (file) {
                  return objectRepo.add(file.file);
                });
                if (testerFiles) testerFiles.forEach(function (file) {
                  return objectRepo.add(file.file);
                });

                return _context4.abrupt('return', component);

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

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

      return addSource;
    }()
  }, {
    key: 'putAdditionalVersion',
    value: function putAdditionalVersion(component, version, message) {
      var releaseType = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : (_constants || _load_constants()).DEFAULT_BIT_RELEASE_TYPE;

      version.log = {
        message,
        username: (_globalConfig || _load_globalConfig()).getSync((_constants || _load_constants()).CFG_USER_NAME_KEY),
        email: (_globalConfig || _load_globalConfig()).getSync((_constants || _load_constants()).CFG_USER_EMAIL_KEY),
        date: Date.now().toString()
      };
      component.addVersion(version, releaseType);
      return this.put({ component, objects: [version] });
    }
  }, {
    key: 'put',
    value: function put(_ref11) {
      var component = _ref11.component,
          objects = _ref11.objects;

      (_logger || _load_logger()).default.debug(`sources.put, id: ${component.id()}, versions: ${component.listVersions().join(', ')}`);
      var repo = this.objects();
      repo.add(component);

      var isObjectShouldBeAdded = function isObjectShouldBeAdded(obj) {
        // don't add a component if it's already exist locally with more versions
        if (obj instanceof (_models || _load_models()).Component) {
          var loaded = repo.loadSync(obj.hash(), false);
          if (loaded) {
            if (Object.keys(loaded.versions) > Object.keys(obj.versions)) {
              return false;
            }
          }
        }
        return true;
      };

      objects.forEach(function (obj) {
        if (isObjectShouldBeAdded(obj)) repo.add(obj);
      });
      return component;
    }

    /**
     * removeVersion - remove specific component version from component
     * @param {Component} component - component to remove version from
     * @param {string} version - version to remove.
     * @param persist
     */

  }, {
    key: 'removeVersion',
    value: function () {
      var _ref12 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee5(component, version) {
        var persist = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
        var objectRepo, modifiedComponent;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee5$(_context5) {
          while (1) {
            switch (_context5.prev = _context5.next) {
              case 0:
                (_logger || _load_logger()).default.debug(`removing version ${version} of ${component.id()} from a local scope`);
                objectRepo = this.objects();
                _context5.next = 4;
                return component.removeVersion(objectRepo, version);

              case 4:
                modifiedComponent = _context5.sent;

                objectRepo.add(modifiedComponent);

                if (!persist) {
                  _context5.next = 9;
                  break;
                }

                _context5.next = 9;
                return objectRepo.persist();

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

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

      function removeVersion(_x7, _x8) {
        return _ref12.apply(this, arguments);
      }

      return removeVersion;
    }()

    /**
     * clean - remove component or component version
     * @param {BitId} bitId - id to remove
     * @param {boolean} deepRemove - remove all component refs or only version refs
     */

  }, {
    key: 'clean',
    value: function () {
      var _ref13 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee6(bitId) {
        var deepRemove = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
        var component;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee6$(_context6) {
          while (1) {
            switch (_context6.prev = _context6.next) {
              case 0:
                (_logger || _load_logger()).default.debug(`sources.clean: ${bitId}, deepRemove: ${deepRemove.toString()}`);
                _context6.next = 3;
                return this.get(bitId);

              case 3:
                component = _context6.sent;

                if (component) {
                  _context6.next = 6;
                  break;
                }

                return _context6.abrupt('return');

              case 6:
                _context6.next = 8;
                return component.remove(this.objects(), deepRemove);

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

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

      return clean;
    }()

    /**
     * merge the existing component with the data from the incoming component
     * here, we assume that there is no conflict between the two, otherwise, this.merge() would throw
     * a MergeConflict exception.
     */

  }, {
    key: 'mergeTwoComponentsObjects',
    value: function mergeTwoComponentsObjects(existingComponent, incomingComponent) {
      // the base component to save is the existingComponent because it might contain local data that
      // is not available in the remote component, such as the "state" property.
      var mergedComponent = existingComponent;
      // in case the existing version has is different than incoming version hash, use the incoming
      // version because we hold the incoming component from a remote as the source of truth
      Object.keys(existingComponent.versions).forEach(function (existingVersion) {
        if (incomingComponent.versions[existingVersion]) {
          mergedComponent.versions[existingVersion] = incomingComponent.versions[existingVersion];
        }
      });
      // in case the incoming component has versions that are not in the existing component, copy them
      Object.keys(incomingComponent.versions).forEach(function (incomingVersion) {
        if (!existingComponent.versions[incomingVersion]) {
          mergedComponent.versions[incomingVersion] = incomingComponent.versions[incomingVersion];
        }
      });
      return mergedComponent;
    }

    /**
     * Adds the objects into scope.object array, in-memory. It doesn't save anything to the file-system.
     *
     * When this function gets called originally from import command, the 'local' parameter is true. Otherwise, if it was
     * originated from export command, it'll be false.
     * If the 'local' is true and the existing component wasn't changed locally, it doesn't check for
     * discrepancies, but simply override the existing component.
     * In this context, "discrepancy" means, same version but different hashes.
     * When using import command, it makes sense to override a component in case of discrepancies because the source of
     * truth should be the remote scope from where the import fetches the component.
     * When the same component has different versions in the remote and the local, it merges the two
     * by calling this.mergeTwoComponentsObjects().
     */

  }, {
    key: 'merge',
    value: function () {
      var _ref15 = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee7(_ref14) {
        var component = _ref14.component,
            objects = _ref14.objects;
        var inScope = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : false;
        var local = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : true;
        var existingComponent, locallyChanged, mergedComponent, conflictVersions;
        return (_regenerator || _load_regenerator()).default.wrap(function _callee7$(_context7) {
          while (1) {
            switch (_context7.prev = _context7.next) {
              case 0:
                if (inScope) component.scope = this.scope.name;
                _context7.next = 3;
                return this.findComponent(component);

              case 3:
                existingComponent = _context7.sent;

                if (existingComponent) {
                  _context7.next = 6;
                  break;
                }

                return _context7.abrupt('return', this.put({ component, objects }));

              case 6:
                _context7.next = 8;
                return existingComponent.isLocallyChanged();

              case 8:
                locallyChanged = _context7.sent;

                if (!(local && !locallyChanged || component.compatibleWith(existingComponent, local))) {
                  _context7.next = 13;
                  break;
                }

                (_logger || _load_logger()).default.debug(`sources.merge component ${component.id()}`);
                mergedComponent = this.mergeTwoComponentsObjects(existingComponent, component);
                return _context7.abrupt('return', this.put({ component: mergedComponent, objects }));

              case 13:
                conflictVersions = component.diffWith(existingComponent, local);
                throw new (_exceptions || _load_exceptions()).MergeConflict(component.id(), conflictVersions);

              case 15:
              case 'end':
                return _context7.stop();
            }
          }
        }, _callee7, this);
      }));

      function merge(_x13) {
        return _ref15.apply(this, arguments);
      }

      return merge;
    }()
  }]);

  return SourceRepository;
}();

exports.default = SourceRepository;