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
