'use strict';

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

var _regenerator;

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

var _ramda;

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

var _objects;

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

var _constants;

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

var _migrationHelper;

function _load_migrationHelper() {
  return _migrationHelper = _interopRequireDefault(require('../../migration/migration-helper'));
}

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

var globalVerbose = false;
var refsIndex = {};

/**
 * Running migration process for scope
 * @param {string} scopeVersion - The current scope version
 * @param {Object} migratonManifest - A manifest which define what migrations to run
 * @param {BitRawObject} objects - Scope's raw objects
 * @param {boolean} verbose - print logs
 */

exports.default = function () {
  var _ref = _asyncToGenerator( /*#__PURE__*/(_regenerator || _load_regenerator()).default.mark(function _callee(scopeVersion, migratonManifest, objects) {
    var verbose = arguments.length > 3 && arguments[3] !== undefined ? arguments[3] : false;
    var migrations, result, noMigrationMsg;
    return (_regenerator || _load_regenerator()).default.wrap(function _callee$(_context) {
      while (1) {
        switch (_context.prev = _context.next) {
          case 0:
            globalVerbose = verbose;
            migrations = (0, (_migrationHelper || _load_migrationHelper()).default)((_constants || _load_constants()).BIT_VERSION, scopeVersion, migratonManifest, verbose);
            result = { newObjects: {}, refsToRemove: [] };

            if (!(_ramda || _load_ramda()).default.isEmpty(migrations)) {
              _context.next = 8;
              break;
            }

            noMigrationMsg = 'there are no migrations to run, leaving the scope as is with no changes';

            (_logger || _load_logger()).default.debug(noMigrationMsg);
            if (verbose) console.log(noMigrationMsg); // eslint-disable-line
            return _context.abrupt('return', result);

          case 8:
            (_ramda || _load_ramda()).default.forEach(_runAllMigrationsForObject(migrations), objects);
            (_ramda || _load_ramda()).default.forEach(_getRealObjectWithUpdatedRefs(result, refsIndex), objects);
            result.newObjects = (_ramda || _load_ramda()).default.values(result.newObjects);
            return _context.abrupt('return', result);

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

  function migrate(_x2, _x3, _x4) {
    return _ref.apply(this, arguments);
  }

  return migrate;
}();

/**
 * Runs all the migrations for all the versions for a given object
 * @param {VersionMigrations[]} migrations
 */


var _runAllMigrationsForObject = function _runAllMigrationsForObject(migrations) {
  return function (rawObject) {
    // Make sure we got a rawObject (we might get a null object in case of corrupted object)
    if (!rawObject) {
      return null;
    }
    (_logger || _load_logger()).default.debug(`start updating object ${rawObject.ref} (${rawObject.id})`);
    // Skip Source files since we don't want the migration to run over them
    if (rawObject.type === 'Source') return null;
    // Add refs to index
    _addObjectRefsToIndex(refsIndex, rawObject);
    return (_ramda || _load_ramda()).default.forEach(_runAllVersionMigrationsForObject(rawObject), migrations);
  };
};

/**
 * Runs all the the migration in specific version on object
 * @param {BitRawObject} rawObject - object to run migration on
 */
var _runAllVersionMigrationsForObject = function _runAllVersionMigrationsForObject(rawObject) {
  return function (migrations) {
    var versionNumber = Object.keys(migrations)[0];
    (_logger || _load_logger()).default.debug(`updating object ${rawObject.ref} (${rawObject.id}) to version ${versionNumber}`);
    var migrationForObjectType = migrations[versionNumber][rawObject.type];
    // There is no migration for this type of object for this version
    if (!migrationForObjectType) return rawObject;
    return (_ramda || _load_ramda()).default.forEach(_runOneMigrationForObject(rawObject), migrationForObjectType);
  };
};

/**
 * Run specific migration function on an object
 * @param {BitRawObject} rawObject
 */
var _runOneMigrationForObject = function _runOneMigrationForObject(rawObject) {
  return function (migration) {
    (_logger || _load_logger()).default.debug(`running migration: ${migration.name} on object ${rawObject.ref} (${rawObject.id})`);
    if (globalVerbose) console.log(`running migration: ${migration.name} on object ${rawObject.ref} (${rawObject.id})`);
    try {
      var migratedContent = migration.migrate(rawObject.getParsedContent());
      rawObject.parsedContent = migratedContent;
      return migratedContent;
    } catch (err) {
      (_logger || _load_logger()).default.error(`FAILED - running migration: ${migration.name} on object ${rawObject.ref} (${rawObject.id})`);
      throw err;
    }
  };
};

/**
 * Adds all the refs from the raw object to a global index
 * To improve performence in case we need to update objet in case the id of the ref has been changed
 * @param {BitRawObject} rawObject
 */
function _addObjectRefsToIndex(index, rawObject) {
  var refs = rawObject.refs();
  refs.forEach(function (ref) {
    index[ref] = rawObject;
  });
}

/**
 * Update a refrence for an object and return the parsed real object
 * @param {*} index - refs index in order to update refs if needed
 * @param {*} oldRef
 * @param {*} newRef
 */
var _updateRefsForObjects = function _updateRefsForObjects(index, oldRef, newRef) {
  // If the object doesn't has a dependent object return null
  // This object reference won't be update anywhere
  if (!index[oldRef]) {
    (_logger || _load_logger()).default.warn(`the object ref: ${oldRef} has been updated to: ${newRef} but there is no reference to this object`);
    return null;
  }
  var realObject = index[oldRef].toRealObject();
  if (oldRef !== newRef) {
    // Get the dependent object and replace the ref to the new one
    (_logger || _load_logger()).default.debug(`replacing reference for ${realObject.id()} old ref was: ${oldRef} new ref is: ${newRef}`);
    if (globalVerbose) {
      console.log(`replacing reference for ${realObject.id()} old ref was: ${oldRef} new ref is: ${newRef}`);
    }
    realObject.replaceRef(new (_objects || _load_objects()).Ref(oldRef), new (_objects || _load_objects()).Ref(newRef));
  }
  return realObject;
};

/**
 * Get the real object and update refs if needed
 * The result will be added to the result cache
 * @param {ScopeMigrationResultCache} result - results cache
 * @param {{ [string]: BitRawObject}} index - refs index in order to update refs if needed
 */
var _getRealObjectWithUpdatedRefs = function _getRealObjectWithUpdatedRefs(result, index) {
  return function (object) {
    // Make sure we got a rawObject (we might get a null object in case of corrupted object)
    if (!object) {
      return null;
    }
    var realObject = object.toRealObject();
    // Make sure to not ovveride result we already put during the updte ref process
    if (result.newObjects[realObject.hash().hash]) return null;
    result.newObjects[realObject.hash().hash] = realObject;
    // Check if we need to update ref
    if (realObject.hash().hash !== object.ref) {
      result.refsToRemove.push(new (_objects || _load_objects()).Ref(object.ref));
      var dependentObject = _updateRefsForObjects(index, object.ref, realObject.hash().hash);
      // Update the dependent object only if found one
      if (dependentObject) {
        result.newObjects[dependentObject.hash().hash] = dependentObject;
      }
    }
  };
};