'use strict';

var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }();

function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } }

var Argument = require('./argument');
var TreeRegexp = require('./tree_regexp');

var _require = require('./errors'),
    UndefinedParameterTypeError = _require.UndefinedParameterTypeError;

// Does not include (){} characters because they have special meaning


var ESCAPE_REGEXP = /([\\^[$.|?*+])/g;
var PARAMETER_REGEXP = /(\\\\)?{([^}]+)}/g;
var OPTIONAL_REGEXP = /(\\\\)?\(([^)]+)\)/g;
var ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP = /([^\s^/]+)((\/[^\s^/]+)+)/g;
var DOUBLE_ESCAPE = '\\\\';

var CucumberExpression = function () {
  /**
   * @param expression
   * @param parameterTypeRegistry
   */
  function CucumberExpression(expression, parameterTypeRegistry) {
    _classCallCheck(this, CucumberExpression);

    this._expression = expression;
    this._parameterTypes = [];

    expression = this.processEscapes(expression);
    expression = this.processOptional(expression);
    expression = this.processAlternation(expression);
    expression = this.processParameters(expression, parameterTypeRegistry);
    expression = '^' + expression + '$';

    this._treeRegexp = new TreeRegexp(expression);
  }

  _createClass(CucumberExpression, [{
    key: 'processEscapes',
    value: function processEscapes(expression) {
      return expression.replace(ESCAPE_REGEXP, '\\$1');
    }
  }, {
    key: 'processOptional',
    value: function processOptional(expression) {
      return expression.replace(OPTIONAL_REGEXP, function (match, p1, p2) {
        return p1 === DOUBLE_ESCAPE ? '\\(' + p2 + '\\)' : '(?:' + p2 + ')?';
      });
    }
  }, {
    key: 'processAlternation',
    value: function processAlternation(expression) {
      return expression.replace(ALTERNATIVE_NON_WHITESPACE_TEXT_REGEXP, function (match) {
        // replace \/ with /
        // replace / with |
        var replacement = match.replace(/\//g, '|').replace(/\\\|/g, '/');
        return '(?:' + replacement + ')';
      });
    }
  }, {
    key: 'processParameters',
    value: function processParameters(expression, parameterTypeRegistry) {
      var _this = this;

      return expression.replace(PARAMETER_REGEXP, function (match, p1, p2) {
        if (p1 === DOUBLE_ESCAPE) return '\\{' + p2 + '\\}';

        var typeName = p2;
        var parameterType = parameterTypeRegistry.lookupByTypeName(typeName);
        if (!parameterType) throw new UndefinedParameterTypeError(typeName);
        _this._parameterTypes.push(parameterType);
        return buildCaptureRegexp(parameterType.regexps);
      });
    }
  }, {
    key: 'match',
    value: function match(text) {
      return Argument.build(this._treeRegexp, text, this._parameterTypes);
    }
  }, {
    key: 'regexp',
    get: function get() {
      return this._treeRegexp.regexp;
    }
  }, {
    key: 'source',
    get: function get() {
      return this._expression;
    }
  }]);

  return CucumberExpression;
}();

function buildCaptureRegexp(regexps) {
  if (regexps.length === 1) {
    return '(' + regexps[0] + ')';
  }

  var captureGroups = regexps.map(function (group) {
    return '(?:' + group + ')';
  });

  return '(' + captureGroups.join('|') + ')';
}

module.exports = CucumberExpression;