(function() {
  var AllDigitsRegex, CustomCaptureIndexRegex, DigitRegex, Pattern, _,
    __slice = [].slice;

  _ = require('underscore-plus');

  AllDigitsRegex = /\\\d+/g;

  CustomCaptureIndexRegex = /\$(\d+)|\${(\d+):\/(downcase|upcase)}/;

  DigitRegex = /\\\d+/;

  module.exports = Pattern = (function() {
    function Pattern(grammar, registry, options) {
      var applyEndPatternLast, begin, beginCaptures, capture, captures, contentName, end, endCaptures, endPattern, group, match, name, patterns, _ref, _ref1;
      this.grammar = grammar;
      this.registry = registry;
      if (options == null) {
        options = {};
      }
      name = options.name, contentName = options.contentName, match = options.match, begin = options.begin, end = options.end, patterns = options.patterns;
      captures = options.captures, beginCaptures = options.beginCaptures, endCaptures = options.endCaptures, applyEndPatternLast = options.applyEndPatternLast;
      this.include = options.include, this.popRule = options.popRule, this.hasBackReferences = options.hasBackReferences;
      this.pushRule = null;
      this.backReferences = null;
      this.scopeName = name;
      this.contentScopeName = contentName;
      if (match) {
        if ((end || this.popRule) && (this.hasBackReferences != null ? this.hasBackReferences : this.hasBackReferences = DigitRegex.test(match))) {
          this.match = match;
        } else {
          this.regexSource = match;
        }
        this.captures = captures;
      } else if (begin) {
        this.regexSource = begin;
        this.captures = beginCaptures != null ? beginCaptures : captures;
        endPattern = this.grammar.createPattern({
          match: end,
          captures: endCaptures != null ? endCaptures : captures,
          popRule: true
        });
        this.pushRule = this.grammar.createRule({
          scopeName: this.scopeName,
          contentScopeName: this.contentScopeName,
          patterns: patterns,
          endPattern: endPattern,
          applyEndPatternLast: applyEndPatternLast
        });
      }
      if (this.captures != null) {
        _ref = this.captures;
        for (group in _ref) {
          capture = _ref[group];
          if (((_ref1 = capture.patterns) != null ? _ref1.length : void 0) > 0 && !capture.rule) {
            capture.scopeName = this.scopeName;
            capture.rule = this.grammar.createRule(capture);
          }
        }
      }
      this.anchored = this.hasAnchor();
    }

    Pattern.prototype.getRegex = function(firstLine, position, anchorPosition) {
      if (this.anchored) {
        return this.replaceAnchor(firstLine, position, anchorPosition);
      } else {
        return this.regexSource;
      }
    };

    Pattern.prototype.hasAnchor = function() {
      var character, escape, _i, _len, _ref;
      if (!this.regexSource) {
        return false;
      }
      escape = false;
      _ref = this.regexSource;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        character = _ref[_i];
        if (escape && (character === 'A' || character === 'G' || character === 'z')) {
          return true;
        }
        escape = !escape && character === '\\';
      }
      return false;
    };

    Pattern.prototype.replaceAnchor = function(firstLine, offset, anchor) {
      var character, escape, escaped, placeholder, _i, _len, _ref;
      escaped = [];
      placeholder = '\uFFFF';
      escape = false;
      _ref = this.regexSource;
      for (_i = 0, _len = _ref.length; _i < _len; _i++) {
        character = _ref[_i];
        if (escape) {
          switch (character) {
            case 'A':
              if (firstLine) {
                escaped.push("\\" + character);
              } else {
                escaped.push(placeholder);
              }
              break;
            case 'G':
              if (offset === anchor) {
                escaped.push("\\" + character);
              } else {
                escaped.push(placeholder);
              }
              break;
            case 'z':
              escaped.push('$(?!\n)(?<!\n)');
              break;
            default:
              escaped.push("\\" + character);
          }
          escape = false;
        } else if (character === '\\') {
          escape = true;
        } else {
          escaped.push(character);
        }
      }
      return escaped.join('');
    };

    Pattern.prototype.resolveBackReferences = function(line, beginCaptureIndices) {
      var beginCaptures, end, resolvedMatch, start, _i, _len, _ref;
      beginCaptures = [];
      for (_i = 0, _len = beginCaptureIndices.length; _i < _len; _i++) {
        _ref = beginCaptureIndices[_i], start = _ref.start, end = _ref.end;
        beginCaptures.push(line.slice(start, end));
      }
      resolvedMatch = this.match.replace(AllDigitsRegex, function(match) {
        var index;
        index = parseInt(match.slice(1));
        if (beginCaptures[index] != null) {
          return _.escapeRegExp(beginCaptures[index]);
        } else {
          return "\\" + index;
        }
      });
      return this.grammar.createPattern({
        hasBackReferences: false,
        match: resolvedMatch,
        captures: this.captures,
        popRule: this.popRule
      });
    };

    Pattern.prototype.ruleForInclude = function(baseGrammar, name) {
      var _ref;
      if (name[0] === "#") {
        return this.grammar.getRepository()[name.slice(1)];
      } else if (name === "$self") {
        return this.grammar.getInitialRule();
      } else if (name === "$base") {
        return baseGrammar.getInitialRule();
      } else {
        this.grammar.addIncludedGrammarScope(name);
        return (_ref = this.registry.grammarForScopeName(name)) != null ? _ref.getInitialRule() : void 0;
      }
    };

    Pattern.prototype.getIncludedPatterns = function(baseGrammar, included) {
      var rule, _ref;
      if (this.include) {
        rule = this.ruleForInclude(baseGrammar, this.include);
        return (_ref = rule != null ? rule.getIncludedPatterns(baseGrammar, included) : void 0) != null ? _ref : [];
      } else {
        return [this];
      }
    };

    Pattern.prototype.resolveScopeName = function(scopeName, line, captureIndices) {
      var resolvedScopeName;
      return resolvedScopeName = scopeName.replace(CustomCaptureIndexRegex, function(match, index, commandIndex, command) {
        var capture, replacement;
        capture = captureIndices[parseInt(index != null ? index : commandIndex)];
        if (capture != null) {
          replacement = line.substring(capture.start, capture.end);
          while (replacement[0] === '.') {
            replacement = replacement.substring(1);
          }
          switch (command) {
            case 'downcase':
              return replacement.toLowerCase();
            case 'upcase':
              return replacement.toUpperCase();
            default:
              return replacement;
          }
        } else {
          return match;
        }
      });
    };

    Pattern.prototype.handleMatch = function(stack, line, captureIndices, rule, endPatternMatch) {
      var end, ruleToPush, scopes, start, tokens, zeroLengthMatch, _ref;
      scopes = this.grammar.scopesFromStack(stack, rule, endPatternMatch);
      if (this.scopeName && !this.popRule) {
        scopes.push(this.resolveScopeName(this.scopeName, line, captureIndices));
      }
      if (this.captures) {
        tokens = this.getTokensForCaptureIndices(line, _.clone(captureIndices), captureIndices, scopes, stack);
      } else {
        _ref = captureIndices[0], start = _ref.start, end = _ref.end;
        zeroLengthMatch = end === start;
        if (zeroLengthMatch) {
          tokens = [];
        } else {
          tokens = [this.grammar.createToken(line.slice(start, end), scopes)];
        }
      }
      if (this.pushRule) {
        ruleToPush = this.pushRule.getRuleToPush(line, captureIndices);
        ruleToPush.anchorPosition = captureIndices[0].end;
        stack.push(ruleToPush);
      } else if (this.popRule) {
        stack.pop();
      }
      return tokens;
    };

    Pattern.prototype.getTokensForCaptureRule = function(rule, line, captureStart, captureEnd, scopes, stack) {
      var captureText, tokens;
      captureText = line.substring(captureStart, captureEnd);
      tokens = rule.grammar.tokenizeLine(captureText, __slice.call(stack).concat([rule])).tokens;
      tokens = tokens.filter(function(token) {
        return token.value !== '';
      });
      return tokens;
    };

    Pattern.prototype.getTokensForCaptureIndices = function(line, currentCaptureIndices, allCaptureIndices, scopes, stack) {
      var captureHasNoScope, captureRule, captureTokens, childCapture, emptyCapture, parentCapture, previousChildCaptureEnd, scope, tokens, _ref, _ref1;
      parentCapture = currentCaptureIndices.shift();
      tokens = [];
      if (scope = (_ref = this.captures[parentCapture.index]) != null ? _ref.name : void 0) {
        scopes = scopes.concat(this.resolveScopeName(scope, line, allCaptureIndices));
      }
      if (captureRule = (_ref1 = this.captures[parentCapture.index]) != null ? _ref1.rule : void 0) {
        captureTokens = this.getTokensForCaptureRule(captureRule, line, parentCapture.start, parentCapture.end, scopes, stack);
        tokens.push.apply(tokens, captureTokens);
        while (currentCaptureIndices.length && currentCaptureIndices[0].start < parentCapture.end) {
          currentCaptureIndices.shift();
        }
      } else {
        previousChildCaptureEnd = parentCapture.start;
        while (currentCaptureIndices.length && currentCaptureIndices[0].start < parentCapture.end) {
          childCapture = currentCaptureIndices[0];
          emptyCapture = childCapture.end - childCapture.start === 0;
          captureHasNoScope = !this.captures[childCapture.index];
          if (emptyCapture || captureHasNoScope) {
            currentCaptureIndices.shift();
            continue;
          }
          if (childCapture.start > previousChildCaptureEnd) {
            tokens.push(this.grammar.createToken(line.slice(previousChildCaptureEnd, childCapture.start), scopes));
          }
          captureTokens = this.getTokensForCaptureIndices(line, currentCaptureIndices, allCaptureIndices, scopes, stack);
          tokens.push.apply(tokens, captureTokens);
          previousChildCaptureEnd = childCapture.end;
        }
        if (parentCapture.end > previousChildCaptureEnd) {
          tokens.push(this.grammar.createToken(line.slice(previousChildCaptureEnd, parentCapture.end), scopes));
        }
      }
      return tokens;
    };

    return Pattern;

  })();

}).call(this);
