// the tagRangeFinder function is

//   Copyright (C) 2011 by Daniel Glazman <daniel@glazman.org>

// released under the MIT license (../../LICENSE) like the rest of CodeMirror

CodeMirror.tagRangeFinder = function(cm, line, hideEnd) {

  var nameStartChar = "A-Z_a-z\\u00C0-\\u00D6\\u00D8-\\u00F6\\u00F8-\\u02FF\\u0370-\\u037D\\u037F-\\u1FFF\\u200C-\\u200D\\u2070-\\u218F\\u2C00-\\u2FEF\\u3001-\\uD7FF\\uF900-\\uFDCF\\uFDF0-\\uFFFD";

  var nameChar = nameStartChar + "\-\:\.0-9\\u00B7\\u0300-\\u036F\\u203F-\\u2040";

  var xmlNAMERegExp = new RegExp("^[" + nameStartChar + "][" + nameChar + "]*");



  var lineText = cm.getLine(line);

  var found = false;

  var tag = null;

  var pos = 0;

  while (!found) {

    pos = lineText.indexOf("<", pos);

    if (-1 == pos) // no tag on line

      return;

    if (pos + 1 < lineText.length && lineText[pos + 1] == "/") { // closing tag

      pos++;

      continue;

    }

    // ok we weem to have a start tag

    if (!lineText.substr(pos + 1).match(xmlNAMERegExp)) { // not a tag name...

      pos++;

      continue;

    }

    var gtPos = lineText.indexOf(">", pos + 1);

    if (-1 == gtPos) { // end of start tag not in line

      var l = line + 1;

      var foundGt = false;

      var lastLine = cm.lineCount();

      while (l < lastLine && !foundGt) {

        var lt = cm.getLine(l);

        var gt = lt.indexOf(">");

        if (-1 != gt) { // found a >

          foundGt = true;

          var slash = lt.lastIndexOf("/", gt);

          if (-1 != slash && slash < gt) {

            var str = lineText.substr(slash, gt - slash + 1);

            if (!str.match( /\/\s*\>/ )) { // yep, that's the end of empty tag

              if (hideEnd === true) l++;

              return l;

            }

          }

        }

        l++;

      }

      found = true;

    }

    else {

      var slashPos = lineText.lastIndexOf("/", gtPos);

      if (-1 == slashPos) { // cannot be empty tag

        found = true;

        // don't continue

      }

      else { // empty tag?

        // check if really empty tag

        var str = lineText.substr(slashPos, gtPos - slashPos + 1);

        if (!str.match( /\/\s*\>/ )) { // finally not empty

          found = true;

          // don't continue

        }

      }

    }

    if (found) {

      var subLine = lineText.substr(pos + 1);

      tag = subLine.match(xmlNAMERegExp);

      if (tag) {

        // we have an element name, wooohooo !

        tag = tag[0];

        // do we have the close tag on same line ???

        if (-1 != lineText.indexOf("</" + tag + ">", pos)) // yep

        {

          found = false;

        }

        // we don't, so we have a candidate...

      }

      else

        found = false;

    }

    if (!found)

      pos++;

  }



  if (found) {

    var startTag = "(\\<\\/" + tag + "\\>)|(\\<" + tag + "\\>)|(\\<" + tag + "\\s)|(\\<" + tag + "$)";

    var startTagRegExp = new RegExp(startTag, "g");

    var endTag = "</" + tag + ">";

    var depth = 1;

    var l = line + 1;

    var lastLine = cm.lineCount();

    while (l < lastLine) {

      lineText = cm.getLine(l);

      var match = lineText.match(startTagRegExp);

      if (match) {

        for (var i = 0; i < match.length; i++) {

          if (match[i] == endTag)

            depth--;

          else

            depth++;

          if (!depth) {

            if (hideEnd === true) l++;

            return l;

          }

        }

      }

      l++;

    }

    return;

  }

};



CodeMirror.braceRangeFinder = function(cm, line, hideEnd) {

  var lineText = cm.getLine(line), at = lineText.length, startChar, tokenType;

  for (;;) {

    var found = lineText.lastIndexOf("{", at);

    if (found < 0) break;

    tokenType = cm.getTokenAt({line: line, ch: found}).className;

    if (!/^(comment|string)/.test(tokenType)) { startChar = found; break; }

    at = found - 1;

  }

  if (startChar == null || lineText.lastIndexOf("}") > startChar) return;

  var count = 1, lastLine = cm.lineCount(), end;

  outer: for (var i = line + 1; i < lastLine; ++i) {

    var text = cm.getLine(i), pos = 0;

    for (;;) {

      var nextOpen = text.indexOf("{", pos), nextClose = text.indexOf("}", pos);

      if (nextOpen < 0) nextOpen = text.length;

      if (nextClose < 0) nextClose = text.length;

      pos = Math.min(nextOpen, nextClose);

      if (pos == text.length) break;

      if (cm.getTokenAt({line: i, ch: pos + 1}).className == tokenType) {

        if (pos == nextOpen) ++count;

        else if (!--count) { end = i; break outer; }

      }

      ++pos;

    }

  }

  if (end == null || end == line + 1) return;

  if (hideEnd === true) end++;

  return end;

};



CodeMirror.indentRangeFinder = function(cm, line) {

  var tabSize = cm.getOption("tabSize");

  var myIndent = cm.getLineHandle(line).indentation(tabSize), last;

  for (var i = line + 1, end = cm.lineCount(); i < end; ++i) {

    var handle = cm.getLineHandle(i);

    if (!/^\s*$/.test(handle.text)) {

      if (handle.indentation(tabSize) <= myIndent) break;

      last = i;

    }

  }

  if (!last) return null;

  return last + 1;

};



CodeMirror.newFoldFunction = function(rangeFinder, markText, hideEnd) {

  var folded = [];

  if (markText == null) markText = '<div style="position: absolute; left: 2px; color:#600">&#x25bc;</div>%N%';



  function isFolded(cm, n) {

    for (var i = 0; i < folded.length; ++i) {

      var start = cm.lineInfo(folded[i].start);

      if (!start) folded.splice(i--, 1);

      else if (start.line == n) return {pos: i, region: folded[i]};

    }

  }



  function expand(cm, region) {

    cm.clearMarker(region.start);

    for (var i = 0; i < region.hidden.length; ++i)

      cm.showLine(region.hidden[i]);

  }



  return function(cm, line) {

    cm.operation(function() {

      var known = isFolded(cm, line);

      if (known) {

        folded.splice(known.pos, 1);

        expand(cm, known.region);

      } else {

        var end = rangeFinder(cm, line, hideEnd);

        if (end == null) return;

        var hidden = [];

        for (var i = line + 1; i < end; ++i) {

          var handle = cm.hideLine(i);

          if (handle) hidden.push(handle);

        }

        var first = cm.setMarker(line, markText);

        var region = {start: first, hidden: hidden};

        cm.onDeleteLine(first, function() { expand(cm, region); });

        folded.push(region);

      }

    });

  };

};

