"use strict";

const util = require('util');

const ParameterTypeMatcher = require('./parameter_type_matcher');

const ParameterType = require('./parameter_type');

const CombinatorialGeneratedExpressionFactory = require('./combinatorial_generated_expression_factory');

class CucumberExpressionGenerator {
  constructor(parameterTypeRegistry) {
    this._parameterTypeRegistry = parameterTypeRegistry;
  }

  generateExpressions(text) {
    const parameterTypeCombinations = [];

    const parameterTypeMatchers = this._createParameterTypeMatchers(text);

    let expressionTemplate = '';
    let pos = 0; // eslint-disable-next-line no-constant-condition

    while (true) {
      let matchingParameterTypeMatchers = [];

      for (const parameterTypeMatcher of parameterTypeMatchers) {
        const advancedParameterTypeMatcher = parameterTypeMatcher.advanceTo(pos);

        if (advancedParameterTypeMatcher.find) {
          matchingParameterTypeMatchers.push(advancedParameterTypeMatcher);
        }
      }

      if (matchingParameterTypeMatchers.length > 0) {
        matchingParameterTypeMatchers = matchingParameterTypeMatchers.sort(ParameterTypeMatcher.compare); // Find all the best parameter type matchers, they are all candidates.

        const bestParameterTypeMatcher = matchingParameterTypeMatchers[0];
        const bestParameterTypeMatchers = matchingParameterTypeMatchers.filter(m => ParameterTypeMatcher.compare(m, bestParameterTypeMatcher) === 0); // Build a list of parameter types without duplicates. The reason there
        // might be duplicates is that some parameter types have more than one regexp,
        // which means multiple ParameterTypeMatcher objects will have a reference to the
        // same ParameterType.
        // We're sorting the list so preferential parameter types are listed first.
        // Users are most likely to want these, so they should be listed at the top.

        let parameterTypes = [];

        for (const parameterTypeMatcher of bestParameterTypeMatchers) {
          if (parameterTypes.indexOf(parameterTypeMatcher.parameterType) === -1) {
            parameterTypes.push(parameterTypeMatcher.parameterType);
          }
        }

        parameterTypes = parameterTypes.sort(ParameterType.compare);
        parameterTypeCombinations.push(parameterTypes);
        expressionTemplate += escape(text.slice(pos, bestParameterTypeMatcher.start));
        expressionTemplate += '{%s}';
        pos = bestParameterTypeMatcher.start + bestParameterTypeMatcher.group.length;
      } else {
        break;
      }

      if (pos >= text.length) {
        break;
      }
    }

    expressionTemplate += escape(text.slice(pos));
    return new CombinatorialGeneratedExpressionFactory(expressionTemplate, parameterTypeCombinations).generateExpressions();
  }
  /**
   * @deprecated
   */


  generateExpression(text) {
    return util.deprecate(() => this.generateExpressions(text)[0], 'CucumberExpressionGenerator.generateExpression: Use CucumberExpressionGenerator.generateExpressions instead')();
  }

  _createParameterTypeMatchers(text) {
    let parameterMatchers = [];

    for (const parameterType of this._parameterTypeRegistry.parameterTypes) {
      if (parameterType.useForSnippets) {
        parameterMatchers = parameterMatchers.concat(this._createParameterTypeMatchers2(parameterType, text));
      }
    }

    return parameterMatchers;
  }

  _createParameterTypeMatchers2(parameterType, text) {
    // TODO: [].map
    const result = [];

    for (const regexp of parameterType.regexps) {
      result.push(new ParameterTypeMatcher(parameterType, regexp, text));
    }

    return result;
  }

}

function escape(s) {
  return s.replace(/%/g, '%%') // for util.format
  .replace(/\(/g, '\\(').replace(/{/g, '\\{').replace(/\//g, '\\/');
}

module.exports = CucumberExpressionGenerator;