(function() {
  var Range, SelectorCache, SelfClosingTags, TagFinder, _;

  Range = require('atom').Range;

  _ = require('underscore-plus');

  SelectorCache = require('./selector-cache');

  SelfClosingTags = require('./self-closing-tags');

  module.exports = TagFinder = (function() {
    function TagFinder(editor) {
      this.editor = editor;
      this.tagPattern = /(<(\/?))([^\s>]+)([\s>]|$)/;
      this.wordRegex = /[^>\r\n]*/;
      this.tagSelector = SelectorCache.get('meta.tag | punctuation.definition.tag');
      this.commentSelector = SelectorCache.get('comment.*');
    }

    TagFinder.prototype.patternForTagName = function(tagName) {
      tagName = _.escapeRegExp(tagName);
      return new RegExp("(<" + tagName + "([\\s>]|$))|(</" + tagName + ">)", 'gi');
    };

    TagFinder.prototype.isRangeCommented = function(range) {
      var scopes;
      scopes = this.editor.scopeDescriptorForBufferPosition(range.start).getScopesArray();
      return this.commentSelector.matches(scopes);
    };

    TagFinder.prototype.isTagRange = function(range) {
      var scopes;
      scopes = this.editor.scopeDescriptorForBufferPosition(range.start).getScopesArray();
      return this.tagSelector.matches(scopes);
    };

    TagFinder.prototype.isCursorOnTag = function() {
      return this.tagSelector.matches(this.editor.getLastCursor().getScopeDescriptor().getScopesArray());
    };

    TagFinder.prototype.findStartTag = function(tagName, endPosition) {
      var pattern, scanRange, startRange, unpairedCount;
      scanRange = new Range([0, 0], endPosition);
      pattern = this.patternForTagName(tagName);
      startRange = null;
      unpairedCount = 0;
      this.editor.backwardsScanInBufferRange(pattern, scanRange, (function(_this) {
        return function(_arg) {
          var match, range, stop;
          match = _arg.match, range = _arg.range, stop = _arg.stop;
          if (_this.isRangeCommented(range)) {
            return;
          }
          if (match[1]) {
            unpairedCount--;
            if (unpairedCount < 0) {
              startRange = range.translate([0, 1], [0, -match[2].length]);
              return stop();
            }
          } else {
            return unpairedCount++;
          }
        };
      })(this));
      return startRange;
    };

    TagFinder.prototype.findEndTag = function(tagName, startPosition) {
      var endRange, pattern, scanRange, unpairedCount;
      scanRange = new Range(startPosition, this.editor.buffer.getEndPosition());
      pattern = this.patternForTagName(tagName);
      endRange = null;
      unpairedCount = 0;
      this.editor.scanInBufferRange(pattern, scanRange, (function(_this) {
        return function(_arg) {
          var match, range, stop;
          match = _arg.match, range = _arg.range, stop = _arg.stop;
          if (_this.isRangeCommented(range)) {
            return;
          }
          if (match[1]) {
            return unpairedCount++;
          } else {
            unpairedCount--;
            if (unpairedCount < 0) {
              endRange = range.translate([0, 2], [0, -1]);
              return stop();
            }
          }
        };
      })(this));
      return endRange;
    };

    TagFinder.prototype.findStartEndTags = function() {
      var endPosition, ranges;
      ranges = null;
      endPosition = this.editor.getLastCursor().getCurrentWordBufferRange({
        wordRegex: this.wordRegex
      }).end;
      this.editor.backwardsScanInBufferRange(this.tagPattern, [[0, 0], endPosition], (function(_this) {
        return function(_arg) {
          var endRange, entireMatch, isClosingTag, match, prefix, range, startRange, stop, suffix, tagName;
          match = _arg.match, range = _arg.range, stop = _arg.stop;
          stop();
          entireMatch = match[0], prefix = match[1], isClosingTag = match[2], tagName = match[3], suffix = match[4];
          if (range.start.row === range.end.row) {
            startRange = range.translate([0, prefix.length], [0, -suffix.length]);
          } else {
            startRange = Range.fromObject([range.start.translate([0, prefix.length]), [range.start.row, Infinity]]);
          }
          if (isClosingTag) {
            endRange = _this.findStartTag(tagName, startRange.start);
          } else {
            endRange = _this.findEndTag(tagName, startRange.end);
          }
          if ((startRange != null) && (endRange != null)) {
            return ranges = {
              startRange: startRange,
              endRange: endRange
            };
          }
        };
      })(this));
      return ranges;
    };

    TagFinder.prototype.findEnclosingTags = function() {
      var ranges;
      if (ranges = this.findStartEndTags()) {
        if (this.isTagRange(ranges.startRange) && this.isTagRange(ranges.endRange)) {
          return ranges;
        }
      }
      return null;
    };

    TagFinder.prototype.findMatchingTags = function() {
      if (this.isCursorOnTag()) {
        return this.findStartEndTags();
      }
    };

    TagFinder.prototype.parseFragment = function(fragment, stack, matchExpr, cond) {
      var match, topElem;
      match = fragment.match(matchExpr);
      while (match && cond(stack)) {
        if (SelfClosingTags.indexOf(match[1]) === -1) {
          topElem = stack[stack.length - 1];
          if (match[2] && topElem === match[2]) {
            stack.pop();
          } else if (match[1]) {
            stack.push(match[1]);
          }
        }
        fragment = fragment.substr(match.index + match[0].length);
        match = fragment.match(matchExpr);
      }
      return stack;
    };

    TagFinder.prototype.tagsNotClosedInFragment = function(fragment) {
      return this.parseFragment(fragment, [], /<(\w[-\w]*)|<\/(\w[-\w]*)/, function() {
        return true;
      });
    };

    TagFinder.prototype.tagDoesNotCloseInFragment = function(tags, fragment) {
      var escapedTag, matchExpr, stack, stackLength, tag;
      if (tags.length === 0) {
        return false;
      }
      stack = tags;
      stackLength = stack.length;
      tag = tags[tags.length - 1];
      escapedTag = _.escapeRegExp(tag);
      matchExpr = new RegExp("<(" + escapedTag + ")|<\/(" + escapedTag + ")");
      stack = this.parseFragment(fragment, stack, matchExpr, function(s) {
        return s.length >= stackLength || s[s.length - 1] === tag;
      });
      return stack.length > 0 && stack[stack.length - 1] === tag;
    };

    TagFinder.prototype.closingTagForFragments = function(preFragment, postFragment) {
      var tag, tags;
      tags = this.tagsNotClosedInFragment(preFragment);
      tag = tags[tags.length - 1];
      if (this.tagDoesNotCloseInFragment(tags, postFragment)) {
        return tag;
      } else {
        return null;
      }
    };

    return TagFinder;

  })();

}).call(this);
