'use strict';
const path = require('path');
const debug = require('debug')('yeoman:environment');

/**
 * @mixin
 * @alias env/namespace-composability
 */
const composability = module.exports;

/**
 * Get a generator only by namespace.
 * @private
 * @param  {YeomanNamespace|String} namespace
 * @return {Generator|null} - the generator found at the location
 */
composability.getByNamespace = function (namespace) {
  const ns = this.requireNamespace(namespace).namespace;
  const Generator = this.store.get(ns) || this.store.get(this.alias(ns));
  return this._findGeneratorClass(Generator);
};

/**
 * Lookup and register generators from the custom local repository.
 *
 * @private
 * @param  {YeomanNamespace[]} namespacesToLookup - namespaces to lookup.
 * @return {Object[]} List of generators
 */
composability.lookupLocalNamespaces = function (namespacesToLookup) {
  if (!namespacesToLookup) {
    return [];
  }
  namespacesToLookup = Array.isArray(namespacesToLookup) ? namespacesToLookup : [namespacesToLookup];
  namespacesToLookup = namespacesToLookup.map(ns => this.requireNamespace(ns));
  // Keep only those packages that has a compatible version.
  namespacesToLookup = namespacesToLookup.filter(ns => {
    return this.repository.verifyInstalledVersion(ns.generatorHint, ns.semver) !== undefined;
  });
  return this.lookupLocalPackages(namespacesToLookup.map(ns => ns.generatorHint));
};

/**
 * Install generators at the custom local repository and register.
 *
 * @private
 * @param  {Object} packages - packages to install key(packageName): value(versionRange).
 * @return  {Boolean} - true if the install succeeded.
 */
composability.installLocalGenerators = function (packages) {
  const entries = Object.entries(packages).filter(([packageName, version]) => !this.repository.verifyInstalledVersion(packageName, version));
  if (entries.length === 0) {
    return true;
  }
  const toInstall = {};
  entries.forEach(([packageName, version]) => {
    toInstall[packageName] = version;
  });
  if (this.repository.installPackages(toInstall)) {
    const packagesToLookup = entries.map(([packageName, _]) => packageName);
    this.lookupLocalPackages(packagesToLookup);
    return true;
  }
  return false;
};

/**
 * Lookup and register generators from the custom local repository.
 *
 * @private
 * @param  {String[]} [packagesToLookup='generator-*'] - packages to lookup.
 */
composability.lookupLocalPackages = function (packagesToLookup = 'generator-*') {
  this.lookup({packagePatterns: packagesToLookup, npmPaths: this.repository.nodeModulesPath});
};

/**
 * Lookup and register generators from the custom local repository.
 *
 * @private
 * @param  {YeomanNamespace[]} namespacesToLookup - namespaces to lookup.
 * @return {Object[]} List of generators
 */
composability.lookupLocalNamespaces = function (namespacesToLookup) {
  if (!namespacesToLookup) {
    return [];
  }
  namespacesToLookup = Array.isArray(namespacesToLookup) ? namespacesToLookup : [namespacesToLookup];
  namespacesToLookup = namespacesToLookup.map(ns => this.requireNamespace(ns));
  // Keep only those packages that has a compatible version.
  namespacesToLookup = namespacesToLookup.filter(ns => {
    return this.repository.verifyInstalledVersion(ns.generatorHint, ns.semver) !== undefined;
  });
  return this.lookupLocalPackages(namespacesToLookup.map(ns => ns.generatorHint));
};

/**
 * Search for generators or sub generators by namespace.
 *
 * @private
 * @param {boolean|Object} [options] options passed to lookup. Options singleResult,
 *                                   filePatterns and packagePatterns can be overridden
 * @return {Array|Object} List of generators
 */
composability.lookupNamespaces = function (namespaces, options = {}) {
  if (!namespaces) {
    return [];
  }
  namespaces = Array.isArray(namespaces) ? namespaces : [namespaces];
  namespaces = namespaces.map(ns => this.requireNamespace(ns));
  const opts = namespaces.map(ns => {
    const nsOpts = {packagePatterns: ns.generatorHint};
    if (ns.generator) {
      // Build filePatterns to look specifically for the namespace.
      const genPath = ns.generator.split(':').join('/');
      let filePatterns = [`${genPath}/index.?s`, `${genPath}.?s`];
      const lookups = options.lookups || this.lookups;
      filePatterns = lookups.map(prefix => {
        return filePatterns.map(pattern => path.join(prefix, pattern));
      }).reduce(
        (accumulator, currentValue) => accumulator.concat(currentValue),
        []
      );
      nsOpts.filePatterns = filePatterns;
      nsOpts.singleResult = true;
    }
    return nsOpts;
  });
  return opts.map(opt => this.lookup({...opt, ...options})).reduce((acc, cur) => acc.concat(cur), []);
};

/**
 * Load or install namespaces based on the namespace flag
 *
 * @private
 * @param  {String|Array} - namespaces
 * @return  {boolean} - true if every required namespace was found.
 */
composability.prepareEnvironment = function (namespaces) {
  debug('Preparing %o', namespaces);
  namespaces = Array.isArray(namespaces) ? namespaces : [namespaces];
  let missing = namespaces.map(ns => this.requireNamespace(ns));

  const updateMissing = () => {
    // Remove already loaded namespaces
    missing = namespaces.filter(ns => !this.getByNamespace(ns));
    return missing;
  };

  const assertMissing = missing => {
    if (missing.length !== 0) {
      throw new Error(`Error preparing environment for ${missing.map(ns => ns.complete).join()}`);
    }
  };

  updateMissing();

  // Remove optionals
  missing = missing.filter(ns => !ns.optional);

  // Keep only ns with load and install flags
  const failed = missing.filter(ns => !ns.load && !ns.install);

  // Fail for required ns without load or install flags
  assertMissing(failed);

  // Lookup at custom repository
  this.lookupLocalNamespaces(missing);
  if (updateMissing().length === 0) {
    return true;
  }

  // Lookup local and global for load flag.
  this.lookupNamespaces(missing.filter(ns => ns.load));
  if (updateMissing().length === 0) {
    return true;
  }

  // Fail if ns with load flag failed to load.
  assertMissing(missing.filter(ns => ns.load));

  // Install missing
  const toInstall = {};
  missing.forEach(ns => {
    toInstall[ns.generatorHint] = ns.semver;
  });
  debug('Installing %o', toInstall);
  this.installLocalGenerators(toInstall);
  if (updateMissing().length === 0) {
    return true;
  }
  // At last, try to lookup if install failed.
  this.lookupNamespaces(missing);

  assertMissing(updateMissing());
  return true;
};

@@ -1407,1991 +1407,8 @@
 %7D;%0A%0A
-/**%0A * Install generators at the custom local repository and register.%0A *%0A * @private%0A * @param  %7BObject%7D packages - packages to install key(packageName): value(versionRange).%0A * @return  %7BBoolean%7D - true if the install succeeded.%0A */%0Acomposability.installLocalGenerators = function (packages) %7B%0A  const entries = Object.entries(packages).filter((%5BpackageName, version%5D) =%3E !this.repository.verifyInstalledVersion(packageName, version));%0A  if (entries.length === 0) %7B%0A    return true;%0A  %7D%0A  const toInstall = %7B%7D;%0A  entries.forEach((%5BpackageName, version%5D) =%3E %7B%0A    toInstall%5BpackageName%5D = version;%0A  %7D);%0A  if (this.repository.installPackages(toInstall)) %7B%0A    const packagesToLookup = entries.map((%5BpackageName, _%5D) =%3E packageName);%0A    this.lookupLocalPackages(packagesToLookup);%0A    return true;%0A  %7D%0A  return false;%0A%7D;%0A%0A/**%0A * Lookup and register generators from the custom local repository.%0A *%0A * @private%0A * @param  %7BString%5B%5D%7D %5BpackagesToLookup='generator-*'%5D - packages to lookup.%0A */%0Acomposability.lookupLocalPackages = function (packagesToLookup = 'generator-*') %7B%0A  this.lookup(%7BpackagePatterns: packagesToLookup, npmPaths: this.repository.nodeModulesPath%7D);%0A%7D;%0A%0A/**%0A * Lookup and register generators from the custom local repository.%0A *%0A * @private%0A * @param  %7BYeomanNamespace%5B%5D%7D namespacesToLookup - namespaces to lookup.%0A * @return %7BObject%5B%5D%7D List of generators%0A */%0Acomposability.lookupLocalNamespaces = function (namespacesToLookup) %7B%0A  if (!namespacesToLookup) %7B%0A    return %5B%5D;%0A  %7D%0A  namespacesToLookup = Array.isArray(namespacesToLookup) ? namespacesToLookup : %5BnamespacesToLookup%5D;%0A  namespacesToLookup = namespacesToLookup.map(ns =%3E this.requireNamespace(ns));%0A  // Keep only those packages that has a compatible version.%0A  namespacesToLookup = namespacesToLookup.filter(ns =%3E %7B%0A    return this.repository.verifyInstalledVersion(ns.generatorHint, ns.semver) !== undefined;%0A  %7D);%0A  return this.lookupLocalPackages(namespacesToLookup.map(ns =%3E ns.generatorHint));%0A%7D;%0A%0A
 /**%0A

Remove duplicated methods, they were moved to another file (#218)