const path = require('path');

const eslint = require('eslint');
const glob = require('glob');
const difference = require('./array-diff');
const getSortedRules = require('./sort-rules');
const normalizePluginName = require('./normalize-plugin-name');

function _getConfigFile(specifiedFile) {
  if (specifiedFile) {
    if (path.isAbsolute(specifiedFile)) {
      return specifiedFile;
    }
    return path.join(process.cwd(), specifiedFile);
  }
  // This is not being called with an arg. Use the package.json `main`
  return require(path.join(process.cwd(), 'package.json')).main;
}

async function _getConfigs(configFile, files) {
  let isPathIgnored;
  let getConfigForFile;

  if (eslint.ESLint) {
    const esLint = new eslint.ESLint({
      // Ignore any config applicable depending on the location on the filesystem
      useEslintrc: false,
      // Point to the particular config
      overrideConfigFile: configFile
    });
    isPathIgnored = esLint.isPathIgnored.bind(esLint);
    getConfigForFile = esLint.calculateConfigForFile.bind(esLint);
  } else {
    const cliEngine = new eslint.CLIEngine({
      // Ignore any config applicable depending on the location on the filesystem
      useEslintrc: false,
      // Point to the particular config
      configFile
    });
    isPathIgnored = cliEngine.isPathIgnored.bind(cliEngine);
    getConfigForFile = cliEngine.getConfigForFile.bind(cliEngine);
  }

  const configs = files.map(async filePath => (
    await isPathIgnored(filePath) ? false : getConfigForFile(filePath)
  ));
  return new Set((await Promise.all(configs)).filter(Boolean));
}

async function _getConfig(configFile, files) {
  return Array.from(await _getConfigs(configFile, files)).reduce((prev, item) => {
    return Object.assign(prev, item, {rules: Object.assign({}, prev.rules, item.rules)});
  }, {});
}

function _getCurrentNamesRules(config) {
  return config.rules ? Object.keys(config.rules) : [];
}

function _isDeprecated(rule) {
  return rule && rule.meta && rule.meta.deprecated;
}

function _notDeprecated(rule) {
  return !_isDeprecated(rule);
}

function _getPluginRules(config) {
  const pluginRules = new Map();
  const plugins = config.plugins;
  /* istanbul ignore else */
  if (plugins) {
    plugins.forEach(plugin => {
      const normalized = normalizePluginName(plugin);
      const pluginConfig = require(normalized.module);
      const rules = pluginConfig.rules === undefined ? {} : pluginConfig.rules;

      Object.keys(rules).forEach(ruleName =>
        pluginRules.set(`${normalized.prefix}/${ruleName}`, rules[ruleName])
      );
    });
  }
  return pluginRules;
}

function _getCoreRules() {
  return (eslint.Linter ? new eslint.Linter() : eslint.linter).getRules();
}

function _filterRuleNames(ruleNames, rules, predicate) {
  return ruleNames.filter(ruleName => predicate(rules.get(ruleName)));
}

function _isNotCore(rule) {
  return rule.indexOf('/') !== '-1';
}

function _escapeRegExp(str) {
  return str.replace(/[\\^$.*+?()[\]{}|]/g, '\\$&');
}

function _createExtensionRegExp(extensions) {
  const normalizedExts = extensions.map(ext => _escapeRegExp(
    ext.startsWith('.') ? ext.slice(1) : ext
  ));

  return new RegExp(`.\\.(?:${normalizedExts.join("|")})$`);
}

function RuleFinder(config, {omitCore, includeDeprecated}) {
  let currentRuleNames = _getCurrentNamesRules(config);
  if (omitCore) {
    currentRuleNames = currentRuleNames.filter(_isNotCore);
  }

  const pluginRules = _getPluginRules(config); // eslint-disable-line vars-on-top
  const coreRules = _getCoreRules();
  const allRules = omitCore ? pluginRules : new Map([...coreRules, ...pluginRules]);

  let allRuleNames = [...allRules.keys()];
  let pluginRuleNames = [...pluginRules.keys()];
  if (!includeDeprecated) {
    allRuleNames = _filterRuleNames(allRuleNames, allRules, _notDeprecated);
    pluginRuleNames = _filterRuleNames(pluginRuleNames, pluginRules, _notDeprecated);
  }
  const deprecatedRuleNames = _filterRuleNames(currentRuleNames, allRules, _isDeprecated);
  const dedupedRuleNames = [...new Set(allRuleNames)];
  const unusedRuleNames = difference(dedupedRuleNames, currentRuleNames);

  // Get all the current rules instead of referring the extended files or documentation
  this.getCurrentRules = () => getSortedRules(currentRuleNames);

  // Get all the current rules' particular configuration
  this.getCurrentRulesDetailed = () => config.rules;

  // Get all the plugin rules instead of referring the extended files or documentation
  this.getPluginRules = () => getSortedRules(pluginRuleNames);

  // Get all the available rules instead of referring eslint and plugin packages or documentation
  this.getAllAvailableRules = () => getSortedRules(dedupedRuleNames);

  this.getUnusedRules = () => getSortedRules(unusedRuleNames);

  // Get all the current rules that are deprecated
  this.getDeprecatedRules = () => getSortedRules(deprecatedRuleNames);
}

async function createRuleFinder(specifiedFile, options) {
  const configFile = _getConfigFile(specifiedFile);

  const {ext = ['.js']} = options;
  const extensionRegExp = _createExtensionRegExp(ext);
  const files = glob.sync(`**/*`, {dot: true, matchBase: true})
    .filter(file => extensionRegExp.test(file));

  const config = await _getConfig(configFile, files);

  return new RuleFinder(config, options);
}

module.exports = function (specifiedFile, options = {}) {
  return createRuleFinder(specifiedFile, options);
};

@@ -30,22 +30,34 @@
 %0A%0Aconst 
-eslint
+%7B ESLint, Linter %7D
  = requi
@@ -623,100 +623,37 @@
 igs(
-configFile, files) %7B%0A  let isPathIgnored;%0A  let getConfigForFile;%0A%0A  if (eslint.ESLint
+overrideConfigFile, files
 ) %7B%0A
-  
   co
@@ -669,23 +669,16 @@
 t = new 
-eslint.
 ESLint(%7B
@@ -678,18 +678,16 @@
 SLint(%7B%0A
-  
     // I
@@ -754,34 +754,32 @@
  filesystem%0A    
-  
 useEslintrc: fal
@@ -778,34 +778,32 @@
 trc: false,%0A    
- 
- 
 // Point to the 
@@ -824,18 +824,16 @@
 fig%0A    
-  
 override
@@ -846,511 +846,14 @@
 File
-: configFile%0A    %7D);%0A    isPathIgnored = esLint.isPathIgnored.bind(esLint);%0A    getConfigForFile = esLint.calculateConfigForFile.bind(esLint);%0A  %7D else %7B%0A    const cliEngine = new eslint.CLIEngine(%7B%0A      // Ignore any config applicable depending on the location on the filesystem%0A      useEslintrc: false,%0A      // Point to the particular config%0A      configFile%0A    %7D);%0A    isPathIgnored = cliEngine.isPathIgnored.bind(cliEngine);%0A    getConfigForFile = cliEngine.getConfigForFile.bind(cliEngine);
 %0A  %7D
+);
 %0A%0A  
@@ -908,16 +908,23 @@
   await 
+esLint.
 isPathIg
@@ -949,19 +949,32 @@
 false : 
-get
+esLint.calculate
 ConfigFo
@@ -2125,60 +2125,19 @@
 urn 
-(eslint.Linter ? new eslint.Linter() : eslint.linter
+new Linter(
 ).ge

remove unneeded fallback code